std::localtime
这个函数功能是将time_t转换成本地时间的结构体tm。功能同C风格的函数localtime
,只是不带命名空间。
按照标准说法此函数不是线程安全的。
见cplusplus上提示
The function also accesses and modifies a shared internal object, which may introduce data races on concurrent calls to gmtime and localtime.
cpprefence上说得更明确
This function may not be thread-safe.
但在不同平台下实现还是不一样的。
Windows
在Windows下,此函数是线程安全的。
Both the 32-bit and 64-bit versions of gmtime, mktime, mkgmtime, and localtime all use a single tm structure per thread for the conversion.
意思是,不管是32位还是64位版本,这个函数都会为每一个线程分配一个单独的tm结构体。
1 | #include <ctime> |
输出1
2
3
4t1 (original) :Fri Dec 04 11:26:11 2015
t1 :Fri Dec 04 11:26:11 2015
t2 :Fri Dec 04 11:26:16 2015
在主线程中调用localtime
,打印的时间t1与在另一个线程中打印的时间t1是相同的,可见,Windows下的localtime
是线程安全的。
POSIX
此时函数就不是线程安全的。不是线程安全的原因是,这个函数内部使用了一个静态tm结构体,每个访问它的函数都会修改这个值。
与上面同样的代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20#include <ctime>
#include <iostream>
#include <thread>
int main()
{
std::time_t t1 = std::time(nullptr);
std::tm* t1_tm = std::localtime(&t1);
std::cout << "t1 (original) :" << std::asctime(t1_tm) << '\n';
std::this_thread::sleep_for(std::chrono::seconds(5));
std::thread thread1(std::bind([&t1_tm](){
std::time_t t2 = std::time(nullptr);
std::tm* t2_tm = std::localtime(&t2);
std::cout << "t1 (overwritten):" << std::asctime(t1_tm);
std::cout << "t2 :" << std::asctime(t2_tm);
}));
thread1.join();
return 0;
}
编译选项1
g++ -std=c++11 -pthread localtimetest.cpp -o localtimetest
结果是1
2
3
4t1 (original) :Fri Dec 4 11:30:40 2015
t1 (overwritten):Fri Dec 4 11:30:45 2015
t2 :Fri Dec 4 11:30:45 2015
另一个线程打印的时间t1与主线程中打印的时间t1不同,而与这个线程下的t2相同,说明这个线程中调用的localtime
修改了主线程中调用localtime
的结果。所以不是线程安全的。
注意
虽然Window下的localtime
是线程安全的,但是要注意在调用localtime
后,不要再使用前一个调用localtime
的结果,MSDN上说明:
Each call to one of these routines destroys the result of the previous call.
意思是每次调用这个函数都会破坏前一个调用的结果。
看示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21#include <ctime>
#include <iostream>
#include <thread>
int main()
{
std::time_t t1 = std::time(nullptr);
std::this_thread::sleep_for(std::chrono::seconds(5));
std::time_t t2 = std::time(nullptr);
// std::tm* points to static data that will be overwritten
// for any new localtime call
// 1) Print the original value before it is overwritten
std::tm* t1_tm = localtime(&t1);
std::cout << "t1 (original) :" << std::asctime(t1_tm) << '\n';
// 2) Print t2 and t1 after t1 is overwritten by t2
std::tm* t2_tm = localtime(&t2);
std::cout << "t1 (overwritten):" << std::asctime(t1_tm);
std::cout << "t2 :" << std::asctime(t2_tm);
return 0;
}
结果:1
2
3
4t1 (original) :Fri Dec 04 11:39:34 2015
t1 (overwritten):Fri Dec 04 11:39:39 2015
t2 :Fri Dec 04 11:39:39 2015
这个程序在第二次调用localtime
之后,就把第一次调用localtime
的结果t1_tm
覆盖了。这个错误会非常隐蔽的发生,要特别注意。当然可以使用localtime_s
(Windows)或者localtime_r
(POSIX)来避免。1
2struct tm *localtime_r(const time_t *timep, struct tm *result);
errno_t localtime_s( struct tm* _tm, const time_t *time );
原因就是,这两个函数会返回自定义的tm结构体。
声明:以上代码参考Exploring C++11, part 2 (localtime and time again)
版权声明