性能文章>通过生产者与消费者模型感受死锁>

通过生产者与消费者模型感受死锁原创

710909

一. 实验目的及实验环境

1.实验目的

通过观察、分析实验现象,深入理解产生死锁的原因,学会分析死锁的方法, 并利用 pstack、 gdb 或 core 文件分析( valgrind (DRD+Helgrind) 可选 )其中的一 种方法来分析死锁。

2.实验环境

(1)硬件

CPU:Intel i5

内存:16G

显示器:NVIDIA 1050Ti

硬盘空间:1T

(2)软件

虚拟机名称及版本:VMware

操作系统名称及版本:Ubuntu16.04

编译器:gedit

二. 实验内容

1、实验前准备工作

仔细阅读参考资料 Linux 死锁现象及分析方法,了解对死锁现象进行分析的各种工具,选择其中一种对死锁现象进行分析。

2、实验内容

1)准备好生产者-消费者问题,或者哲学家就餐问题产生死锁的代码。

2)编译程序后,注意加调试选项-g,先预计一下这个程序的运行结果,运行程序,若程序没有响应,按 ctrl+c 中断程序运行,然后再重新运行,如此反复若干次,记录下每次的运行结果。若产生了死锁,通过工具对死锁进行分析。

三、实验结果分析

连续多次查看这个进程的函数调用关系堆栈,死锁线程将一直处于等锁状态,对比多次的函数调用堆栈输出结果,确定哪两个线程(或者几个线程)一直没有变化且一直处于等锁的状态,给出运行结果截图,在图中标出死锁出现的地方,并分析为什么会出现死锁代码设计:假设只有一个生产者,却有一个消费者,生产者一次生产一个资源,消费者一次消耗一个资源,按照基本原理应该是先申请资源,进而互斥锁上锁,若申请失败,就不上锁,等待申请成功,再上锁。为了产生死锁条件修改顺序:先互斥锁上锁,然后再进行资源申请。这样有可能出现生产者未来得及生产资源,消费者就进行申请,但先上锁后申请,所以未申请到,不会解锁,因为互斥锁未解锁,生产者无法生产。举个简单的例子(和我组成员刘传玺一同商讨得出):

假设有一个筐子,甲做馒头,乙吃馒头,合理的情况应该是乙看一眼筐里有没有馒头,若有,则伸手去取,若没有,则等甲放进去,再取;相对应,如果做甲看见乙在取馒头,此时筐子被占用了,甲暂时还不能放馒头进去,等乙取完了, 甲才放新馒头。就这样有条不紊一直运行。但是现在情况变了,乙不管三七二十一伸手就拿,要是拿到了还好,就吃了,要是手快了,馒头还没做好,他伸手取一抓,没抓找,手就放在筐子里等,甲一看手在筐里放着,我馒头也放不进去啊,那就等他把手拿出来再放进去… …一个在等馒头来,一个在等手出去:死锁!

发生死锁,无资源,却申请资源 :

image.png

进行检查 :

image.png

锁定错误位置 :

image.png

互斥锁先锁定后申请资源,顺序出错,可能会导致死锁。发现错误,解决错误:

image.png

四、总结

平时阅读代码,觉得一切顺理成章,非常自然,从未思考为何要这样做。通过本次实验,老师逆向思维,让我们写出死锁!所有代码都在避开死锁,老师让我们写出死锁,无从下手,毫无头绪,实在让人头疼。查阅资料,反复理解运行顺序: 申请,上锁,释放,来来回回,费九牛二虎之力才写出死锁。回头观望, 瞬间恍然大悟,明白老师了良苦用心,躲避错误人人都会,但如果我能从无错中犯错,也就是说我理解了整个运行结构,操作流程之后,才能知道在何处会犯错, 能犯错,通过犯错让我们更深刻的体会错误,理解错误。从而根本的明白错误发生的原因以及修改的方式。不得不说实在高明。同时我也感受到了 Linux 代码的严谨,仅仅是两行代码顺序调换,就发生了 意想不到的错误,若在大工程中犯错,可能会带来毁灭性的后果。让我在感叹代码严谨的同时,也让我明白了不可以抱有侥幸心理,只有错和不错,没有可能一说!可能有错那就是错误,100%正确才是真正的正确,严谨认真、高效简洁是编写代码要有的思维风范。

五.附录:源代码(电子版)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>

#define M 1            
#define P(x)   sem_wait(&x)
#define V(x)   sem_post(&x)

int in = 0;        
int out = 0;        
int buff[M] = {0};

sem_t sem_dr;          
sem_t sem_co;          
pthread_mutex_t mutex;

