多線程使用linux時間函數的方法



linux的時間函數有其特別需要注意的使用方法,在工程項目中,這點很容易忽視,本文就時間函數在多線程中的使用作一個小結。

首先看一個函數,取下一天的功能函數,該函數使用了時間函數localtime或者localtime_r來獲取系統時間。
int GetNextTime(int curtm)
{
 struct tm t;
 t.tm_year = curtm/10000 - 1900;
 t.tm_mon = ((curtm/100)%100 - 1);
 t.tm_mday = (curtm)%100;
 t.tm_hour = 1;
 t.tm_min = 0;
 t.tm_sec = 0;

 time_t nt;
 //struct tm *local;
 struct tm local;
 char buf[16];

 nt = mktime(&t) + 24 * 3600;
 //local = localtime(&nt); // ------------------(1)
 localtime_r(&nt, &local); // ------------------(2)
 sprintf(buf, "%4d%02d%02d%02d%02d%02d", local.tm_year+1900,local.tm_mon+1,local.tm_mday,0,0,0);
 
 return (int)(atol(buf)/1000000);
}

接着,將函數封裝在線程類中,以方便測試多線程使用時間函數localtime或者localtime_r。
class mythread : public Thread
{
 public:
  mythread():m_isRun(false) {}
  virtual void Run()
  {
   int begin = 19860101;
   int end = 20130104;
   int count = 0;
   int cur = begin;
   int next;
   while(cur != end)
   {
    next = GetNextTime(cur);
    count++;
    cur = next;
   }
   if(count != 9865) // 19860101-20130104理論上一共有9865天,如果多線程算出的不是,則打印出來
    cout << count << endl;
  }

  virtual void Stop(){m_isRun=true;}
  virtual ~mythread() {}
 private:
  bool m_isRun;
};

測試:
void threadtest()
{
 for(int k=0;k<20;k++)
 {
  mythread p[30];
  for(int i = 1;i<30;i++)
    p[i].Start();
  for(int i = 1;i<30;i++)
    p[i].Join();
  for(int i = 1;i<30;i++)
    p[i].Stop();
  cout << "the " << k << " test end!" << endl;
 }
}

結果可見,如果時間函數用(1)則,開多線程調用就會報出count不是正確的數量;而用(2)則正確。因此,linux時間函數中——
localtime函數只能用於單線程;而多線程中應該選擇用localtime_r。後者是線程安全的時間函數。

mktime,localtime_r,gettimeofday是線程安全的嗎?

一下摘自:http://hi.baidu.com/pkuyikai/item/aad084ca252966d797445246

之前爲了診斷系統的檢索性能中的問題,分階段地對程序進行了計時,診斷出的結果是:記錄越多,那麼這三個階段所消耗的時間就越長。而反覆review三個階段的主要工作代碼,卻發現這些代碼根本不可能這麼耗時。慢慢地,順藤摸瓜地查出時間消耗都發生在獲取時間值的方法——mktime()上。於是大家一致懷疑是mktime()方法以及相關的localtime_r()方法都不是線程安全的。可其他項目中也用這些方法,爲什麼他們就沒有發現這樣的問題呢?

在glibc的文檔描述中,glibc提供了一個線程安全的方法localtime_r來代替localtime。mktime不存在線程不安全的問題。所以,按照glibc的文檔,在多線程環境下可以安全的使用localtime_r和mktime,實際情況並非如此。找來mktime()和localtime_r()的源代碼,終於發現了奧妙所在。

mktime和localtime_r在實現上都考慮了時區的轉換,而時區的計算要使用全局變量tzname/timezone/daylight。這本質上就是線程不安全的。

通過對glibc中這兩個系統函數源代碼的分析可以知道,它們實現中有兩個問題:

1、tzset_internal 中使用的static變量is_initialized

2、mktime每次都要重寫全局變量tzname/timezone/daylight

所以mktime和localtime_r不適合於多線程應用。

 

解決方案有二:

1、自己實現mktime和localtime_r,但是這樣時區的計算是麻煩的,當然也可以不使用時區信息,或者使用固定時區,比如北京時區,這樣就簡單多了。由於公司的服務器基本不會有時區問題,因此我們也自己實現了這兩個方法。

2、用pthread的mutex來給mktime和localtime_r加鎖,但是這樣要使用pthread庫,移植性不夠好。


    gettimeofday()這個syscall用來供用戶獲取timeval格式的當前時間信息(精確度爲微秒級),以及系統的當前時區信息(timezone)。結構類型 timeval的指針參數tv指向接受時間信息的用戶空間緩衝區,參數tz是一個timezone結構類型的指針,指向接收時區信息的用戶空間緩衝區。這 兩個參數均爲輸出參數,返回值0表示成功,返回負值表示出錯。
   

首先來看一下spin_lock機制。spin_lock機制和semaphore機制解決的都是兩個進程的互斥問題,都是讓一個進程退出臨界區後另一個進程才進入的方法,不過sempahore機制實行的是讓進程 暫時讓出cpu,進入等待隊列等待的策略,而spin_lock實行的卻是卻進程在原地空轉,等着另一個進程結束的策略。

    gettimeofday()代碼中的read_lock_irqsave(lock, flags)以及read_lock_irq(lock), read_lock_bh(lock) 和 write_lock_irqsave(lock, flags) , write_lock_irq(lock), write_lock_bh(lock)都是spin_lock的一個小小的變型,而Spin_lock採用的方式是讓一個進程運行,另外的進程忙等待,由於在只有一個cpu的機器(UP)上微觀上只有一個進程在運行。因此gettimeofday()也不是線程安全的系統調用。
發佈了16 篇原創文章 · 獲贊 40 · 訪問量 27萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章