linux平台,對線程等待和喚醒操作的封裝(pthread_cond_timedwait 用法詳解)


前言 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等語言處理起來那么優雅。通過本文介紹的方法,將復雜的問題封裝起來,讓使用者用起來得心應手,這就是函數封裝要達到的目的。
 
       


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM