pthread 线程之条件变量

简介:

相关的函数如下:
1 int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
2 int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
3 int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
4 int pthread_cond_destroy(pthread_cond_t *cond);
5 int pthread_cond_signal(pthread_cond_t *cond);
6 int pthread_cond_broadcast(pthread_cond_t *cond); //解除所有线程的阻塞
简要说明:
(1)初始化.init()或者pthread_cond_t cond=PTHREAD_COND_INITIALIER;属性置为NULL
(2)等待条件成立.pthread_wait,pthread_timewait.wait()释放锁,并阻塞等待条件变量为真
timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait)
(3)激活条件变量:pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程)
(4)清除条件变量:destroy;无线程等待,否则返回EBUSY

详细说明
1. 初始化:
条件变量采用的数据类型是pthread_cond_t, 在使用之前必须要进行初始化, 这包括两种方式:
静态方式使用PTHREAD_COND_INITIALIZER常量,如下:
pthread_cond_t cond=PTHREAD_COND_INITIALIZER
动态: pthread_cond_init函数, 是释放动态条件变量的内存空间之前, 要用pthread_cond_destroy对其进行清理.
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);

成功则返回0, 出错则返回错误编号.
当pthread_cond_init的attr参数为NULL时, 会创建一个默认属性的条件变量; 非默认情况以后讨论.
尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,因此cond_attr值通常为NULL,且被忽略。
阻塞在条件变量上pthread_cond_wait
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cv,
pthread_mutex_t *mutex);
返回值:函数成功返回0;任何其他返回值都表示错误
函数将解锁mutex参数指向的互斥锁,并使当前线程阻塞在cv参数指向的条件变量上。
被阻塞的线程可以被pthread_cond_signal函数,pthread_cond_broadcast函数唤醒,也可能在被信号中断后被唤醒。
pthread_cond_wait函数的返回并不意味着条件的值一定发生了变化,必须重新检查条件的值。
pthread_cond_wait函数返回时,相应的互斥锁将被当前线程锁定,即使是函数出错返回。
一般一个条件表达式都是在一个互斥锁的保护下被检查。当条件表达式未被满足时,线程将仍然阻塞在这个条件变量上。当另一个线程改变了条件的值并向条件变量发出信号时,等待在这个条件变量上的一个线程或所有线程被唤醒,接着都试图再次占有相应的互斥锁。
阻塞在条件变量上的线程被唤醒以后,直到pthread_cond_wait()函数返回之前条件的值都有可能发生变化。所以函数返回以后,在锁定相应的互斥锁之前,必须重新测试条件值。最好的测试方法是循环调用pthread_cond_wait函数,并把满足条件的表达式置为循环的终止条件。如:
pthread_mutex_lock();
while (condition_is_false)
pthread_cond_wait();
pthread_mutex_unlock();
阻塞在同一个条件变量上的不同线程被释放的次序是不一定的。
注意:pthread_cond_wait()函数是退出点,如果在调用这个函数时,已有一个挂起的退出请求,且线程允许退出,这个线程将被终止并开始执行善后处理函数,而这时和条件变量相关的互斥锁仍将处在锁定状态。

3.解除在条件变量上的阻塞pthread_cond_signal
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cv);
返回值:函数成功返回0;任何其他返回值都表示错误
函数被用来释放被阻塞在指定条件变量上的一个线程。
必须在互斥锁的保护下使用相应的条件变量。否则对条件变量的解锁有可能发生在锁定条件变量之前,从而造成死锁。
唤醒阻塞在条件变量上的所有线程的顺序由调度策略决定,如果线程的调度策略是SCHED_OTHER类型的,系统将根据线程的优先级唤醒线程。
如果没有线程被阻塞在条件变量上,那么调用pthread_cond_signal()将没有作用。

4.阻塞直到指定时间pthread_cond_timedwait
#include <pthread.h>
#include <time.h>
int pthread_cond_timedwait(pthread_cond_t *cv,
pthread_mutex_t *mp, const structtimespec * abstime);
返回值:函数成功返回0;任何其他返回值都表示错误
函数到了一定的时间,即使条件未发生也会解除阻塞。这个时间由参数abstime指定。函数返回时,相应的互斥锁往往是锁定的,即使是函数出错返回。
注意:pthread_cond_timedwait函数也是退出点。
超时时间参数是指一天中的某个时刻。使用举例:
pthread_timestruc_t to;
to.tv_sec = time(NULL) + TIMEOUT;
to.tv_nsec = 0;
超时返回的错误码是ETIMEDOUT。

