面试要点来了~
文章目录
- 前言
- 一、死锁的一系列问题
- 二、生产者消费者模型原理
- 总结
前言
上一篇的互斥量原理中我们讲解了锁的原理,我们知道每次线程申请锁的时候一旦申请成功这个线程自己就把锁带在自己身上了,这就保证了锁的原子性(因为只有一个锁),而当我们已经申请成功锁了然后再去申请锁会发生什么事呢?下面我们在死锁中回答这个问题。
一、死锁
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void *threadRoutine(void* args)
{
cout<<"我是一个新线程"<<endl;
pthread_mutex_lock(&mutex);
cout<<"我拿到了锁"<<endl;
pthread_mutex_lock(&mutex);//由于再次申请锁的问题会停下来(死锁)
cout<<"我又活了过来"<<endl;
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid,nullptr,threadRoutine,nullptr);
sleep(3);
cout<<"主线程正在运行"<<endl;
return 0;
}
首先我们先不让主线程释放锁,看一下死锁的状态:
我们可以看到本来应该打印“我又活了过来”,结果直接退出了,下面我们让主线程给新线程解锁:
通过运行结果我们可以看到刚刚死锁的进程活过来了,那么就意味着可以一个线程申请锁,另一个线程释放锁。所以要破坏第四个条件直接控制一个线程统一释放锁。
下面我们总结一下:
const int num = 5;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void *active(void* args)
{
string name = static_cast<const char*>(args);
while (true)
{
pthread_mutex_lock(&mutex); //先加锁
//然后放进条件变量去等
pthread_cond_wait(&cond,&mutex); //调用的时候会自动释放锁
cout<<name<<" 活动 "<<endl;
pthread_mutex_unlock(&mutex);
}
}
int main()
{
pthread_t tids[num];
for (int i = 0;i<num;i++)
{
char* name = new char[32];
snprintf(name,32,"thread -> %d ",i+1);
pthread_create(tids+i,nullptr,active,name);
}
sleep(3);
while (true)
{
cout<<"主线程唤醒其他线程......"<<endl;
//在环境变量中按顺序唤醒一个线程
pthread_cond_signal(&cond);
sleep(1);
}
for (int i = 0;i<num;i++)
{
pthread_join(tids[i],nullptr);
}
return 0;
}
上面代码的作用是:首先创建5个线程,这5个线程都要进入active函数,在函数中首先加锁,然后让这个线程去环境变量中去等待,在等待的过程中会自己释放锁,然后3秒后主线程开始唤醒这些环境变量里的线程了,pthread_cond_signal这个接口的作用是每次唤醒一个线程,每次唤醒都是按顺序唤醒的,唤醒后这个线程就会打印“活动”,下面我们运行起来看看:
可以看到这里唤醒的顺序都是31245,下面我们可以用一下pthread_cond_broadcast这个接口来一次唤醒所有的接口:
通过运行结果我们可以看到同样是按顺序进行唤醒的,所以条件变量的作用是:允许多线程在cond中队列式等待(队列式等待就是一种顺序)。
下面我们总结一下:
二、生产者消费者模型
下面我们先画张图理解一下生产者消费者模型:
首先在模型中超市并不是属于生产者,超市只是一个交易场所,供货商会把生产的商品放到超市中,然后消费者直接去超市买。为什么消费者不直接去供货商那里购买呢?因为消费者不知道供货商什么时候生产好了商品,而且消费者的需求非常零散,供货商的生产水平很强,导致这两者之前有着天然的屏障。那么这个模型有什么特点呢?
第一点效率高,第二点忙闲不均。第二点是什么意思呢?意思就是说我们可以把供货商生产的一大批商品全部放在超市中让消费者随时随地来买,在供货商生产商品的时候是忙的,当生产的商品没有被购买或者还有很多商品存放的时候供货商不会生产太多,这个时候就是闲的。现在我们来把模型具体化,消费者实际上就是线程,生产者(供货商)也是线程,而超市这个交易场所实际上是一种特定的缓冲区(队列,链式,哈希等),而上面超市中的商品实际上就是数据。对于这个模型不知道大家能想到什么呢?没错就是管道通信,管道不就是一个进程把数据放在缓冲区中另一个进程去缓冲区中拿吗,我们当时写的一个关闭读端一个关闭写端的代码的例子不知道大家想起来没有。
对于我们刚刚说的缓冲区,这个缓冲区既要被消费者看到,也要被生产者看到,那么就注定了交易场所一定是一个会被多线程并发访问的公共区域,并且注定了多线程一定要保护共享资源的安全,注定了一定在这种情况下,要自己维护线程互斥与同步的关系。
下面我们讲解一下模型中的关系:
1.生产者和生产者:生产者和生产者之间一定是互斥的关系,因为他们都抢着要向缓冲区中存放资源,只有谁生产的快谁先放入缓冲区。
2.消费者和消费者:消费者和消费者之间的关系一定也是互斥的,因为当某个商品只有一个的时候那么两个消费者一定会去抢这个商品,这个时候就体现出互斥的关系了。
3.生产者和消费者:生产者和消费者之间是有同步关系的。因为当商品生产好后只有消费者消费了生产者才会继续生产,否则缓冲区是满的即使生产者再次生产也放不进去缓冲区。第二个生产者和消费者之间是有互斥关系的,因为我们不能让消费者一边在缓冲区拿东西一边让生产者往缓冲区送东西,生产者和消费者之间只能有一个进入缓冲区。
下面我们用一个简单的方法记录一下生产者消费者模型:
3种关系(生产者和生产者,消费者和消费者,生产者和消费者),两个角色(生产者和消费者),一个交易场所(通常是缓冲区)。所以我们以后记生产者消费者模型只需记住321即可。