linux c编程:互斥锁条件变量
条件变量:等待与信号发送
使用互斥锁虽然可以解决一些资源竞争的问题,但互斥锁只有两种状态(加锁和解锁),这限制了互斥锁的用途。
条件变量(条件锁)也可以解决线程同步和共享资源访问的问题,条件变量是对互斥锁的补充,它允许一个线程阻塞并等待另一个线程发送的信号,当收到信号时,阻塞的线程被唤醒并试图锁定与之相关的互斥锁。
条件变量初始化
条件变量和互斥锁一样,都有静态动态两种创建方式,静态方式使用PTHREAD_COND_INITIALIZER常量,如下:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER
动态方式调用函数int pthread_cond_init,API定义如下:
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
条件变量的属性由参数attr指定,如果参数attr为NULL,那么就使用默认的属性设置。尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,因此cond_attr值通常为NULL,且被忽略。多线程不能同时初始化一个条件变量,因为这是原子操作。如果函数调用成功,则返回0,并将新创建的条件变量的ID放在参数cond中。
解除条件变量
int pthread_cond_destroy(pthread_cond_t *cond);调用destroy函数解除条件变量并不会释放存储条件变量的内存空间。
条件变量阻塞(等待)
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abtime);
等待有两种方式:条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEDOUT,结束等待,其中abstime以与系统调用time相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。
无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()或pthread_cond_timedwait()(下同)的竞争条件(Race Condition)。mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者自适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。阻塞时处于解锁状态。
激活
int pthread_cond_signal(pthread_cond_t *cond);int pthread_cond_broadcast(pthread_cond_t *cond);pthread_cond_signal函数的作用是发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行,如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。共享变量的状态改变必须遵守lock/unlock的规则:需要在同一互斥锁的保护下使用pthread_cond_signal(即pthread_cond_wait必须放在pthread_mutex_lock和pthread_mutex_unlock之间)否则条件变量可以在对关联条件变量的测试和pthread_cond_wait带来的阻塞之间获得信号,这将导致无限期的等待(死锁)。因为他要根据共享变量的状态来决定是否要等待,所以为了避免死锁,必须要在lock/unlock队中。共享变量的状态改变必须遵守lock/unlock的规则:pthread_cond_signal即可以放在pthread_mutex_lock和pthread_mutex_unlock之间,也可以放在pthread_mutex_lock和pthread_mutex_unlock之后,但是各有优缺点。若为前者,在某些线程的实现中,会造成等待线程从内核中唤醒(由于cond_signal)然后又回到内核空间(因为cond_wait返回后会有原子加锁的行为),所以一来一回会有性能的问题(上下文切换)。详细来说就是,当一个等待线程被唤醒的时候,它必须首先加锁互斥量(参见pthread_cond_wait()执行步骤)。如果线程被唤醒而此时通知线程任然锁住互斥量,则被唤醒线程会立刻阻塞在互斥量上,等待通知线程解锁该互斥量,引起线程的上下文切换。当通知线程解锁后,被唤醒线程继续获得锁,再一次的引起上下文切换。这样导致被唤醒线程不能顺利加锁,延长了加锁时间,加重了系统不必要的负担。但是在LinuxThreads或者NPTL里面,就不会有这个问题,因为在Linux线程中,有两个队列,分别是cond_wait队列和mutex_lock队列,cond_signal只是让线程从cond_wait队列移到mutex_lock队列,而不用返回到用户空间,不会有性能的损耗,因此Linux推荐这种形式。而后者不会出现之前说的那个潜在的性能损耗,因为在signal之前就已经释放锁了。但如果unlock和signal之前,有个低优先级的线程正在mutex上等待的话,那么这个低优先级的线程就会抢占高优先级的线程(cond_wait的线程)。而且,假设而这在上面的放中间的模式下是不会出现的。而对于pthread_cond_broadcast函数,它使所有由参数cond指向的条件变量阻塞的线程退出阻塞状态,如果没有阻塞线程,则函数无效。实例代码如下:void *produce_cond(void *arg){for(;;){pthread_mutex_lock(&put.mutex);if(put.nput >= nitems){pthread_mutex_unlock(&put.mutex);return NULL;}buff[put.nput]=put.nval;put.nput++;put.nval++;pthread_mutex_unlock(&put.mutex);pthread_mutex_lock(&nready.mutex);if(nready.nready == 0)printf("produce_cond nready==0\n");pthread_cond_signal(&nready.cond);nready.nready++;pthread_mutex_unlock(&nready.mutex);*((int *)arg)+=1;}}void *consume_cond(void *arg){int i;for(i=0;i<3;i++){pthread_mutex_lock(&nready.mutex);printf("consume_cond nready =%d\n",&nready);while(nready.nready == 0)pthread_cond_wait(&nready.cond,&nready.mutex);nready.nready--;pthread_mutex_unlock(&nready.mutex);printf("consume_cond\n");if(buff[i]!=i){printf("buff[%d]=%d\n",i,buff[i]);}}}在produce_cond中给用来统计准备好由消费者处理的条目数的计数器nready.nready加一。在加1之前,如果该计数器的值为0,就调用pthread_cond_signal唤醒可能正在等待其值变为非零的任意消费者线程。该计数器是在生产者和消费者之间共享的。因此只有锁住与之关联的互斥所nready.mutex时才能访问它。consume_cond中消费者只是等待计数器nready.nready变为非零,既然该计数器是在所有生产者和消费者之间共享的。那么只有锁住与之关联的互斥锁时才能测试它的值。如果在锁住该互斥锁期间该计数器的值为0,我们就调用pthread_cond_wait进入睡眠,该函数原子地执行以下两个动作:1给互斥锁nread.mutex解锁2把调用线程投入睡眠,直到另外某个线程就本条件变量调用pthread_cond_signal.pthread_cond_wait在返回前重新给互斥锁nready.mutex上锁。因此当它返回并且我们发现计数器nready.nready不为0时,我们就该把计数器减一,然后给该互斥锁解锁

浙公网安备 33010602011771号