文章目录
localtime线程不安全,localtime_r时区问题
1. 代码实现
#include <time.h>
/* The C Standard says that localtime and gmtime return the same pointer. */
struct tm _tmbuf;
/* Return the `struct tm' representation of *T in local time,
using *TP to store the result. */
struct tm *
__localtime_r (t, tp)
const time_t *t;
struct tm *tp;
{
return __tz_convert (t, 1, tp);
}
weak_alias (__localtime_r, localtime_r)
/* Return the `struct tm' representation of *T in local time. */
struct tm *
localtime (t)
const time_t *t;
{
return __tz_convert (t, 1, &_tmbuf);
}
libc_hidden_def (localtime)
都是调用__tz_convert(),区别在于:
- localtime传参及返回值为库中全局变量_tmbuf
- localtime_r参数及返回值都为上层的传参
2. 线程安全问题
2.1 原因
The four functions asctime(), ctime(), gmtime() and localtime() return a pointer to static data and hence are not thread-safe.
- localtime调用__tz_convert()第三个参数为全局变量_tmbuf,并且其返回值也是_tmbuf的指针。返回指针所指向的全局变量的值可能被其他线程调用localtime给修改掉。所以localtime是线程不安全的。
- localtime_r改用调用者传入的变量指针为参数,每个线程都会传入自己的变量指针,所以是线程安全的。
2.2 解决线程不安全
- 网上有看到一种localtime_r的实现,直接调用localtime,说改成了线程安全的实现了,其实依旧是线程不安全的。也许需要再加一层锁,不然问题一样。
static struct tm *
localtime_r (const time_t *t, struct tm *tp)
{
struct tm *l = localtime (t);
if (! l)
return 0;
*tp = *l;
return tp;
}
- 最好的方法可能是将localtime替换为调用glibc中的localtime_r,但localtime_r有时区问题。
3. 时区问题
According to POSIX.1-2004, localtime() is required to behave as though tzset(3) was called, while localtime_r() does not have this requirement. For portable code tzset(3) should be called before localtime_r().
翻译:localtime像调用过了tzset一样。localtime_r没有,所以之前需要主动调用tzset,否则时区更改后localtime_r并不会生效。
3.1 怎么理解“localtime像调用过了tzset一样”
tzset的实现就是调用tzset_internal,而__tz_convert中也会调用。
- localtime调用时(tp == &_tmbuf)成立为1,所以和tzset效果差不多,会去根据当前时区更新数据;
- 而localtime_r则为0,只会在初始化的时候更新一次时区数据。因此在时区更改之后,需要在调用localtime_r之前主动调用tzset,否则获取的时间就不会随时区而变化。
struct tm *
__tz_convert (const time_t *timer, int use_localtime, struct tm *tp)
{
......
__libc_lock_lock (tzset_lock); //加锁
tzset_internal (tp == &_tmbuf, 0);
...... //时间转换逻辑
__libc_lock_unlock (tzset_lock); //释放锁
......
return tp;
}
void
__tzset (void)
{
__libc_lock_lock (tzset_lock);
tzset_internal (1, 1);
if (!__use_tzfile)
{
/* Set `tzname'. */
__tzname[0] = (char *) tz_rules[0].name;
__tzname[1] = (char *) tz_rules[1].name;
}
__libc_lock_unlock (tzset_lock);
}
weak_alias (__tzset, tzset)
3.2 解决localtime_r时区问题
- 封装新接口。需要将工程中的localtime_r全部替换。
struct tm *
my_localtime_r (t, tp)
const time_t *t;
struct tm *tp;
{
tzset();
return localtime_r(t, tp);
}
- 重构localtime_r