title: 线程同步与互斥———条件变量、信号量
date: 2019-08-29 15:59:47
tags: Linux
categories: Linux
在上一条博客中讲了互斥量的操作,即当两个线程访问同一临界资源时通过加锁解锁的方式让他们同时只允许一个访问者对其进行访问。但是这种方式存在一定不足,就是他们只有两种状态锁定和非锁定。而条件变量可以通过允许线程阻塞和等待另一个线程发送信号来弥补互斥锁的不足,所以互斥锁和条件变量通常一起使用。
一、什么是条件变量
首先、条件变量不是锁,但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。当条件满足的时候,线程通常解锁并等待该条件发生变化,一旦另一个线程修改了环境变量,就会通知相应的环境变量唤醒一个或者多个被这个条件变量阻塞的线程。这些被唤醒的线程将重新上锁,并测试条件是否满足。一般来说条件变量被用于线程间的同步;当条件不满足的时候,允许其中的一个执行流挂起和等待。
二、条件变量的两个动作
●条件不满足:阻塞线程
●条件满足:通知阻塞线程开始工作
三、条件变量系统调用相关API
条件变量的类型:pthread_cond_t cond;
1、初始化一个条件变量
函数原型 | int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); |
---|---|
功能 | 初始化(创建)一个条件变量 |
参数 | cond:条件变量,调用时应传&cond给该函数 attr:条件变量属性,通常传NULL,表示使用默认属性 |
返回值 | 函数成功返回0;任何其他返回值都表示错误。 |
2、销毁一个条件变量
函数原型 | int pthread_cond_destroy(pthread_cond_t *cond); |
---|---|
参数 | 要销毁的条件变量 |
返回值 | 函数成功返回0;任何其他返回值都表示错误 |
3、阻塞等待一个条件变量
函数原型 | int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); |
---|---|
参数 | cond:条件变量 mutex:互斥锁 |
作用 | 阻塞线程等待条件变量cond(参数1)满足; 将已上锁的Mutex解锁 当被唤醒(解除阻塞)时,会对互斥锁加锁 |
4、限时等待一个条件变量
函数原型 | int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime); |
---|---|
参数 | cond:条件变量 mutex:互斥锁 abstime:定时时长、它是绝对时间struct timespec结构体定义如下 struct timespec { time_t tv_sec; //秒 long tv_nsec; //纳秒 } time(NULL)返回的就是绝对时间。该参数正确用法: time_t cur = time(NULL); //获取当前时间。 struct timespec t; //定义timespec 结构体变量t t.tv_sec = cur+1; //定时1秒 pthread_cond_timedwait (&cond, &mutex, &t); //传参 |
作用 | 函数到了一定的时间,即使条件未发生也会解除阻塞。这个时间由参数abstime指定 |
5、唤醒至少一个阻塞在条件变量上的线程
函数原型 | int pthread_cond_signal(pthread_cond_t *cond); |
---|---|
参数 | cond:条件变量 |
作用 | 至少唤醒一个,也有可能唤醒两三四五个等,不确定 |
6、唤醒全部阻塞在条件变量上的线程
函数原型 | int pthread_cond_broadcast(pthread_cond_t *cond); |
---|---|
参数 | cond:条件变量 |
作用 | 唤醒全部的条件变量 |
四、生产者消费者模型
生产者每秒生产5张票,消费者每秒消费2张票。生产者生产票数不超过100张
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
pthread_mutex_t mutex; //锁
pthread_cond_t cond; //条件变量
int tickets=10; //操作的共享数据票
void *produce(void *arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
tickets+=5; //生产者每秒生产5(该值可以随意改)
printf("我是生产者,现在共有%d张票\n",tickets);
if(tickets>=95) //最多生产100
{
pthread_cond_wait(&cond,&mutex); //当生产者消费快时,阻塞生产者
}
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
sleep(1);
}
return (void *)1;
}
void *costumer(void *arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
if(tickets<=2)
{
while(tickets<=2)
{
pthread_cond_wait(&cond,&mutex); //当消费者消费快时,阻塞消费者
}
}
tickets-=2; //消费者每秒消费2(该值可随意改)
printf("我是消费者,现在共有%d张票\n",tickets);
pthread_mutex_unlock(&mutex);
if(tickets<95)
{
pthread_cond_signal(&cond);
}
sleep(1);
}
return (void *)1;
}
int main(void)
{
pthread_t id1,id2;
pthread_mutex_init(&mutex,NULL); //初始化锁
pthread_cond_init(&cond,NULL); //初始化条件变量
pthread_create(&id1,NULL,produce,NULL); //回收线程
pthread_create(&id2,NULL,costumer,NULL);
pthread_join(id1,NULL);
pthread_join(id2,NULL);
pthread_cond_destroy(&cond); //销毁条件变量
pthread_mutex_destroy(&mutex);
return 0;
}
五、信号量
信号量是用来解决线程间同步或互斥的一种机制,也是一个特殊的变量,变量的值代表着当前可以利用的资源、如果等于0,那就意味着现在没有资源可用。信号量是非负整数型的变量。它的本质就是一个计数器,用来为多个进程共享的数据结构提供受控访问。
信号量可以分为二值信号量和计数信号量:
- 二值信号量:信号量的值只有0和1,这和互斥量很类似,若资源被锁住,信号量的值为0,若资源可用,则信号量的值为1;
- 计数信号量:信号量的值在0到一个大于1的限制值之间,该计数表示可用的资源的个数。
六、信号量系统调用相关API
信号量类型:sem_t sem;相关头文件:semaphore.h
1、初始化信号量
函数原型 | int sem_init(sem_t *sem, int pshared, unsigned int value); |
---|---|
参数 | sem:信号量 pshared: 0:线程同步、信号量是当前进程的局部信号量 1:进程同步、信号量可以在多个进程间共享 value:信号量sem的初始值,表示同时最多有几个线程操作共享数据 |
返回值 | 成功:0 失败:-1,设置错误码 |
2、销毁信号量
函数原型 | **int sem_destroy(sem_t sem); |
---|---|
参数 | sem:信号量 |
3、加锁(P操作)
函数原型 | int sem_wait(sem_t * sem); int sem_trywait(sem_t *sem); //尝试加锁,加锁失败不阻塞直接返回 int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); //限时尝试加锁 |
---|---|
参数 | sem:信号量 |
说明 | 调用一次相当于对sem做了减1的操作 如果sem值为0,线程会被阻塞 |
4、解锁(V操作)
函数原型 | sem_post(sem_t *sem) |
---|---|
参数 | sem:信号量 |
说明 | 调用一次相当于对sem做了减1的操作 当一个进程使用完某个信号量时,他应该调用sem_post函数来告诉系统收回资源。 |