localtime和gmtime获取的时间不可靠

多线程中使用localtimegmtime的风险

引言

在嵌入式设备管理中,我们经常会遇到设备需要在特定时间自动开关机的场景。然而,一个设备在预定关机后,意外地提前开机然后再次关机,这引起了我们的注意。本文将探讨这一现象背后的原因,并提供解决方案。

核心发现

我们发现,在多线程环境中,使用localtimegmtime函数可能会导致不可预测的结果。具体来说,localtime的返回值可能会被gmtime的结果篡改,这是一个严重的线程安全问题。

问题分析与解决

1. 代码审查与日志分析

通过代码审查和日志分析,我们发现设备在定时检测到当前时间符合条件后,会执行关机操作。然而,日志显示云服务返回的时间与预期存在8小时的差异。

2. 增加日志与挂测

为了进一步诊断问题,我们在代码中增加了打印时间戳的日志,并进行了长时间的挂测。结果发现,时间戳偶尔会出现异常跳动,与实际时间相差8小时。

3. 函数替换与挂测

考虑到localtime是非线程安全的,我们将其替换为线程安全的localtime_r,并继续进行挂测。这次,时间戳显示正常,没有出现之前的异常。

根因探究

尽管localtime是非线程安全的,但时间相差8小时的现象仍然令人费解。我们考虑了以下可能的原因:

  1. 时区变更:我们首先怀疑时区被意外改变,但日志中没有发现任何修改痕迹。
  2. 返回值被修改:我们检查了业务,没有发现有地方使用了localtime的返回值并进行了修改。
  3. uClibc库问题:我们分析了uClibc库中localtime的实现,并发现localtimelocaltime_r在时区处理上是一致的。但官网下载的uClibc版本与AX平台的不一致,我们怀疑是实现上的差异。

深入分析

进一步分析发现,localtime不仅使用全局指针(这是非线程安全的表现),而且与gmtime共享同一个指针。这与glibc的行为不同,可能是问题的关键所在。

struct tm __time_tm;    /* Global shared by gmtime() and localtime(). */

实验验证

为了验证我们的结论,我们设计了一个多线程实验:

  1. 创建多个线程,其中一部分线程使用localtime获取时间,并打印小时数。
  2. 另一部分线程使用gmtime获取时间。
  3. 观察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。这不仅可以避免潜在的线程安全问题,还可以确保程序的健壮性和可靠性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值