localtime的安全性总结

文章目录

  1. 1. Windows
  2. 2. POSIX
  3. 3. 注意

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* 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
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的安全性总结/