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 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 :" << std ::asctime(t1_tm); std ::cout << "t2 :" << std ::asctime(t2_tm); })); thread1.join(); return 0 ; }
输出
1 2 3 4 t1 (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 4 t1 (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* t1_tm = localtime(&t1); std ::cout << "t1 (original) :" << std ::asctime(t1_tm) << '\n' ; 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 4 t1 (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 2 struct 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)
版权声明
本博客 思 采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可
本文永久链接:http://yoursite.com/2016/01/06/localtime的安全性总结/