pthread_cond_wait和pthread_cond_signal

参考原文:https://www.zhihu.com/question/24116967/answer/576056958

bool ready = false;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

Thread A1:

pthread_mutex_lock(&mutex);
while (false == ready) {
    pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);

Thread B1:

ready = true;
pthread_cond_signal(&cond);

错误

如下的执行序列 Thread A 就会丢失 Thread B 的条件变量的唤醒而永久 wait,如下表格,在 ready = false 的时候,Thread A 进入 while 循环,但是还没有执行 wait 的时候,thread B 执行了 ready = true 和 signal 唤醒,那么就出现条件变量唤醒 signal 先于 wait,那么相当于 Thread A 还没有被加入唤醒队列,这个时候,你已经 signal 唤醒了,那么这次唤醒自然就丢失了,执行序列的第 5 行,也就是 Thread A 的第 3 行pthread_cond_wait(&cond, &mutex) 就会一直等在那里了。

原因

核心出错的原因就是一个点,当判断 ready 为 false 进入 while 循环(Thread A 第 2 行),调用 pthread_cond_wait (Thread A 第 3 行)之前,Thread B 修改了 ready 为 true,并且 signal 了条件变量,导致 signal 先于 wait,出现丢失。
这里根本的原因就是没有保证 ready == false 判断成立和 Thread A 调用 wait 进入唤醒队列的原子性,也就是一旦判定 ready 为 false,那么必须进入等待队列,且在这期间不允许有人修改 ready 为 true,并执行 signal 操作。所以正确使用条件变量有两个约束:
(1) 保证 ready == false 判断成立和 Thread A 调用 wait 进入唤醒队列的原子性
(2) 设置 ready = true 在 signal 唤醒之前,即 Thread B 的 1、2 两行顺序绝不能反过来
上面错误的写法仅仅满足了约束 (2),但是却确忽略了(1)。上面错误的写法虽然给 Thread A 加锁了,但是这个锁加得有点糊里糊涂,没弄明白为什么要给 pthread_cond_wait(&cond, &mutex) 传递一个互斥量,以为是 pthread_cond_t 内部存在竞态条件,
其实并不是,pthread_cond_wait(&cond, &mutex)调用之前会加锁,然后在内部将 thread 加入唤醒队列,然后才释放锁,其实就是为了保证约束(1),显然仅仅 Thread A 加锁,而 Thread B 设置 ready = true 没有加锁,并不能保证约束(1)的原子性。所以为了满足约束(1),需要给 Thread B 设置 ready = true 也加锁

正确写法

Thread A1:

pthread_mutex_lock(&mutex);
while (false == ready) {
     pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);

Thread B1:

pthread_mutex_lock(&mutex);
ready = true;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);

这样就保证了原子性约束(1),那么无论线程如何运行,都只会有只有两种情况,
情况 1:如果 Thread A 先拿到 mutex,那么此时 ready 为 false,Thread A 调用 pthread_cond_wait 进入等待队列,接着释放 mutex,然后 Thread B 才能修改 ready,并 signal;
情况 2:如果 Thread A 没有拿到 mutex,Thread B 拿到 mutex,然后修改 ready 为 true,然后释放锁,这样 Thread A 在拿到 mutex,就不会再进 while 循环调 wait 了。

总结

pthread_cond_wait 并不只是自动释放锁、等待、被wakeup后自动获取锁的函数,因为释放锁+等待这两步需要是“原子”的。注意“原子”加引号,因为这里原子的语义并不是真正的原子操作。事实上,这两步根本没法实现成原子操作。设想如果pthread_cond_wait这样实现:

LOCK_ACQUIRE(&mylock);
pthread_mutex_unlock(mutex); // 释放用户传进来的mutex
wait(); // 把当前线程放到该CV的等待队列上
LOCK_RELEASE(&mylock);

那么该线程拿着mylock等待了,谁来释放呢?因此,这里“原子”的意思是,如果线程A调用pthread_cond_wait(cond, mutex)刚释放完mutex,线程B就获得了mutex并且signal或者broadcast了cond,那么需要保证最终的行为跟线程B在线程A wait以后才获得mutex的行为一致,即没有丢失signal,线程A不会一直block。当然,使用条件变量的时候直接认为pthread_cond_wait是原子操作就行了

问题

那么解释如下代码是否可行?

pthread_mutex_lock(&mutex);
while (false == ready) {
     pthread_cond_wait(&cond, &mutex);
	if (条件成立) {
		continue;
	}
}
pthread_mutex_unlock(&mutex);

是可行的

实践

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>

pthread_mutex_t my_lock;
pthread_cond_t  my_cond;
volatile int flag = 1;

int init_pthread_lock(pthread_mutex_t *mutex_lock) {
	int ret;

	ret = pthread_mutex_init(mutex_lock, NULL);
	if(ret != 0 ) {
		printf("fail to pthread_mutex_init\n");
		exit(EXIT_FAILURE);
	}
	return 0;
}
 
