setenv,getenv,mktime非线程安全函数,在多线程环境中使用要加锁

测试程序

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的源代码(下面的源代码位置都是相对于源码根目录的)

--------- 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中定义为:
#define __libc_lock_lock(NAME)
是个空操作,所以它不能起到同步线程的作用。

所以,可以看到,glibc的上述代码实现中,有两个问题:
1、tzset_internal 中使用的static变量is_initialized
(这个我们可以通过在程序中定义一个无用的全局变量,在线程开始工作前,它的初始化中调用一次mktime来克服)
2、mktime每次都要重写全局变量tzname/timezone/daylight
(这个问题,基本上,就没办法解决了)

所以mktime和localtime_r不适合于多线程应用。


解决方案有二:
1、自己实现mktime和localtime_r,但是这样时区的计算是麻烦的,当然也可以不使用时区信息,或者使用固定时区,比如北京时区,这样就简单多了。
2、用pthread的mutex来给mktime和localtime_r加锁,但是这样要使用pthread库,移植性不缓谩?/p>



下表是 UNIX环境高级编程列出 POSIX.1规范中的非线程安全的函数:


asctimeecvtgethostentgetutxlineputc_unlocked
basenameencryptgetlogingmtimeputchar_unlocked
catgetsendgrentgetnetbyaddrhcreateputenv
cryptendpwentgetnetbynamehdestroypututxline
ctimeendutxentgetopthsearchrand
dbm_clearerrfcvtgetprotobynameinet_ntoareaddir
dbm_closeftwgetprotobynumberL64asetenv
dbm_deletegetcvtgetprotobynumberlgammasetgrent
dbm_errorgetc_unlockedgetprotoentlgammafsetkey
dbm_fetchgetchar_unlockedgetpwentlgammalsetpwent
dbm_firstkeygetdategetpwnamlocaleconvsetutxent
dbm_nextkeygetenvgetpwuidlrand48strerror
dbm_opengetgrentgetservbynamemrand48strtok
dbm_storegetgrgidgetservbyportnftwttyname
dirnamegetgrnamgetserventnl_langinfounsetenv
dlerrorgethostbyaddrgetutxentptsnamewcstombs
drand48gethostbynamegetutxidptsnameectomb

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值