前言 linux平台下,線程等待和喚醒操作是很常見的,但是平台函數不易使用;筆者對此操作做了封裝,使之更易於使用。
線程等待和喚醒函數比較
平台提供了線程等待相關函數,這些函數之間用法也有些差異:
sleep | 線程等待,等待期間線程無法喚醒。 |
pthread_cond_wait | 線程等待信號觸發,如果沒有信號觸發,無限期等待下去。 |
pthread_cond_timedwait | 線程等待一定的時間,如果超時或有信號觸發,線程喚醒。 |
通過上表,可以看出pthread_cond_timedwait函數是最為靈活,使用也最為廣泛。sleep的缺陷是當有緊急事件到達時,線程無法及時喚醒。pthread_cond_wait缺陷是:必須借助別的線程觸發信號,否則線程自身無法喚醒,如果使用函數,線程無法處理定時任務。
一般情況下,線程要做的工作可能有:定期處理某個事物;無事可做時,線程掛起;有事可做時,立即喚醒工作。要完成上面所述的功能,必須用pthread_cond_timedwait函數,本文介紹的就是對該函數封裝。
線程喚醒操作還涉及互斥量pthread_mutex_t,感覺與我們理解的等待和喚醒操作無關;此函數的引入,增加了理解難度。本文封裝完全屏蔽了此概念。
函數定義如下
//函數涉及的變量 typedef struct ThreadSignal_T { BOOL relativeTimespan; //是否采用相對時間 pthread_cond_t cond; pthread_mutex_t mutex; pthread_condattr_t cattr; } ThreadSignal; //初始化 void ThreadSignal_Init(ThreadSignal *signal,BOOL relativeTimespan); //關閉 void ThreadSignal_Close(ThreadSignal *signal); //等待n毫秒 void ThreadSignal_Wait(ThreadSignal *signal, int ms); //喚醒線程 void ThreadSignal_Signal(ThreadSignal *signal);
上述函數定義非常直觀,利於理解。但是平台提供的函數,就不是那么直觀。我把上述函數的實現一一列出來。
1)ThreadSignal_Init
void ThreadSignal_Init(ThreadSignal *signal, BOOL relativeTimespan) { //relativeTimespan 是不是采用相對時間等待。參見函數 ThreadSignal_Wait signal->relativeTimespan = relativeTimespan; pthread_mutex_init(&signal->mutex, NULL); if (relativeTimespan) { //如果采用相對時間等待,需要額外的處理。 //采用相對時間等待。可以避免:因系統調整時間,導致等待時間出現錯誤。 int ret = pthread_condattr_init(&signal->cattr); ret = pthread_condattr_setclock(&signal->cattr, CLOCK_MONOTONIC); ret = pthread_cond_init(&signal->cond, &signal->cattr); } else { pthread_cond_init(&signal->cond, NULL); } }
2) ThreadSignal_Close
void ThreadSignal_Close(ThreadSignal *signal) { if (signal->relativeTimespan) { pthread_condattr_destroy(&(signal->cattr)); } pthread_mutex_destroy(&signal->mutex); pthread_cond_destroy(&signal->cond); }
3) ThreadSignal_Wait
void ThreadSignal_Wait(ThreadSignal *signal, int ms) { pthread_mutex_lock(&signal->mutex); if (signal->relativeTimespan) { //獲取時間 struct timespec outtime; clock_gettime(CLOCK_MONOTONIC, &outtime); //ms為毫秒,換算成秒 outtime.tv_sec += ms/1000; //在outtime的基礎上,增加ms毫秒 //outtime.tv_nsec為納秒,1微秒=1000納秒 //tv_nsec此值再加上剩余的毫秒數 ms%1000,有可能超過1秒。需要特殊處理 uint64_t us = outtime.tv_nsec/1000 + 1000 * (ms % 1000); //微秒 //us的值有可能超過1秒, outtime.tv_sec += us / 1000000; us = us % 1000000; outtime.tv_nsec = us * 1000;//換算成納秒 int ret = pthread_cond_timedwait(&signal->cond, &signal->mutex, &outtime); } else { struct timeval now; gettimeofday(&now, NULL); //在now基礎上,增加ms毫秒 struct timespec outtime; outtime.tv_sec = now.tv_sec + ms / 1000; //us的值有可能超過1秒 uint64_t us = now.tv_usec + 1000 * (ms % 1000); outtime.tv_sec += us / 1000000; us = us % 1000000; outtime.tv_nsec = us * 1000; int ret = pthread_cond_timedwait(&signal->cond, &signal->mutex, &outtime); } pthread_mutex_unlock(&signal->mutex); }
上述函數處理起來有點啰嗦,有些讀者可能認為這是多此一舉,其實不然。 struct timespec outtime;結構中有兩個值:tv_sec ,tv_usec 。分別是秒和納秒。等待一段時間就是:在這兩個值上增加一定的數值。
tv_usec 此值有范圍限制的,就是不能超過1秒暨1000000000納秒。如果超出1秒,就要在tv_sec 此值增加一秒;tv_usec 減去一秒。筆者是在實踐中發現此問題的,不是無中生有。如果tv_usec 此值溢出,調用pthread_cond_timedwait函數,會立馬返回。
4)ThreadSignal_Signal
void ThreadSignal_Signal(ThreadSignal *signal) { pthread_mutex_lock(&signal->mutex); pthread_cond_signal(&signal->cond); pthread_mutex_unlock(&signal->mutex); }
后記 一個簡單的信號等待操作,linux下處理起來就如此復雜,遠不如c#、java等語言處理起來那么優雅。通過本文介紹的方法,將復雜的問題封裝起來,讓使用者用起來得心應手,這就是函數封裝要達到的目的。