int init_pthread_cond(pthread_cond_t *cond) {
	int ret ;

	ret = pthread_cond_init(cond,NULL);
	if(ret != 0) {
		printf("fail to pthread_cond_init\n");
		exit(EXIT_FAILURE);
	}

	return 0;
}
 
void *recv_thread(void *arg)
{
	int cnt = 1;
	printf("recv thread, enter ...\n");
	pthread_mutex_lock(&my_lock);
	while(flag) {
		pthread_cond_wait(&my_cond, &my_lock);
		printf("recv thread, wakeup %d time ...\n", cnt++);
		flag = 0;
	}
	pthread_mutex_unlock(&my_lock);
    printf("recv thread, exit ...\n");
	pthread_exit(NULL);
}
 
void *send_thread(void *arg)
{
	printf("send thread, enter ...\n");
	sleep(5);

	pthread_mutex_lock(&my_lock);
	printf("send thread, before send signal ...\n");
	pthread_cond_signal(&my_cond);
	printf("send thread, after send signal ...\n");
	pthread_mutex_unlock(&my_lock);

	printf("send thread, exit ...\n");
	pthread_exit(NULL);
}
 
int main(int argc, const char *argv[]) {
	int ret;
	pthread_t  tid[2];

	printf("main thread, enter ...\n");
	init_pthread_lock (&my_lock);
	init_pthread_cond(&my_cond);

	ret = pthread_create(&tid[0], NULL, recv_thread, NULL);
	if(ret != 0) {
		printf("main thread fail to pthread_create recv_thread, %s\n",strerror(errno));
		exit(EXIT_FAILURE);
	}
	printf("main thread, pthread_create recv_thread successful\n");

	ret = pthread_create(&tid[1], NULL, send_thread, NULL);
	if(ret != 0) {
		printf("main thread, fail to pthread_create send_thread, %s\n",strerror(errno));
		exit(EXIT_FAILURE);
	}
	printf("main thread, pthread_create send_thread successful\n");

	pthread_join(tid[0],NULL);
	pthread_join(tid[1],NULL);

	printf("main thread, exit ...\n");

	exit(EXIT_SUCCESS);
}
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>

pthread_mutex_t my_lock;
pthread_cond_t  my_cond;
volatile int flag = 1;

int init_pthread_lock(pthread_mutex_t *mutex_lock) {
	int ret;

	ret = pthread_mutex_init(mutex_lock, NULL);
	if(ret != 0 ) {
		printf("fail to pthread_mutex_init\n");
		exit(EXIT_FAILURE);
	}
	return 0;
}
 
int init_pthread_cond(pthread_cond_t *cond) {
	int ret ;

	ret = pthread_cond_init(cond,NULL);
	if(ret != 0) {
		printf("fail to pthread_cond_init\n");
		exit(EXIT_FAILURE);
	}

	return 0;
}
 
void *recv_thread(void *arg)
{
    int cnt = 1;
    printf("recv thread, enter ...\n");
    pthread_mutex_lock(&my_lock);
    while(flag) {
        pthread_cond_wait(&my_cond, &my_lock);
        printf("recv thread, wakeup %d time ...\n", cnt++);
    }
    pthread_mutex_unlock(&my_lock);
    printf("recv thread, exit ...\n");
    pthread_exit(NULL);
}

 
void *send_thread(void *arg)
{
	printf("send thread, enter ...\n");
	sleep(5);

	pthread_mutex_lock(&my_lock);
	printf("send thread, before send signal ...\n");
	pthread_cond_signal(&my_cond);
	printf("send thread, after send signal ...\n");
	pthread_mutex_unlock(&my_lock);

	printf("send thread, exit ...\n");
	pthread_exit(NULL);
}
 
int main(int argc, const char *argv[]) {
	int ret;
	pthread_t  tid[2];

	printf("main thread, enter ...\n");
	init_pthread_lock (&my_lock);
	init_pthread_cond(&my_cond);

	ret = pthread_create(&tid[0], NULL, recv_thread, NULL);
	if(ret != 0) {
		printf("main thread fail to pthread_create recv_thread, %s\n",strerror(errno));
		exit(EXIT_FAILURE);
	}
	printf("main thread, pthread_create recv_thread successful\n");

	ret = pthread_create(&tid[1], NULL, send_thread, NULL);
	if(ret != 0) {
		printf("main thread, fail to pthread_create send_thread, %s\n",strerror(errno));
		exit(EXIT_FAILURE);
	}
	printf("main thread, pthread_create send_thread successful\n");

    pthread_mutex_lock(&my_lock);
    printf("main thread, before send signal ...\n");
    flag = 0;
    pthread_cond_signal(&my_cond);
    printf("main thread, after send signal ...\n");
    pthread_mutex_unlock(&my_lock);

    pthread_join(tid[0],NULL);
    pthread_join(tid[1],NULL);

	exit(EXIT_SUCCESS);
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值