14

通过生产者与消费者模型感受死锁——西邮本科生的实验

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzI3NzA5MzUxNA%3D%3D&%3Bmid=2664607418&%3Bidx=1&%3Bsn=55424b304afafbbdebcdaf48e0371ac2
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

一. 实验目的及实验环境

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 中断程序运行,然后再重新运行,如此反复若干次,记录下每次的运行结果。若产生了死锁,通过工具对死锁进行分析。

三、实验结果分析

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

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

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

UBZnmuU.png!web

进行检查

r2iIjqf.png!web锁定错误位置 :

AZJRBfe.png!web

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

3aa2I3r.png!web

四、总结

平时阅读代码,觉得一切顺理成章,非常自然,从未思考为何要这样做。通过本次实验,老师逆向思维,让我们写出死锁!所有代码都在避开死锁,老师让我们写出死锁,无从下手,毫无头绪,实在让人头疼。查阅资料,反复理解运行顺序: 申请,上锁,释放,来来回回,费九牛二虎之力才写出死锁。回头观望, 瞬间恍然大悟,明白老师了良苦用心,躲避错误人人都会,但如果我能从无错中犯错,也就是说我理解了整个运行结构,操作流程之后,才能知道在何处会犯错, 能犯错,通过犯错让我们更深刻的体会错误,理解错误。从而根本的明白错误发生的原因以及修改的方式。不得不说实在高明。同时我也感受到了 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);

}


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK