多线程中使用localtime和gmtime的风险
引言
在嵌入式设备管理中,我们经常会遇到设备需要在特定时间自动开关机的场景。然而,一个设备在预定关机后,意外地提前开机然后再次关机,这引起了我们的注意。本文将探讨这一现象背后的原因,并提供解决方案。
核心发现
我们发现,在多线程环境中,使用localtime和gmtime函数可能会导致不可预测的结果。具体来说,localtime的返回值可能会被gmtime的结果篡改,这是一个严重的线程安全问题。
问题分析与解决
1. 代码审查与日志分析
通过代码审查和日志分析,我们发现设备在定时检测到当前时间符合条件后,会执行关机操作。然而,日志显示云服务返回的时间与预期存在8小时的差异。
2. 增加日志与挂测
为了进一步诊断问题,我们在代码中增加了打印时间戳的日志,并进行了长时间的挂测。结果发现,时间戳偶尔会出现异常跳动,与实际时间相差8小时。
3. 函数替换与挂测
考虑到localtime是非线程安全的,我们将其替换为线程安全的localtime_r,并继续进行挂测。这次,时间戳显示正常,没有出现之前的异常。
根因探究
尽管localtime是非线程安全的,但时间相差8小时的现象仍然令人费解。我们考虑了以下可能的原因:
- 时区变更:我们首先怀疑时区被意外改变,但日志中没有发现任何修改痕迹。
- 返回值被修改:我们检查了业务,没有发现有地方使用了
localtime的返回值并进行了修改。 - uClibc库问题:我们分析了uClibc库中
localtime的实现,并发现localtime与localtime_r在时区处理上是一致的。但官网下载的uClibc版本与AX平台的不一致,我们怀疑是实现上的差异。
深入分析
进一步分析发现,localtime不仅使用全局指针(这是非线程安全的表现),而且与gmtime共享同一个指针。这与glibc的行为不同,可能是问题的关键所在。
struct tm __time_tm; /* Global shared by gmtime() and localtime(). */
实验验证
为了验证我们的结论,我们设计了一个多线程实验:
- 创建多个线程,其中一部分线程使用
localtime获取时间,并打印小时数。 - 另一部分线程使用
gmtime获取时间。 - 观察
localtime获取的小时数是否为本地时间,结果显示localtime获取的小时数出现了UTC时间。
以下是实验的代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
void* thread_localtime(void* arg) {
while(1) {
time_t rawtime;
struct tm* timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
printf("Local tm_hour:%d\n", timeinfo->tm_hour);
usleep(200*1000);
}
return NULL;
}
void* thread_gmtime(void* arg) {
while(1) {
time_t rawtime;
struct tm* utc_info;
time(&rawtime);
utc_info = gmtime(&rawtime);
printf("UTC tm_hour:%d\n", utc_info->tm_hour);
usleep(200*1000);
}
return NULL;
}
int main() {
pthread_t threads[10];
for (int i = 0; i < 10; i++) {
if (i < 5) {
pthread_create(&threads[i], NULL, thread_localtime, NULL);
} else {
pthread_create(&threads[i], NULL, thread_gmtime, NULL);
}
}
// 等待所有线程完成
for (int i = 0; i < 10; i++) {
pthread_join(threads[i], NULL);
}
return 0;
}
结论
在多线程环境中,为了避免全局指针被篡改的风险,建议使用线程安全的函数,如localtime_r。这不仅可以避免潜在的线程安全问题,还可以确保程序的健壮性和可靠性。

1500

被折叠的 条评论
为什么被折叠?