5.释放阻塞的所有线程pthread_cond_broadcast
#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cv);
返回值:函数成功返回0;任何其他返回值都表示错误
函数唤醒所有被pthread_cond_wait函数阻塞在某个条件变量上的线程,参数cv被用来指定这个条件变量。当没有线程阻塞在这个条件变量上时,pthread_cond_broadcast函数无效。
由于pthread_cond_broadcast函数唤醒所有阻塞在某个条件变量上的线程,这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用pthread_cond_broadcast函数。

6.释放条件变量pthread_cond_destroy
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cv);
返回值:函数成功返回0;任何其他返回值都表示错误
释放条件变量。
注意:条件变量占用的空间并未被释放。

7.唤醒丢失问题
在线程未获得相应的互斥锁时调用pthread_cond_signal或pthread_cond_broadcast函数可能会引起唤醒丢失问题。
唤醒丢失往往会在下面的情况下发生:
一个线程调用pthread_cond_signal或pthread_cond_broadcast函数;
另一个线程正处在测试条件变量和调用pthread_cond_wait函数之间;
没有线程正在处在阻塞等待的状态下。

条件锁pthread_cond_t
说明,
等待线程
1。使用pthread_cond_wait前要先加锁
2。pthread_cond_wait内部会解锁,然后等待条件变量被其它线程激活
3。pthread_cond_wait被激活后会再自动加锁

激活线程:
1。加锁(和等待线程用同一个锁)
2。pthread_cond_signal发送信号
3。解锁
激活线程的上面三个操作在运行时间上都在等待线程的pthread_cond_wait函数内部。


其他
pthread_cond_wait()和pthread_cond_timedwait()都被实现为取消点,因此,在该处等待的线程将立即重新运行,在重新锁定mutex后离开pthread_cond_wait(),然后执行取消动作。也就是说如果pthread_cond_wait()被取消,mutex是保持锁定状态的,因而需要定义退出回调函数来为其解锁。
EXAMPLE
Consider two shared variables x and y, protected by the mutex mut, and
a condition variable cond that is to be signaled whenever x becomes
greater than y.
int x,y;
pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
// Waiting until x is greater than y is performed as follows:
pthread_mutex_lock(&mut);
while (x <= y) {
pthread_cond_wait(&cond, &mut);
}
/* operate on x and y */
pthread_mutex_unlock(&mut);
// Modifications on x and y that may cause x to become greater than y
// should signal the condition if needed:
pthread_mutex_lock(&mut);
/* modify x and y */
if (x > y) pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mut);
/* If it can be proved that at most one waiting thread needs to be waken
up (for instance, if there are only two threads communicating through x
and y), pthread_cond_signal can be used as a slightly more efficient
alternative to pthread_cond_broadcast. In doubt, use
pthread_cond_broadcast.
To wait for x to becomes greater than y with a timeout of 5 seconds,
do:
*/
struct timeval now;
struct timespec timeout;
int retcode;
pthread_mutex_lock(&mut);
gettimeofday(&now);
timeout.tv_sec = now.tv_sec + 5;
timeout.tv_nsec = now.tv_usec * 1000;
retcode = 0;
while (x <= y && retcode != ETIMEDOUT) {
retcode = pthread_cond_timedwait(&cond, &mut, &timeout);
}
if (retcode == ETIMEDOUT) {
/* timeout occurred */
} else {
/* operate on x and y */
}
pthread_mutex_unlock(&mut);
要想知道更详细,请看 man pthread_cond_wait

————————————————————————————————————————————————————
经典的生产者/消费者的例证。
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define MAX 5

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; /*初始化互斥锁*/
pthread_cond_t=PTHREAD_CODE_INITIALIZER; /*初始化条件变量*/

typedef struct{
char buffer[MAX];
int how_many;
}BUFFER;

BUFFER share={“”,0};
char ch=’A’;/*初始化ch*/

void *read_some(void *);
void *write_some(void *);

