pthread

    技术2025-03-23  20

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

    还有很多

    Processed: 0.009, SQL: 9