1. 程式人生 > >mktime和localtime_r能在多執行緒環境下使用麼?

mktime和localtime_r能在多執行緒環境下使用麼?

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庫,移植性不夠好。