目录
Linux线程同步
条件变量
什么是条件变量:
条件变量是利用线程间共享的全局变量进行同步的一种机制。主要包括两种动作:
- 线程等待条件变量的成立而挂起
- 另一个线程使条件变量成立而通知其它线程。
这个怎么理解呢?举个例子:当你去超市买手机,问售货员有没有苹果13,售货员需要在苹果13到的时候才知道有没有苹果13,那么你需要等待售货员的通知,这个时候你是被挂起的,在wait售货员;同时当苹果13到店里的时候,售货员知道了有苹果13,条件成立了,售货员通知你苹果13到了,这个时候就是发信号signal的时候。
一般条件变量需要与互斥锁同时使用,那么互斥锁的用处在下面的例子有讲到。
条件变量的作用:
使用条件变量可以以原子方式阻塞线程,直到某个特定条件为真为止,条件变量一般是与互斥锁一起使用的,对条件变量的测试一般是在互斥锁的保护下进行的。
条件变量的用法:
初始化:
条件变量的初始化和创建进程初始化一摸一样,只是换了个名字,条件变量初始化也包括动态初始化和静态初始化
动态初始化:
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr); 参数:
cond:要初始化的条件变量
attr:NULL--设置条件变量属性,一般设置为NULL即可
返回值:
成功返回0,失败返回错误码
静态初始化:
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
销毁:
int pthread_cond_destroy(pthread_cond_t *cond)
等待条件满足:
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量,后面详细解释
唤醒等待:
int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒所有进程
int pthread_cond_signal(pthread_cond_t *cond); //唤醒一个进程
条件变量简单案例:
思路:
创建一个master线程和3个worker线程,master线程用来发送信号signal,worker的每个线程接收到master线程信号开始做自己的动作。
主函数中的主线程工作:初始化(线程,条件变量),创建线程,等待线程,销毁。
两个新线程的动作:master状态就绪发送信号,唤醒等待队列中的线程,worker线程阻塞等待接收信号,再做动作。我们使用了pthread_cond_signal唤醒队列中的线程,那么唤醒的是哪一个呢?唤醒的是队列中第一个线程。
#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
using namespace std;
pthread_mutex_t mtx;//互斥锁
pthread_cond_t cond;//条件变量
void* ctrl(void* args)
{
std::string name=(char*)args;
while(true)
{
std::cout<<"master say:begin work"<<std::endl;
//pthread_cond_broadcast(&cond);
pthread_cond_signal(&cond);
sleep(1);
}
}
void* work(void* args)
{
int number=*(int*)args;
delete (int*)args;
while(true)
{
pthread_cond_wait(&cond,&mtx);
std::cout<<"worker:"<<number<<"is working..."<<std::endl;
}
}
int main()
{
#define NUM 3
pthread_mutex_init(&mtx,nullptr);
pthread_cond_init(&cond,nullptr);
pthread_t master;//创建线程
pthread_t worker[NUM];
pthread_create(&master,nullptr,ctrl,(void*)"boss");
for(int i=0;i<NUM;i++)
{
int* number=new int(i);
pthread_create(worker+i,nullptr,work,(void*)number);
}
for(int i=0;i<NUM;i++)
{
pthread_join(worker[i],nullptr);
}
pthread_join(master,nullptr);
pthread_mutex_destroy(&mtx);
pthread_cond_destroy(&cond);
}
makefile文件:
myctrl:myctrl.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f myctrl
运行结果:
这个例子也就说明了条件变量内部一定有一个等待队列。我们可以把条件变量看作一个结构体,结构体里面有一个队列用来存放等待的线程,还有一个状态变量,当进程被唤醒,这个状态变量status由0变1,然后再将等待队列的线程由D状态变成R状态,完成唤醒操作。
struct cond
{
int status;
task_struct* q;
}
为什么pthread_cond_wait需要互斥量?
- 条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须 要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件 变量上的线程。
- 条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有 互斥锁就无法安全的获取和修改共享数据
- 既然上锁了,那么当一个线程条件不满足,使用条件变量等待,会将锁释放归还,然后继续执行下面的动作代码,这样不会因为条件不满足一直占用锁,做不了其它动作而浪费锁资源。
生产者消费者模型
为何要使用生产者消费者模型
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而 通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者 要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队 列就是用来给生产者和消费者解耦的。
生产者消费者模型优点
- 解耦
- 支持并发
- 支持忙闲不均
基于BlockingQueue的生产者消费者模型
在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别 在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元 素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程 操作时会被阻塞)
C++ queue模拟阻塞队列的生产消费模型
我们用队列模拟实现一个生产者消费者模型,其中需要将Push函数和Pop函数自定义实现。那么Push函数的核心就是,向阻塞队列中push数据,当队列满的时候,就不能再push数据,需要return。
因为push操作和pop操作都要抢占队列,所以队列不是线程安全的。我们知道STL不是安全的,所以STL安全需要我们成需要自己去维护的。所以生产者和消费者需要通过枷锁来保证队列的安全。所以成员变量中bq_和cap_都是临界资源。我们要引入pthread.h中的锁。
当生产者不断在生产,加入生产者生产力特别强,不断地向队列里塞任务,使得队列被塞满了。当队列被塞满了,生产者还是在不断的申请锁,导致消费者得不到资源,进而导致消费者产生的饥饿问题。所以我们最理想的情况是:
- 当生产满了的时候,就应该不要生产了(不要竞争锁了),而应该让消费者来消费
- 当消费空了,就不应该消费(不要竞争锁了),而应该让生产者来进行生产
这次我们就不想让某个线程来无脑抢锁,无脑的向队列里塞,此