pthread_cond_timedwait&&pthread_cond_wait&&sigwait()

由於工作上的事情,要用到線程之間的同步,而且有超時處理,在網上看到了使用pthread_cond_timedwait()函數和pthread_cond_wait()函數,其實2個函數都差不多,我主要是要用pthread_cond_timedwait()函數。代替不可控的sleep函數。

pthread_cond_timedwait()函數有三個入口參數:

(1)pthread_cond_t __cond:條件變量(觸發條件)

(2)pthread_mutex_t __mutex: 互斥鎖

(3)struct timespec __abstime: 等待時間(其值爲系統時間 + 等待時間)

當在指定時間內有信號傳過來時,pthread_cond_timedwait()返回0,否則返回一個非0數(我沒有找到返回值的定義);

在使用pthread_cond_timedwait()函數時,必須有三步:

1:加互斥鎖:pthread_mutex_lock(&__mutex)

2:等待:pthread_cond_timedwait(&__cond, &__mutex, &__abstime)   //解鎖->等待->加鎖

3:解互斥鎖:pthread_mutex_unlock(&__mutex)

發送信號量時,也要有三步:

1:加互斥鎖:pthread_mutex_lock(&__mutex)

2:發送:pthread_cond_signal(&__cond)

3:解互斥鎖:pthread_mutex_unlock(&__mutex)

pthread_cond_timedwait()在官方文檔中介紹了按絕對時間等待超時,但是幾乎沒有對按相對等待做說明。然而絕對時間模式有個最致命的缺點,就是在設置等待時間的時候,若系統時間發生了調整,可能出現永遠等不到超時的極端情況。使用相對時間可以避免上述問題。所以需要使用CLOCK_MONOTONIC,相對時間來防止時間戳跳變,非常安全。

在pthread_cond_wait時執行pthread_cancel後,要先在pthread_cleanup handler時要先解鎖已與相應條件變量綁定的mutex。這樣是爲了保證pthread_cond_wait可以返回到調用線程。所以需要cleanup函數配套使用,pthread_cleanup_push(cleanup, NULL);註冊cleanup。pthread_cleanup_pop(0);取消。

sigwait是同步的等待信號的到來,而不是像進程中那樣是異步的等待信號的到來。sigwait函數使用一個信號集作爲他的參數,並且在集合中的任一個信號發生時返回該信號值,解除阻塞,然後可以針對該信號進行一些相應的處理。在多線程代碼中,總是使用sigwait或者sigwaitinfo或者sigtimedwait等函數來處理信號。而不是signal或者sigaction等函數。因爲在一個線程中調用signal或者sigaction等函數會改變所以線程中的信號處理函數。而不是僅僅改變調用signal/sigaction的那個線程的信號處理函數。調用sigwait同步等待的信號必須在調用線程中被屏蔽,並且通常應該在所有的線程中被屏蔽(這樣可以保證信號絕不會被送到除了調用sigwait的任何其它線程),這是通過利用信號掩碼的繼承關係來達到的。


代碼如下

#include <stdio.h>
#include <pthread.h>
#include <time.h>
 
typedef struct mutex_cond
{
	pthread_condattr_t cattr;
        pthread_mutex_t i_mutex;
        pthread_cond_t i_cv;
		void* i_sigevent; 
}mutex_cond_t;
mutex_cond_t mcond;

//CLOCK_MONOTONIC 
int pthread_cond_timedwait_init(void){
    int ret = -1;
    ret = pthread_condattr_init(&(mcond.cattr));
    if (ret != 0)
    {
        printf("pthread_condattr_init failed %d\n", ret);
        return ret;
    }
    mcond.i_sigevent = NULL;
    ret = pthread_mutex_init ( &(mcond.i_mutex), NULL);
    ret = pthread_condattr_setclock(&(mcond.cattr), CLOCK_MONOTONIC);
    ret = pthread_cond_init(&(mcond.i_cv), &(mcond.cattr));

    return ret;
}

#define handle_error_en(en, msg)\
        do { errno= en; perror(msg);exit(EXIT_FAILURE);}while(0)

static void *sig_thread(void*arg)
{
      sigset_t *set=(sigset_t*) arg;
      int s, sig;

      for (;;){
            s = sigwait(set,&sig);
            if (s != 0)
                  handle_error_en(s,"sigwait");
            printf("Signal handling thread got signal %d\n", sig);
            //sent signal
            pthread_mutex_lock(&(mcond.i_mutex));
            pthread_cond_signal(&(mcond.i_cv));
            pthread_mutex_unlock(&(mcond.i_mutex));
      }
}


void cleanup(void *arg) 
{ 
    printf("cleanup !\n");
    pthread_mutex_unlock(&(mcond.i_mutex));
} 

// g++ -o pwait pwait.cpp -lpthread -lrt
int main()
{
        sigset_t set;
        int s;
        pthread_t sigwait_thread = NULL;

	pthread_cond_timedwait_init();

        sigemptyset(&set);
        sigaddset(&set, SIGUSR1);
        s = pthread_sigmask(SIG_BLOCK,&set,NULL);
        if (s!= 0)
            handle_error_en(s,"pthread_sigmask");
        s = pthread_create(&sigwait_thread,NULL,&sig_thread,(void*)&set);
        if (s!= 0)
            handle_error_en(s,"sig pthread_create");
 
    	struct timespec tv;
	while(1)
	{
	    pthread_testcancel();//若有取消信號,取消線程
            pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);//不可取消線程,阻塞取消signal

            //do somethings
            
            pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);//可以取消
            pthread_cleanup_push(cleanup, NULL);  // thread cleanup handler 
            pthread_mutex_lock(&(mcond.i_mutex));
            clock_gettime(CLOCK_MONOTONIC, &tv);
	    printf("now time:%d\n", tv.tv_sec);
	    tv.tv_sec += 60;// 設置20秒後沒收到事件超時返回
	    ret = pthread_cond_timedwait(&(mcond.i_cv), &(mcond.i_mutex), &tv);
            pthread_mutex_unlock(&(mcond.i_mutex));
            pthread_cleanup_pop(0);

	}
	
	return 0;
}

此時發送信號給進程kill -11 pid,main會提前解除休眠。

那麼什麼是取消點呢?:

取消點是在程序在運行的時候檢測是否收到取消請求,是否允許允許操作執行的點。下面的POSIX線程函數就是取消點:
pthread_join()
 pthread_cond_wait()
pthread_cond_timedwait()
 pthread_testcancel()
sem_wait()
sigwait()

還有很多

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章