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()也不是線程安全的系統調用。