线程同步
一个资源,我们不知道他是不是准备好了资源,就需要频繁申请。这时候对于这个执行流,多次检测,极大的浪费了资源。而且对于资源来说一个线程多次申请资源的时候,他一直申请到资源,别的线程申请不到资源,就造成了线程饥饿。所以就有了线程同步的概念。为了解决访问临界资源合理性问题。应该让一个资源申请之后,不能再次被同一个执行流申请,新来的也得排队。让资源访问具有顺序性。
那么什么叫同步?
按照一定的顺序,进行资源的访问,就叫做线程的同步。同时线程对于资源的申请不要频繁的询问,当资源准备好之后会发消息通知的。
1.条件变量
在申请临界资源之前,先要做临界资源的检测,有没有。检测的本质其实对临界资源的访问,那么对于临界资源的检测也是需要加锁和解锁的。常规方式的检测条件就绪,注定了要频繁申请释放锁。[申请到锁不意味着资源是就绪的]那么有没有办法让线程检测到资源不就绪的时候不要频繁的检测,等待。当条件就绪的时候,通知对应的线程进行资源的访问。所以要引入条件变量。
- 当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
- 例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情 况就需要用到条件变量。
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
参数:
cond:要初始化的条件变量
attr:NULL
这是动态分配的条件变量初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
这个是静态分配条件变量初始化。
对于没有就绪的资源等待
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);//按照书顺序唤醒一个线程
wait是在加锁和解锁之间的wait 检测临界资源就绪一定在临界资源内等待。
示例demo
#include<iostream>
#include<pthread.h>
#include<string>
#include<cstdio>
#include<unistd.h>
using namespace std;
#define Thread_NUM 4
typedef void(*func_t)( string &name,pthread_mutex_t* mtx,pthread_cond_t*cond);
volatile bool quit =false;
class Thread_date
{
public:
Thread_date( string &name,func_t func,pthread_mutex_t* mtx,pthread_cond_t*cond):name_(name),func_(func),ptmux(mtx),ptcond(cond)
{
}
public:
string name_;
func_t func_;
pthread_mutex_t *ptmux;
pthread_cond_t *ptcond;
};
void func1(string &name,pthread_mutex_t* mtx,pthread_cond_t*cond)
{
while(!quit)
{
//加锁
pthread_mutex_lock(mtx);
pthread_cond_wait(cond,mtx);//默认该线程在支持的时候wait代码被执行,当前线程立马被阻塞。
//将线程放到队列中等待。
cout<<name<<" runing......a"<<endl;
pthread_mutex_unlock(mtx);
}
}
void func2(string &name,pthread_mutex_t* mtx,pthread_cond_t*cond)
{
while(!quit)
{
//加锁
pthread_mutex_lock(mtx);
pthread_cond_wait(cond,mtx);//默认该线程在支持的时候wait代码被执行,当前线程立马被阻塞。
cout<<name<<" runing......b"<<endl;
pthread_mutex_unlock(mtx);
}
}void func3(string &name,pthread_mutex_t* mtx,pthread_cond_t*cond)
{
while(!quit)
{
//加锁
pthread_mutex_lock(mtx);
pthread_cond_wait(cond,mtx);//默认该线程在支持的时候wait代码被执行,当前线程立马被阻塞。
cout<<name<<" runing......c"<<endl;
pthread_mutex_unlock(mtx);
}
}void func4(string &name,pthread_mutex_t* mtx,pthread_cond_t*cond)
{
while(!quit)
{
//wait不少这样子用的 wait是在加锁和解锁之间的wait 检测临界资源就绪一定在临界资源内等待
//加锁
pthread_mutex_lock(mtx);
pthread_cond_wait(cond,mtx);//默认该线程在支持的时候wait代码被执行,当前线程立马被阻塞。在临界资源内等待
cout<<name<<" runing......d"<<endl;
pthread_mutex_unlock(mtx);
}
}
void *Entry(void*args)
{
Thread_date*fd =(Thread_date*)args;
fd->func_(fd->name_,fd->ptmux,fd->ptcond);
delete fd;
return nullptr;
}
int main()
{
pthread_t tid[Thread_NUM];
pthread_mutex_t mux;//创建锁
pthread_cond_t cond;//创建条件变量
pthread_mutex_init(&mux,nullptr);
pthread_cond_init(&cond,nullptr);//初始化条件变量。
func_t funcs[Thread_NUM] = {func1, func2, func3, func4};
for(int i=0;i<Thread_NUM;i++)
{
string name="thread ---";
name+=to_string(i+1);
Thread_date*td =new Thread_date(name,funcs[i],&mux,&cond);//条件传递 锁和条件变量
pthread_create(tid+i,nullptr,Entry,(void*)td);
}
sleep(2);
//尝试唤醒新线程
int cun =10;
while(1)
{
cout<<"唤醒线程中---------------cnt"<<cun--<<".............";
pthread_cond_signal(&cond);//唤醒条件变量
sleep(1);
}
quit =true;
for(int i=0;i<Thread_NUM;i++)
{
pthread_join(tid[i],nullptr);
cout<<"线程----"<<i<<"退出成功"<<endl;
}
pthread_mutex_destroy(&mux);//释放锁
pthread_cond_destroy(&cond);//销毁条件变量
return 0;
}
2.生产者消费者模型
为何要使用生产者消费者模型
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理, 直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了 生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
生产者和生产者之间是互斥关系,消费者和消费者之间是竞争,互斥关系。生产者和消费者是互斥/同步关系(生产完再消费,消费完再生产)。遵循321原则。给线程角色化,承担不同的角色,而中间的超市本质上数据结构形成的缓冲区,商品是数据。某种意义进程间通信就是生产者,消费者模型。
那么多生产和多消费意义在哪里?可以让生产和消费有多个执行流进行生产和消费。