Skip to content

localtime线程不安全造成的线上问题

Published: at 08:18 AM | 3 min read

线上出现了一个比较奇怪的问题,日期B的时间赋值给了日期A,但是浏览代码发现根本没有这样的逻辑。

经过一番调查基本能确定是localtime函数线程不安全造成的。

下面使用demo来模拟一下出现问题的情况
t1和t2是两个时间,使用GetTimeString转换的时候t2的时间可能会赋值给t1.

test.cpp

#include <time.h>
#include <iostream>
#include <string>
#include <vector>
#include <boost/thread.hpp>
#include <boost/shared_ptr.hpp>

const time_t t1 = 1595486546;
const time_t t2 = 2530972800;
const std::string &T1 = "2020-07-23 14:42:26";
const std::string &T2 = "2050-03-16 00:00:00";
const int COUNT = 100000;


std::string GetTimeString(time_t t) 
{
	if (t == -1) {
		return "";
	}

	struct tm *local = localtime(&t);

	char timeChars[50];
	strftime(timeChars, 50, "%Y-%m-%d %H:%M:%S", local);

	return std::string(timeChars);
}


void foo1()
{
	for (int i = 0; i < COUNT; i++) {
		std::string str = GetTimeString(t1);
		if (str != T1) {
			std::cout << "foo1 ERROR i:" << i << " " << str << std::endl;
			break;
		}
	}
}

void foo2()
{
	for (int i = 0; i < COUNT; i++) {
		std::string str = GetTimeString(t2);
		if (str != T2) {
			std::cout << "foo2 ERROR i:" << i << " " << str << std::endl;
			break;
		}
	}
}

int main()
{

	std::cout << "t1:" << GetTimeString(t1) << std::endl;
	std::cout << "t2:" << GetTimeString(t2) << std::endl;
	std::cout << "-------------start-----------" << std::endl;

	std::vector<boost::shared_ptr<boost::thread> > threadList;
	for (int i = 0; i < 10; i++) {
		if (i % 2 != 0) {
			boost::shared_ptr<boost::thread> p(new boost::thread(foo1));
			threadList.push_back(p);
		} else {
			boost::shared_ptr<boost::thread> p(new boost::thread(foo2));
			threadList.push_back(p);
		}
	}
	for (int i = 0; i < threadList.size(); i++) {
		if (threadList[i]->joinable()) {
			threadList[i]->join();
		}
	}

	std::cout << "exit" << std::endl;
	return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 2.8)
project( process )

SET(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
find_package(Boost REQUIRED COMPONENTS
thread   
)
if(NOT Boost_FOUND)
    message("Not found Boost")
endif()

include_directories(${Boost_INCLUDE_DIRS})
message("${Boost_INCLUDE_DIRS}")
message("${Boost_LIBRARIES}")

add_executable( process test.cpp )
target_link_libraries(process ${Boost_LIBRARIES})
~

编译:

cmake .
make

运行:

./process

结果:

[root@node172 tujiaw]# ./process
t1:2020-07-23 14:42:26
t2:2050-03-16 00:00:00
-------------start-----------
foo2 ERROR i:25281 2050-03-16 00:00:26
foo2 ERROR i:25611 2050-03-16 06:42:26
foo1 ERROR i:18045 2050-03-16 00:00:00
foo2 ERROR i:41655 2050-03-23 14:42:26
foo2 ERROR i:25632 2050-03-16 06:42:26
foo1 ERROR i:84292 2020-07-23 06:42:26
exit

以上是跑多次下来的结果,可以看到第一条foo1正确数据应该是t1,结果变成了2050年。

应该使用线程安全的localtime,如下代码在windows和linux下是线程安全的:

struct tm* LocalTimeSafe(time_t t, struct tm &local)
{
#ifdef WIN32
    // 目前的版本windows是线程安全的
    return localtime(&t);
#else
    // linux下localtime_r是线程安全的, localtime线程不安全
    return localtime_r(&t, &local);
#endif
}