int main(void)
{
pthread_t t_read;
pthread_t t_write;

pthread_create(&t_read,NULL,read_some,(void *)NULL); /*创建进程t_a*/
pthread_create(&t_write,NULL,write_some,(void *)NULL); /*创建进程t_b*/
pthread_join(t_write,(void **)NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
exit(0);
}

void *read_some(void *junk)
{
int n=0;

printf(“R %2d: starting/n”,pthread_self());

while(ch!=’Z’)
{
pthread_mutex_lock(&lock_it);/*锁住互斥量*/
if(share.how_many!=MAX)
{

share.buffer[share.how_many++]=ch++;/*把字母读入缓存*/
printf(“R %2d:Got char[%c]/n”,pthread_self(),ch-1);/*打印读入字母*/
if(share.how_many==MAX)
{
printf(“R %2d:signaling full/n”,pthread_self());
pthread_cond_signal(&write_it);/*如果缓存中的字母到达了最大值就发送信号*/
}
pthread_mutex_unlock(&lock_it);/*解锁互斥量*/
}
sleep(1);
printf(“R %2d:Exiting/n”,pthread_self());
return NULL;
}

void *write_some(void *junk)
{
int i;
int n=0;
printf(“w %2d: starting/n”,pthread_self());

while(ch!=’Z’)
{
pthread_mutex_lock(&lock_it);/*锁住互斥量*/
printf(“/nW %2d:Waiting/n”,pthread_self());
while(share.how_many!=MAX)/*如果缓存区字母不等于最大值就等待*/
pthread_cond_wait(&write_it,&lock_it);
printf(“W %2d:writing buffer/n”,pthread_self());
for(i=0;share.buffer[i]&&share.how_many;++i,share.how_many--)
putchar(share.buffer[i]); /*循环输出缓存区字母*/
pthread_mutex_unlock(&lock_it);/*解锁互斥量*/
}
printf(“W %2d:exiting/n”,pthread_self());
return NULL;
}

_______________________________________________________________________________________

输入q后,需要等线程从sleep中醒来(由挂起状态变为运行状态),即最坏情况要等10s,线程才会被join。采用sleep的缺点:不能及时唤醒线程。
采用pthread_cond_timedwait函数实现的如下:

#include <stdio.h>

#include <sys/time.h>;
#include <unistd.h>;
#include <pthread.h>;
#include <errno.h>;

pthread_t thread;
pthread_cond_t cond;
pthread_mutex_t mutex;
bool flag = true;

void * thr_fn(void * arg) {
struct timeval now;
struct timespec outtime;
pthread_mutex_lock(&mutex);
while (flag) {
printf(".\n");
gettimeofday(&now, NULL);
outtime.tv_sec = now.tv_sec + 5;
outtime.tv_nsec = now.tv_usec * 1000;
pthread_cond_timedwait(&cond, &mutex, &outtime);
}
pthread_mutex_unlock(&mutex);
printf("thread exit\n");
}

int main() {
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
if (0 != pthread_create(&thread, NULL, thr_fn, NULL)) {
printf("error when create pthread,%d\n", errno);
return 1;
}
char c ;
while ((c = getchar()) != 'q');
printf("Now terminate the thread!\n");
flag = false;
pthread_mutex_lock(&mutex);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
printf("Wait for thread to exit\n");
pthread_join(thread, NULL);
printf("Bye\n");
return 0;
}

等待的时间通过abstime参数(绝对系统时间,过了该时刻就超时)指定,超时则返回ETIMEDOUT错误码。开始等待后,等待时间不受系统时钟改变的影响。

尽管时间通过秒和纳秒指定,系统时间是毫秒粒度的。需要根据调度和优先级原因,设置的时间长度应该比预想的时间要多或者少点。可以通过使用系统时钟接口gettimeofday()获得timeval结构体。

注: 为了可靠的使用条件变量和确保不忘记对条件变量的唤醒操作,应该采用一个bool变量和mutex变量同条件变量配合使用。如本文demo.

____________________________________________________________________________

条件变量与互斥锁、信号量的区别
1.互斥锁必须总是由给它上锁的线程解锁,信号量的挂出即不必由执行过它的等待操作的同一进程执行。一个线程可以等待某个给定信号灯,而另一个线程可以挂出该信号灯。
2.互斥锁要么锁住,要么被解开(二值状态,类型二值信号量)。
3.由于信号量有一个与之关联的状态(它的计数值),信号量挂出操作总是被记住。然而当向一个条件变量发送信号时,如果没有线程等待在该条件变量上,那么该信号将丢失。
4.互斥锁是为了上锁而设计的,条件变量是为了等待而设计的,信号灯即可用于上锁,也可用于等待,因而可能导致更多的开销和更高的复杂性。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值