由于工作上的事情,要用到线程之间的同步,而且有超时处理,在网上看到了使用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
#include
#include
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()
还有很多