Linux--线程死锁

http://blog.csdn.net/gebushuaidanhenhuai/article/details/73799824

线程为什会死锁??“锁”又是什么东西?我们这篇博客主要讲一下为什么要给线程加锁,为什么会出现线程死锁,线程死锁怎么解决。

互斥锁

在我的上篇博客已经讲解了一些线程的基本知识Linux–线程控制我们可以了解到线程是共享同一份内存的。这就意味着多个线程同时访问共享数据时可能发生冲突。

分析程序


我们首先分析一个小程序:

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>static int g_count = 0;
void* read_write_num(void* _val)
{int val = 0;int i = 0;for(; i<5000; ++i){val  = g_count;printf("pthread id is:%x,count is : %d\n",(unsigned long)pthread_self(),g_count);g_count = val + 1;}return NULL;
}int main()
{pthread_t tid1;pthread_t tid2;pthread_create(&tid1,NULL,read_write_num,NULL);pthread_create(&tid2,NULL,read_write_num,NULL);pthread_join(tid1,NULL);pthread_join(tid2,NULL);printf("count final val is:%d\n ",g_count);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

这里写图片描述

上面程序是创建两个线程,各自都把全局静态变量count增加5000次。正常情况下结果应该是10000,但是程序的最终结果总是5000左右,与正确结果相差很远,这是为什么呢? 
这是因为对多线程的程序,发生了访问冲突了。对一个线程要对一个变量进行加1操作,需要进行三步: 
1.将数据从内存单元读入寄存器。 
2.把寄存器中的变量做加1操作。 
3.把加1后的变量重新写回内存单元。

我们来模拟一下上面程序的运行。我们上面的程序有两个线程tid1,tid2。假如此时变量count = 10. 
当tid1进行第二步时,从内存读到10,在寄存器加1–>10+1=11。 
但是此时tid2切入,从内存读到10,在寄存器加1–>10+1=11。 
因为tid1还没有把11写入到内存中,tid2读到的值并不是我们想要的11,这就引发了问题。 
解决的方法就是引入互斥锁,获得锁线程可以完成“读–修改–写”的操作,然后释放锁给其他线程,没有获得锁的线程只能等待而不能访问共享数据。这样“读–修改–写”散步操作组成了一个原子操作,要么都执行,要么都不执行。

互斥锁操作函数

互斥锁的数据类型为pthread_mutex_t. 
创建互斥锁:

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t* restrict mutex,const pthread_mutexattr_t* restrict attr)
  • 1
  • 2

返回值:成功返回0,失败返回错误号。 
参数: 
pthread_mutex_init()函数是以动态方式创建互斥锁的,参数attr指定了新建互斥锁的属性。如果参数attr为空,则使用默认的互斥锁属性,默认属性为快速互斥锁 。 
如果Mutex变量是静态分配的,也可以利用宏定义来初始化。

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  • 1

此方法相当于pthread_mutex_init初始化并且attr参数为NULL. 
销毁互斥锁:

#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 1
  • 2

返回值:成功返回0,失败返回错误码。 
加锁:

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
  • 1
  • 2

解锁:

#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • 1
  • 2

返回值:成功返回0,失败返回错误号。 
一个线程可以调用pthread_mutex_lock还会获得Mutex,若果这时另一个线程已经调用pthread_mutex_lock获得了该Mutex,则当前进程需要挂起等待,直到另一个线程调用pthread_mutex_unlock释Mutex,当前进程被唤醒,才能获得该Mutex并继续执行。 
如果一个线程既想获得锁,又不想挂起等待,可以调用pthread_mutex_trylock,如果Mutex已经被另一个线程获得,这个函数会失败返回EBUSY,而不会使线程挂起等待。

#include <pthread.h>
int pthread_mutex_trylock(pthread_mutex_t *mutex);
  • 1
  • 2

