【Linux】面试重点:死锁和生产消费模型原理

这篇具有很好参考价值的文章主要介绍了【Linux】面试重点:死锁和生产消费模型原理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

面试要点来了~

文章目录

  • 前言
  • 一、死锁的一系列问题
  • 二、生产者消费者模型原理
  • 总结

前言

上一篇的互斥量原理中我们讲解了锁的原理,我们知道每次线程申请锁的时候一旦申请成功这个线程自己就把锁带在自己身上了,这就保证了锁的原子性(因为只有一个锁),而当我们已经申请成功锁了然后再去申请锁会发生什么事呢?下面我们在死锁中回答这个问题。


一、死锁

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放的资源而处于的一种永久等待状态。
上面死锁的概念说一组进程中的各个进程,那么一个锁会引发死锁的问题吗?答案是会的,因为代码是程序员写的,所以一旦代码写的有问题即使是一把锁也会造成死锁的问题,比如说:申请锁却不去释放。申请了一把锁成功后又去申请同样的锁,这种情况下肯定会申请失败,一旦申请失败就导致该线程被阻塞挂起,那么这个锁就变成了死锁。申请了一把锁后又去申请其他的锁,结果其他的锁申请失败被阻塞,一旦被阻塞那么这个进程就会带着第一次申请成功的锁休眠,这样就造成死锁问题(因为第一把锁永远不会有线程申请成功了)。
当然造成死锁的条件有多种,下面我们直接给出结论:
1.互斥
2.请求与保持(我拿着我的然后申请你的)
3.环路等待(循环等待条件)
4.不剥夺条件
想要避免死锁,我们直接破坏上面4个条件中的任意一个即可。对于互斥这个条件想要破坏很简单,我们直接不加锁即可。对于请求与保持这个条件,我们只要主动的去释放锁那么就不会出现这样的问题。环路等待就是多个线程申请锁的顺序不一样,导致有的线程出现“请求与保持”的情况,对于这个问题我们只需要让每个线程按照顺序去申请锁即可。第四个条件的意思是:一个线程申请了锁然后只能自己释放锁,对于这样的情况一旦这个线程不去释放锁就会造成死锁问题,所以我们要剥夺这个线程的释放锁的条件,让其他线程去释放刚刚那个没有释放锁的线程。这里可能有些人会懵了,还可以一个线程申请锁,另一个线程释放刚刚那个线程申请的锁?答案是可以,下面我们用代码演示一下:
#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;
}

首先我们先不让主线程释放锁,看一下死锁的状态:

【Linux】面试重点:死锁和生产消费模型原理

 我们可以看到本来应该打印“我又活了过来”,结果直接退出了,下面我们让主线程给新线程解锁:

【Linux】面试重点:死锁和生产消费模型原理

【Linux】面试重点:死锁和生产消费模型原理通过运行结果我们可以看到刚刚死锁的进程活过来了,那么就意味着可以一个线程申请锁,另一个线程释放锁。所以要破坏第四个条件直接控制一个线程统一释放锁。

下面我们总结一下:

避免死锁的方法:
1. 破坏死锁的四个必要条件
2. 加锁顺序一致
3. 避免锁未释放的场景
4. 资源一次性分配
下面讲解linux线程同步中的条件变量:
当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
例如一个线程访问队列时,发现队列为空,它只能等待,直到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。
同步概念与竞态条件:
同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。
竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也不难理解。
下面讲一下条件变量函数初始化接口:
【Linux】面试重点:死锁和生产消费模型原理
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrictattr);
参数:
cond :要初始化的条件变量
attr NULL
销毁条件变量:
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);
下面我们用代码演示这些接口如何使用:
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这个接口的作用是每次唤醒一个线程,每次唤醒都是按顺序唤醒的,唤醒后这个线程就会打印“活动”,下面我们运行起来看看:

【Linux】面试重点:死锁和生产消费模型原理

 可以看到这里唤醒的顺序都是31245,下面我们可以用一下pthread_cond_broadcast这个接口来一次唤醒所有的接口:

【Linux】面试重点:死锁和生产消费模型原理

【Linux】面试重点:死锁和生产消费模型原理 通过运行结果我们可以看到同样是按顺序进行唤醒的,所以条件变量的作用是:允许多线程在cond中队列式等待(队列式等待就是一种顺序)。

