转自:
测试程序
- snippet.c
pthread_mutex_t st_lock; void* test_thd_func(void *p_arg) { pthread_detach(pthread_self()); char ac_name[1024] = {}; for (int i = 0; i < 10000000; i++) { sprintf(ac_name,"stra_1_fe_filter_dt_other_hadoop_arg_%d",i); printf("setenv %s\n",ac_name); pthread_mutex_lock(&st_lock); setenv(ac_name," 1500",0); pthread_mutex_unlock(&st_lock); } pthread_exit(0); return NULL; } void test_time() { pthread_mutex_init(&st_lock,NULL); pthread_t st_thd; pthread_create(&st_thd,NULL,test_thd_func,NULL); for (int i = 0; i < 10000000; i++) { /*测试非法日期会不会导致程序异常*/ struct tm st_tm2; memset(&st_tm2,0,sizeof(st_tm2)); strptime("189912310000","%Y%m%d%H%M",&st_tm2); pthread_mutex_lock(&st_lock); time_t st_time2 = mktime(&st_tm2); pthread_mutex_unlock(&st_lock); printf("time_t=%lld\n",st_time2); } }
这段代友如果不加锁的话会出现core,因为在 线程A中调用了setenv,而在线程B中调用了mktime,mktime的实现中会调用getenv,而setenv和getenv都是非线程安全的,所以会出现core。
下面转一篇类似的文章:
mktime和localtime_r能在多线程环境下使用么? 2008-08-25 16:42 localtime和mktime是用来在时间分量和时间秒数之间进行转换的标准c函数。 在glibc的文档描述中,localtime的实现是使用了一个内部静态缓存来保存结果,所以这是一个不可用于多线程环境的api。glibc提供了一个线程安全版本localtime_r。mktime不存在这个问题。 所以,按照glibc的文档,在多线程环境下可以安全的使用localtime_r和mktime,实际情况并非如此。 mktime和localtime_r在实现上都考虑了时区的转换,而时区的计算要使用全局变量tzname/timezone/daylight。这本质上就是线程不安全的。
参考glibc-2.3.2的源代码(下面的源代码位置都是相对于源码根目录的)
- snippet.c
--------- time/localtime.c 和 time/tzset.c localtime_r中调用了tzset_internal来设置时区,入口参数为always=0,所以理论上只要第一次初次化过了,就不需初始化了。参考下面的代码。 但是由于引入了静态变量is_initialized,在多线程环境下,这种实现代码是有问题的。无法保证并发执行环境下的正确性。 ---- (time/tzset.c) ------- /* Interpret the TZ envariable. */ static void internal_function tzset_internal (always) int always; { static int is_initialized; register const char *tz; register size_t l; char *tzbuf; unsigned short int hh, mm, ss; unsigned short int whichrule; if (is_initialized && !always) return; is_initialized = 1; ........... } 但是mktime不是这样的, ---- (time/mktime.c) ------- /* Convert *TP to a time_t value. */ time_t mktime (tp) struct tm *tp; { #ifdef _LIBC /* POSIX.1 8.1.1 requires that whenever mktime() is called, the time zone names contained in the external variable `tzname' shall be set as if the tzset() function had been called. */ __tzset (); #endif return __mktime_internal (tp, my_mktime_localtime_r, &localtime_offset); } 由于_LIBC被定义,所以tzset将每次都被调用,而tzset的代码是这样的 ---- (time/tzset.c) ------- void __tzset (void) { __libc_lock_lock (tzset_lock); tzset_internal (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); }
tzset_internal将每次都被调用,时区信息将每次都被重写。
需要说明的是,前面的宏__libc_lock_lock在sysdeps/generic/bits/libc-lock.h中定义为:
- snippet.c
#define __libc_lock_lock(NAME)
是个空操作,所以它不能起到同步线程的作用。
是个空操作,所以它不能起到同步线程的作用。
所以,可以看到,glibc的上述代码实现中,有两个问题:
- tzset_internal 中使用的static变量is_initialized(这个我们可以通过在程序中定义一个无用的全局变量,在线程开始工作前,它的初始化中调用一次mktime来克服)
- mktime每次都要重写全局变量tzname/timezone/daylight(这个问题,基本上,就没办法解决了)
所以mktime和localtime_r不适合于多线程应用。
解决方案有二:
- 自己实现mktime和localtime_r,但是这样时区的计算是麻烦的,当然也可以不使用时区信息,或者使用固定时区,比如北京时区,这样就简单多了。
- 用pthread的mutex来给mktime和localtime_r加锁,但是这样要使用pthread库。
下表是 UNIX 环境高级编程列出 POSIX.1 规范中的非线程安全的函数:
:———– | —————- | —————- | ———– | —————- |
asctime | ecvt | gethostent | getutxline | putc_unlocked |
basename | encrypt | getlogin | gmtime | putchar_unlocked |
catgets | endgrent | getnetbyaddr | hcreate | putenv |
crypt | endpwent | getnetbyname | hdestroy | pututxline |
ctime | endutxent | getopt | hsearch | rand |
dbm_clearerr | fcvt | getprotobyname | inet_ntoa | readdir |
dbm_close | ftw | getprotobynumber | L64a | setenv |
dbm_delete | getcvt | getprotobynumber | lgamma | setgrent |
dbm_error | getc_unlocked | getprotoent | lgammaf | setkey |
dbm_fetch | getchar_unlocked | getpwent | lgammal | setpwent |
dbm_firstkey | getdate | getpwnam | localeconv | setutxent |
dbm_nextkey | getenv | getpwuid | lrand48 | strerror |
dbm_open | getgrent | getservbyname | mrand48 | strtok |
dbm_store | getgrgid | getservbyport | nftw | ttyname |
dirname | getgrnam | getservent | nl_langinfo | unsetenv |
dlerror | gethostbyaddr | getutxent | ptsname | wcstombs |
drand48 | gethostbyname | getutxid | ptsname | ectomb |
除此之外:
inet_ntoa —→ inet_ntop
asctime(), ctime(), gmtime() and localtime()返回指针指向静态数据,因此不是线程安全的。
线程安全版本为time_r(), ctime_r(), gmtime_r() and localtime_r()are specified by SUSv2, and available since libc 5.2.5. 最好使用使用gettimeofday。
gethostbyname, gethostbyaddr是不可重入函数;已经被getaddrinfo, getnameinfo替代。
打印时间,使用strftime。