这时我们就可以解决上面那个代码的bug了。

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>static int g_count = 0;
pthread_mutex_t mutex_lock = PTHREAD_MUTEX_INITIALIZER;
void* read_write_num(void* _val)
{int val = 0;int i = 0;for(; i<5000; ++i){pthread_mutex_lock(&mutex_lock);val  = g_count;printf("pthread id is:%x,count is : %d\n",(unsigned long)pthread_self(),g_count);g_count = val + 1;pthread_mutex_unlock(&mutex_lock);}return NULL;
}int main()
{pthread_t tid1;pthread_t tid2;pthread_create(&tid1,NULL,read_write_num,NULL);pthread_create(&tid2,NULL,read_write_num,NULL);pthread_join(tid1,NULL);pthread_join(tid2,NULL);printf("count final val is:%d\n ",g_count);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

这里写图片描述

线程死锁

⼀般情况下,如果同⼀个线程先后两次调⽤lock,在第⼆次调⽤时,由于锁已经被占⽤,该线程会挂起等待别的线程释放锁,然⽽锁正是被⾃⼰占⽤着的,该线程又被挂起⽽没有机会释放锁,因此 就永远处于挂起等待状态了,这叫做死锁(Deadlock)。 
另一种情况:线程A获 得了锁1,线程B获得了锁2,这时线程A调⽤lock试图获得锁2,结果是需要挂起等待线程B释放 锁2,⽽这时线程B也调⽤lock试图获得锁1,结果是需要挂起等待线程A释放锁1,于是线程A和B都 永远处于挂起状态了。 
死锁产生的4个必要条件: 
1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。 
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。 
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。 
4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。 
预防死锁: 
只要打破四个必要条件之一就能有效预防死锁的发生:打破互斥条件:改造独占性资源为虚拟资源,大部分资源已无法改造。 
打破不可抢占条件:当一进程占有一独占性资源后又申请一独占性资源而无法满足,则退出原占有的资源。 
打破占有且申请条件:采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待,这样就不会占有且申请。 
打破循环等待条件:实现资源有序分配策略,对所有设备实现分类编号,所有进程只能采用按序号递增的形式申请资源。

有序资源分配法


这种算 法资源按某种规则系统中的所有资源统一编号(例如打印机为1、磁带机为2、磁盘为3、等等),申请时必须以上升的次序。系统要求申请进程: 
1、对它所必须使用的而且属于同一类的所有资源,必须一次申请完; 
2、在申请不同类资源时,必须按各类设备的编号依次申请。例如:进程PA,使用资源的顺序是R1,R2; 进程PB,使用资源的顺序是R2,R1;若采用动态分配有可能形成环路条件,造成死锁。 
采用有序资源分配法:R1的编号为1,R2的编号为2; 
PA:申请次序应是:R1,R2 
PB:申请次序应是:R1,R2 
这样就破坏了环路条件,避免了死锁的发生

银行家算法


避免死锁算 法中最有代表性的算 法是Dijkstra E.W 于1968年提出的银行家 算 法: 
银行家算法是避免死锁的一种重要方法,防止死锁的机构只能确保上述四个条件之一不出现,则系统就不会发生死锁。通过这个算法可以用来解决生活中的实际问题,如银行贷款等。 
程序实现思路银行家算法顾名思义是来源于银行的借贷业务,一定数量的本金要应多个客户的借贷周转,为了防止银行家资金无法周转而倒闭,对每一笔贷款,必须考察其是否能限期归还。在操作 系统中研究资源分配策略时也有类似问题,系统中有限的资源要供多个进程使用,必须保证得到的资源的进程能在有限的时间内归还资源,以供其他进程使用资源。如果资源分配不得到就会发生进程循环等待资源,则进程都无法继续执行下去的死锁现象。 
把一个进程需要和已占有资源的情况记录在进程控制中,假定进程控制块PCB其中“状态”有就绪态、等待态和完成态。当进程在处于等待态时,表示系统不能满足该进程当前的资源申请。“资源需求总量”表示进程在整个执行过程中总共要申请的资源量。显然,每个进程的资源需求总量不能超过系统拥有的资源总数, 银行算法进行资源分配可以避免死锁。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/384061.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

两个队列实现一个栈

用两个队列实现一个栈的原理是这样的. 规定两个队列, 必须有一个队列是非空, 一个队列是空.每次入栈时必须往非空队列中入, 而每次出栈时, 必须将非空队列里的元素装到空队列中, 直到非空队列中只有一个元素时, 此时就将剩下的这个元素出栈即可. 而取栈顶元素时, 和出栈一样, 先…

POJ-1144 Network——Trajan+割点

【题目描述】 A Telephone Line Company (TLC) is establishing a new telephone cable network. They are connecting several places numbered by integers from 1 to N . No two places have the same number. The lines are bidirectional and always connect together tw…

Linux--生产者与消费者

http://blog.csdn.net/gebushuaidanhenhuai/article/details/74011636 基本概念 提到生产者和消费者&#xff0c;我们最有可能想到的是商店卖东西&#xff0c;顾客在货架上(缓冲区&#xff09;买东西。 生产者消费者问题&#xff0c;其实是一个多线程同步问题的经典案例。该问…

进程的挂起以及可重入函数

相关接口     pause 函数用于将进程挂起. 如果信号的处理动作是终止进程, 则进程终止, pause 函数没有返回值; 如果信号的处理动作是忽略, 则进程被挂起, pause函数不返回, 如果信号的处理动作是捕捉, 则调用信号处理动作之后pause 返回 -1.来看一段代码 #include<s…

POJ1236Network of Schools——强连通分量缩点建图

【题目描述】 A number of schools are connected to a computer network. Agreements have been developed among those schools: each school maintains a list of schools to which it distributes software (the “receiving schools”). Note that if B is in the distri…

C——通过调用函数分配内存

http://blog.csdn.net/u012627502/article/details/3579724 1&#xff09;以返回值方式返回&#xff1a;把动态分配的存储位置地址&#xff0c;赋值给指针类型返回值&#xff08;不同于被调用函数的自动变量地址&#xff09; 2&#xff09;以形参形式返回&#xff1a;二级指针类…

gdb调试多进程程序

1.gdb下调试多进程程序只需要以下几条命令即可              除此之外还可以查看正在调试的进程 info inferiors, 同时也可以将当前正在调试的进程切换到另外一个进程中让其取运行     2.代码调试演示 #include<stdio.h> #include<stdlib.h> #…

BZOJ1123-BLO——强连通分量求割点+计数

【题目描述】 Byteotia城市有n个 towns m条双向roads. 每条 road 连接 两个不同的 towns ,没有重复的road. 所有towns连通。Input 输入n<100000 m<500000及m条边Output 输出n个数&#xff0c;代表如果把第i个点去掉&#xff0c;将有多少对点不能互通。Sample Input 5…

关于memcpy和memmove两函数的区别

http://blog.csdn.net/caowei840701/article/details/8491836 [cpp] view plaincopy <p> 关于memcpy和memmove两个c标准库函数&#xff0c;其功能都是将一块内存区域中的指定大小内容复制到目标内存中&#xff0c;在翻阅c标准库实现的源代码我们发现他们是有区别的。&…

判断字符串出栈合法性

先来看说一下思路 接下来就是写代码了 int StackOrder(SeqStack* stack, char* input, char* output, int size_input, int size_output) {if(stack NULL || input NULL || output NULL){return 0;}int i_input 0;int j_output 0;SeqStackType value;for(; j_output <…

CodeForces - 1200C——小模拟

【题目描述】 Amugae is in a very large round corridor. The corridor consists of two areas. The inner area is equally divided by n sectors, and the outer area is equally divided by m sectors. A wall exists between each pair of sectors of same area (inner o…

1 单例模式

达内 闵大神 //饿汉单例模式 #include <iostream> using namespace std;class Singleton { public:static Singleton& getInstance(){return s_instance;} private:Singleton(){}Singleton(const Singleton& that){}static Singleton s_instance;//静态成员变量 …

共享栈

1.定义 所谓共享栈就是利用一个数组实现两个栈. 先来看一下共享栈的数据结构 typedef struct SharedStack {int top1;int top2;SeqStackType* data; }SharedStack; 2. 初始化 void SharedStackInit(SharedStack* stack) {if(stack NULL){return;//非法输入}stack -> top…

BZOJ1018 | SHOI2008-堵塞的交通traffic——线段树维护区间连通性+细节

【题目描述】 BZOJ1018 | SHOI2008-堵塞的交通traffic 有一天&#xff0c;由于某种穿越现象作用&#xff0c;你来到了传说中的小人国。小人国的布局非常奇特&#xff0c;整个国家的交通系统可 以被看成是一个2行C列的矩形网格&#xff0c;网格上的每个点代表一个城市&#xff0…

C++ 函数隐藏

C该函数隐藏 只有基类成员函数的定义已声明virtualkeyword&#xff0c;当在派生类中的时间&#xff0c;以支付功能实现&#xff0c;virtualkeyword可以从时间被添加以增加。它不影响多状态。 easy混淆视听&#xff0c;掩盖&#xff1a; &#xff0c;规则例如以下&#xff1a; …

迷宫求解(递归)

首先来看一下迷宫简易图                                  我们用 0 来表示该位置是墙, 用 1 来表示该位置是路. 所以, 我们在处理迷宫问题的时候可以将其看成一个二维数组即可, 而对应的每一条路我们可以用坐标的形式将其表示, 所以还需要…

CodeForces - 786C——二分+模拟?

【题目描述】 Rick and Morty want to find MR. PBH and they cant do it alone. So they need of Mr. Meeseeks. They Have generated n Mr. Meeseeks, standing in a line numbered from 1 to n. Each of them has his own color. i-th Mr. Meeseeks color is ai.Rick and M…

迷宫求解(非递归)

上篇文章写出了利用函数形成栈桢的特性完成迷宫求解问题, 本篇文章我们自己手动维护一个栈, 其进行出栈, 入栈, 取栈顶元素, 来完成迷宫求解寻路的过程     思路和以前一样, 首先, 我们先定义一个栈, 对其初始化, 同时, 定义一个迷宫地图, 对该地图进行初始化, 先判断当前…

数据结构练习——双向链表

http://www.cnblogs.com/-Lei/archive/2012/04/10/2440399.html 复习一下数据结构。。。。说不准下个星期就用上了 只不过写的很简单&#xff0c;没有封装 DoubleLinkList.h #ifndef GUARD_DoubleLinkList_h #define GUARD_DoubleLinkList_h#include <stdio.h>struct Li…

CodeChef - DGCD——树链剖分+差分

【题目描述】 Youre given a tree on N vertices. Each vertex has a positive integer written on it, number on the ith vertex being vi. Your program must process two types of queries :1. Find query represented by F u v : Find out gcd of all numbers on the uniq…