下面我们总结一下:

为什么 pthread_cond_wait 需要互斥量 ?
条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。
条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。
【Linux】面试重点:死锁和生产消费模型原理
由于解锁和等待不是原子操作。调用解锁之后, pthread_cond_wait 之前,如果已经有其他线程获取到互斥量,摒弃条件满足,发送了信号,那么 pthread_cond_wait 将错过这个信号,可能会导致线程永远阻塞在这个 pthread_cond_wait 。所以解锁和等待必须是一个原子操作。
int pthread_cond_wait(pthread_cond_ t *cond,pthread_mutex_ t * mutex) ; 进入该函数后,会去看条件量等于0 不?等于,就把互斥量变成 1 ,直到 cond_ wait 返回,把条件量改成 1 ,把互斥量恢复
成原样。

 二、生产者消费者模型

下面我们先画张图理解一下生产者消费者模型:

【Linux】面试重点:死锁和生产消费模型原理

 首先在模型中超市并不是属于生产者,超市只是一个交易场所,供货商会把生产的商品放到超市中,然后消费者直接去超市买。为什么消费者不直接去供货商那里购买呢?因为消费者不知道供货商什么时候生产好了商品,而且消费者的需求非常零散,供货商的生产水平很强,导致这两者之前有着天然的屏障。那么这个模型有什么特点呢?

第一点效率高,第二点忙闲不均。第二点是什么意思呢?意思就是说我们可以把供货商生产的一大批商品全部放在超市中让消费者随时随地来买,在供货商生产商品的时候是忙的,当生产的商品没有被购买或者还有很多商品存放的时候供货商不会生产太多,这个时候就是闲的。现在我们来把模型具体化,消费者实际上就是线程,生产者(供货商)也是线程,而超市这个交易场所实际上是一种特定的缓冲区(队列,链式,哈希等),而上面超市中的商品实际上就是数据。对于这个模型不知道大家能想到什么呢?没错就是管道通信,管道不就是一个进程把数据放在缓冲区中另一个进程去缓冲区中拿吗,我们当时写的一个关闭读端一个关闭写端的代码的例子不知道大家想起来没有。

对于我们刚刚说的缓冲区,这个缓冲区既要被消费者看到,也要被生产者看到,那么就注定了交易场所一定是一个会被多线程并发访问的公共区域,并且注定了多线程一定要保护共享资源的安全,注定了一定在这种情况下,要自己维护线程互斥与同步的关系。

下面我们讲解一下模型中的关系:

1.生产者和生产者:生产者和生产者之间一定是互斥的关系,因为他们都抢着要向缓冲区中存放资源,只有谁生产的快谁先放入缓冲区。

2.消费者和消费者:消费者和消费者之间的关系一定也是互斥的,因为当某个商品只有一个的时候那么两个消费者一定会去抢这个商品,这个时候就体现出互斥的关系了。

3.生产者和消费者:生产者和消费者之间是有同步关系的。因为当商品生产好后只有消费者消费了生产者才会继续生产,否则缓冲区是满的即使生产者再次生产也放不进去缓冲区。第二个生产者和消费者之间是有互斥关系的,因为我们不能让消费者一边在缓冲区拿东西一边让生产者往缓冲区送东西,生产者和消费者之间只能有一个进入缓冲区。

下面我们用一个简单的方法记录一下生产者消费者模型:

3种关系(生产者和生产者,消费者和消费者,生产者和消费者),两个角色(生产者和消费者),一个交易场所(通常是缓冲区)。所以我们以后记生产者消费者模型只需记住321即可。文章来源地址https://www.toymoban.com/news/detail-485581.html


总结

为何要使用生产者消费者模型
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。