void print()
{    
    static int number = 0;
    int i;    
    printf("(%2d)\t",number);
    for(i = 0; i < M; i++)        
    printf("%d ", buff[i]);    
    number++;    
    printf("\n");
}

void *producer()
{    
    for(;;)    
    {        
        sleep(1);    
        P(sem_dr);        
        pthread_mutex_lock(&mutex);
        in = in % M;        
        printf("(+)produce a product. buffer:");
        buff[in] = 1;        
        print();        
        ++in;
        pthread_mutex_unlock(&mutex);        
        V(sem_co);    
    }
}

void *consumer()
{    
    for(;;)    
    {        
        sleep(1);
        pthread_mutex_lock(&mutex);        
        P(sem_co);
        out = out % M;        
        printf("(-)consume a product. buffer:");
        buff[out] = 0;        
        print();        
        ++out;
        pthread_mutex_unlock(&mutex);        
        V(sem_dr);    
    }
}

void sem_mutex_init()
{        
    int init1 = sem_init(&sem_dr, 0, M);    
    int init2 = sem_init(&sem_co, 0, 0);    
    if( (init1 != 0) && (init2 != 0))    
    {        
        printf("sem init failed \n");        
        exit(1);    
    }        
    int init3 = pthread_mutex_init(&mutex, NULL);    
    if(init3 != 0)    
    {        
        printf("mutex init failed \n");        
        exit(1);    
    }      
}

int main()
{    
    pthread_t id1;    
    pthread_t id2;    
    int i;    
    int ret;
    sem_mutex_init();   /*create the producer thread*/    
    ret = pthread_create(&id1, NULL, producer, NULL);    
    if(ret != 0)    
    {        
        printf("producer creation failed \n");        
        exit(1);    
    }                                                                                                                            
    ret = pthread_create(&id2, NULL, consumer, NULL);                                           
    if(ret != 0)                                                                                                              
    {
        printf("consumer creation failed \n");        
        exit(1);    
    }                                                                                                                           
    pthread_join(id1,NULL);    
    pthread_join(id2,NULL);    
    exit(0);
}
请先登录,再评论

暂无回复,快来写下第一个回复吧~

为你推荐

类初始化导致死锁
一张图简单描述死锁 如上图,Thread1 拿到了 object1,Thread2 拿到了 object2,但是现在 Thread1 需要拿到 object2 的锁才能继续往下,Thread2 又要拿到 object1 才能继续往下
在调试器里看LINUX内核态栈溢出
图灵最先发明了栈,但没有给它取名字。德国人鲍尔也“发明”了栈,取名叫酒窖。澳大利亚人汉布林也“发明”了栈,取名叫弹夹。1959年,戴克斯特拉在度假时想到了Stack这个名字,后来被广泛使用。
LONG究竟有多长,从皇帝的新衣到海康SDK
转眼之间初中毕业30年了,但我仍清楚的记得初中英语的一篇课文,题目叫《皇帝的新装》(“The king’s new clothes”)。这篇课文的前两句话是:”Long long ago, there
雕刻在LINUX内核中的LINUS故事
因为LINUX操作系统的流行,Linus 已经成为地球人都知道的名人。虽然大家可能都听过钱钟书先生的名言:“假如你吃个鸡蛋觉得味道不错,又何必认识那个下蛋的母鸡呢?” 但是如果真是遇到一个“特别显赫”
如何使用Linux内核中没有被导出的变量或函数?
本文详细介绍了使用EXPORT_SYMBOL宏导出函数或变量、使用kallsyms_lookup_name()查找函数或变量的虚拟地址以及内核模块中直接使用内核函数的虚拟地址等3种方案解决没有被EXPORT_SYMBOL 相关的宏导出的变量或函数不能直接使用的问题
LINUX网络子系统中DMA机制的实现
我们先从计算机组成原理的层面介绍DMA,再简单介绍Linux网络子系统的DMA机制是如何的实现的。 计算机组成原理中的DMA 以往的I/O设备和主存交换信息都要经过CPU的操作。不论是最早的轮询方式,
内存泄漏(增长)火焰图
本文总结了在分析内存增长和内存泄漏问题用到的4种追踪方法得到有关内存使用情况的代码路径,使用栈追踪技术对代码路径进行检查,并且会以火焰图的形式把它们可视化输出,在Linux上演示分析过程,随后概述其它系统的情况。
为什么容器内存占用居高不下,频频 OOM(续)
在之前的文章《[为什么容器内存占用居高不下,频频 OOM](https://heapdump.cn/article/1589003)》 中,我根据现状进行了分析和说明,收到了很多读者的建议和疑