线程同步之条件变量与互斥锁

1、同步与互斥的概念

线程的同步是宏观上的概念,在微观上还包含线程的相互排斥和线程的先后执行的约束问题。
说人话就是,表面上我时不时去看你有没有完成,这是同步,但是其实我是跟你相互排斥的,而且还得是你先完成我才能完成,是有先后关系的。

举个例子:
在这里插入图片描述
宏观上,同步相当于测试小组一定要等待研发小组全部完成了项目才能测试,是有依赖关系的,而互斥不管研发小组是完成好了还是完成了一半。

2、条件变量的原理

互斥锁与条件变量的不同:
1)互斥锁只有锁定和非锁定的状态,而条件变量可以满足某个条件然后唤醒锁定阻塞的线程。

互斥锁与条件变量如何配合使用:
2)条件变量内部是一个等待队列,放置阻塞的线程,就是调用wait函数的线程,粗略认为等待队列就是阻塞线程。互斥锁对等待队列上锁。
为什么上锁呢?因为对于所有线程来说,等待队列也是共享内存,这样所有线程都能对等待队列进行操作。

比如定义了一个结构体,结构体里包含了唤醒阻塞函数的条件,阻塞函数里给这个条件赋值为非,但是这个条件所有线程都能进行修改,这样没等着 唤醒线程 给唤醒了,就被其他线程修改了怎么办,所以得对阻塞线程进行上锁。

唤醒:
3)条件变量允许线程等待特定条件发生,当条件不满足时,线程通常先进入阻塞状态,等待条件满足。一旦满足,唤醒一个或多个阻塞线程。(条件需要开发人员给出)

依然举上面给的例子:
在这里插入图片描述
测试小组被阻塞,放置等待队列,然后研发小组把软件项目完成后,通知唤醒等待队列里的测试小组。

3、相关函数
#include <pthread.h>

1)条件变量的创建:

int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_ condattr_t *restrict attr);

2)条件变量的销毁:

int pthread_cond_destroy(pthread_cond_t * cond);

cond条件变量
attr通常NULL采用默认方式
返回值成功返回0;错误返回错误码。

3)条件变量的阻塞函数:

int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);

int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);

cond条件变量
mutex互斥锁
返回值成功返回0;错误返回错误码。
struct timespec
{
	time_t tv_sec;  /*seconds*/
	long tv_nsec;  /*nanoseconds*/
};

问:这里为什么会有参数互斥锁?
互斥锁是对条件变量cond的保护。

线程由于调用wait阻塞后,等着另一个线程来唤醒,唤醒后,释放锁。

4)条件变量的唤醒函数:

int pthread_cond_signal(pthread_cond_t *cond);
功能:通知单个线程

int pthread_cond_broadcast(pthread_cond_t *cond);
功能:通知所有线程

返回值成功返回0;错误返回错误码

4、写代码加强理解
一个线程负责计算结果,另一个线程负责获取结果。

#include<stdio.h>
#include<pthread.h>

typedef struct{
	int 			 res;		//计算结果
	int				 is_wait;   //给出的用于判断是否唤醒阻塞线程的条件
	pthread_mutex_t  mutex;	    //互斥锁
	pthread_cond_t   cond;		//条件变量
}Result;

//计算的线程
void *set_fun(void *arg)
{
	Result *r = (void *)arg;	
	int i=1;
	int sum=0;
	r->is_wait = 0;
	
	for( ; i<=100; i++)
	{
		sum += i;
	}
	r->res = sum;
	
	pthread_mutex_lock(&r->mutex);
	 
	//判断获取结果的线程是否准备好
	while(r->is_wait == 0) //r->is_wait 所有线程都能对其进行操作,所以需要锁
	{
		pthread_mutex_unlock(&r->mutex); //为什么这里上锁?
		usleep(100);
		pthread_mutex_lock(&r->mutex);
	}

问:这里为什么需要释放锁?
如果计算结果的线程先运行,加锁后,获取结果的线程就被阻塞,s_wait就设置不了为1。然后不释放锁的话,就永远等于0。然后set_fun线程就一直做死循环。

所以这里要先解锁。然后set_fun休眠,然后等着获取结果的线程的s_wait为1,然后再上锁,保护这个结果不被修改,为1退出循环。


	pthread_mutex_unlock(&r->mutex);
	pthread_cond_broadcast(&r->cond);	//唤醒 获得结果的线程
	
	return (void *)0;
}

//获得结果的线程
void *get_fun(void *arg)
{
	Result *r = (void *)arg;
	
	pthread_mutex_lock(&r->mutex);//对两个线程共享的判断条件进行保护 A
	
	r->is_wait = 1;		//代表获取结果的线程已经准备好
	pthread_cond_wait(&r->cond, &r->mutex); //准备好后,获取结果的线程进行等待,r->cond等待队列,r->mutex保护条件的互斥锁

	pthread_mutex_unlock(&r->mutex);		//当计算函数被唤醒后,为什么在这之后释放锁,去看第五点会说明,其实这里的unlock和上面的lock A 不是一对,怎么回事呢?
	
	//去获取计算的结果
	int res = r->res;
	printf("0x%lx get sum is %d\n",pthread_self(),res);
	
	return (void *)0;
}

int main()
{
	int err;
	pthread_t set,get;
	Result r;
	r.is_wait = 0; //对结构体初始化
	
	pthread_mutex_init(&r.mutex, NULL);
	pthread_cond_init(&r.cond, NULL);

	if((err = pthread_create(&set, NULL, set_fun, (void *)&r)) != 0)
	{
		perror("create pthread error");
		return -1;
	}
	if((err = pthread_create(&get, NULL, get_fun, (void *)&r)) != 0)
	{
		perror("create pthread error");
		return -2;
	}

	pthread_join(set, NULL);
	pthread_join(get, NULL);

	pthread_mutex_destroy(&r.mutex);
	pthread_cond_destroy(&r.cond);
	
	return 0;
}

5、pthread_cond_wait(cond, mutex)的内部是如何实现
pthread_cond_wait(cond, mutex){

  1. unlock(&mutex); //释放锁,因为函数外部已经上锁了

  2. lock(&mutex);
    //因为线程自己也要插到等待队列里,所以我们要对等待队列进行保护。

  3. 将线程自己插入到条件变量的等待对列中

  4. unlock(&mutex)

  5. 当前等待的线程阻塞(等待阻塞)<====等其他线程通知唤醒(broadcast/signal)

  6. 在唤醒后,继续上锁lock(&mutex);(锁定阻塞)

  7. 从等待队列中删除线程自己
    }

内部已经上锁了,所以外部要再wait之后解锁。
这就是上面的,unlock为什么是在,wait后。这个unlock是跟内部的lock是一对的。
在这里插入图片描述
6、线程的状态转换

在这里插入图片描述
为什么要po这张图呢,因为我发现线程的阻塞状态 是由等待阻塞wait和 锁定阻塞lock的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值