到了这里,关于【Linux】面试重点:死锁和生产消费模型原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Linux】线程安全-生产者消费者模型

    1个线程安全的队列:只要保证先进先出特性的数据结构都可以称为队列 这个队列要保证互斥(就是保证当前只有一个线程对队列进行操作,其他线程不可以同时来操作),还要保证同步,当生产者将队列中填充满了之后要通知消费者来进行消费,消费者消费之后通知生产者

    2024年02月10日
    浏览(32)
  • 【Linux】深入理解生产者消费者模型

    生产者 - 消费者模型 Producer-consumer problem 是一个非常经典的多线程并发协作的模型,在分布式系统里非常常见。 在多线程开发中,如果生产者生产数据的速度很快,而消费者消费数据的速度很慢,那么生产者就必须等待消费者消费完了数据才能够继续生产数据,同理如果消费

    2024年02月06日
    浏览(26)
  • 【Linux】多线程 --- 线程同步与互斥+生产消费模型

    人生总是那么痛苦吗?还是只有小时候是这样? —总是如此 1. 假设现在有一份共享资源tickets,如果我们想让多个线程都对这个资源进行操作,也就是tickets- -的操作,但下面两份代码分别出现了不同的结果,上面代码并没有出现问题,而下面代码却出现了票为负数的情况,这

    2024年02月06日
    浏览(36)
  • Linux基于多线程和任务队列实现生产消费模型

    目录 一、生产者消费者模型 二、代码实现模型 2.1 BlockQueue.hpp 2.2 MainCP.cc 2.3 执行结果 三、效率优势 将上述图片逻辑转换成代码逻辑就是,一批线程充当生产者角色,一批线程充当消费者角色,仓库是生产者和消费者获取的公共资源!下面我想用 321原则 来解释这个模型。 既

    2024年02月09日
    浏览(36)
  • Linux——生产者消费者模型和信号量

    目录 ​​​​​​​ 基于BlockingQueue的生产者消费者模型 概念 条件变量的第二个参数的作用  锁的作用 生产者消费者模型的高效性 生产者而言,向blockqueue里面放置任务 消费者而言,从blockqueue里面拿取任务: 总结 完整代码(不含存储数据的线程) 完整代码(含存储线程)  信

    2024年02月07日
    浏览(32)
  • Linux入门之多线程|线程的同步|生产消费模型

    文章目录 一、多线程的同步 1.概念 2.条件变量 2.1条件变量概念 2.2条件变量接口 1.条件变量初始化 2.等待条件满足 3.唤醒等待 3.销毁条件变量 2.3条件变量demo 二、生产消费模型 1.生产消费模型 2.基于BlockQueue的生产者消费者模型 3.基于C++用条件变量和互斥锁实现一个生产消费模

    2024年02月09日
    浏览(27)
  • 【linux】POSIX信号量+基于环形队列的生产消费模型

    喜欢的点赞,收藏,关注一下把! 上篇文章最后我们基于BlockQueue生产者消费者模型写了代码,测试什么的都通过了。最后我们说代码还有一些不足的地方,由这些不足从而引入了我们接下来要学的信号量! 我们在看一看不足的地方 1.一个线程,在操作临界资源的时候,必须

    2024年02月01日
    浏览(34)
  • 【Linux】多线程02 --- 线程的同步互斥问题及生产消费模型

    🍎 作者: 阿润菜菜 📖 专栏: Linux系统编程 线程同步互斥问题是指多线程程序中,如何保证共享资源的正确访问和线程间的协作。 因为线程互斥是实现线程同步的基础和前提,我们先讲解线程互斥问题。 在多线程中,假设我们有一个黄牛抢票的代码,其中有一份共享资源

    2024年02月08日
    浏览(31)
  • 【linux】线程同步+基于BlockingQueue的生产者消费者模型

    喜欢的点赞,收藏,关注一下把! 在线程互斥写了一份抢票的代码,我们发现虽然加锁解决了抢到负数票的问题,但是一直都是一个线程在抢票,它错了吗,它没错但是不合理。那我们应该如何安全合理的抢票呢? 讲个小故事。 假设学校有一个VIP学霸自习室,这个自习室有

    2024年02月03日
    浏览(89)
  • 【Linux】详解线程第三篇——线程同步和生产消费者模型

    本篇线程同步的内容是完全基于线程互斥来讲的,如果屏幕前的你对于线程互斥还不是很了解的话,可以看看我上一篇博客:【Linux】详解线程第二篇——用黄牛抢陈奕迅演唱会门票的例子来讲解【 线程互斥与锁 】 上篇线程互斥中重点讲了互斥锁,虽然解决了多线程并发导

    2024年02月07日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包