由於工作上的事情,要用到線程之間的同步,而且有超時處理,在網上看到了使用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()
還有很多