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,一经查实,立即删除!

相关文章

两个队列实现一个栈

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

Linux--生产者与消费者

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

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

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

gdb调试多进程程序

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

关于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 <…

共享栈

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

迷宫求解(递归)

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

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

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…

线程的终止分离

1.线程的终止 注意该函数是针对用户级别的, 其中 retal 必须指向一个全局变量, 或者是一个 malloc 分配的, 因为如果是线程的局部变量, 当该线程退出时, 其他线程不能得到这个变量, 因为线程的局部变量各自私有 2. 现成的取消 其中thread是线程的 tid 3.线程的等待与分离 (1)…

C语言中的深拷贝和浅拷贝

http://www.cnblogs.com/zhanggaofeng/p/5421804.html C语言中的深拷贝和浅拷贝 //C语言中的深拷贝和浅拷贝 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<string.h>typedef struct _student{char name[30];char *title;…

死锁的产生和避免

1.死锁产生的四个必要条件 (1)互斥条件&#xff1a;资源是独占的且排他使用&#xff0c;进程互斥使用资源&#xff0c;即任意时刻一个资源只能给一个进程使用&#xff0c;其他进程若申请一个资源&#xff0c;而该资源被另一进程占有时&#xff0c;则申请者等待直到资源被占有者…

gethostbyname() 函数说明

https://www.cnblogs.com/cxz2009/archive/2010/11/19/1881611.html gethostbyname()函数说明——用域名或主机名获取IP地址 包含头文件#include <netdb.h>#include <sys/socket.h>函数原型struct hostent *gethostbyname(const char *name);这个函数的传入值是域…

求解迷宫最短路径

1. 多通路迷宫初始化 先构建一个多通路迷宫,并且对其初始化 void MazeInitShortPath(Maze* maze) {if(maze NULL){return;}int row 0;int col 0;for(; row < MAX_COL; row){for(col 0; col < MAX_COL; col){maze -> map[row][col] Map[row][col];}printf("…

带环迷宫求最短路径

前面介绍了简单的迷宫求解问题, 今天我们就对带环迷宫求出它的最短路径 1.首先来看一个带环迷宫的简单地图 在这张迷宫地图中,我们规定入口点的位置entry的坐标是 (0, 1), 同时, 我们给入口点传一个非法坐标,作为入口点的前一个位置(-1, -1). 接下来的思路就和上一篇的思路是一…

线程的同步与互斥

1. 互斥量 在Linux 下线程使用的都是局部变量, 而我们知道, 每一个线程都独立拥有自己的一个栈, 而这些局部便令就在栈中,而线程的创建就是为了实现通信, 此时线程之间无法共享这些变量     为了使得线程之间能够共享数据, 一次我们可以创建一个全局变量, 此时线程都在进程…

C语言 可变参数

http://www.cnblogs.com/zhanggaofeng/p/6434554.html //可变参数 #include <stdio.h> #include <stdlib.h> #include <string.h> //引用头文件 #include <stdarg.h>/* va_list用于声明一个变量&#xff0c;我们知道函数的可变参数列表其实就是一个字符…

c语言经典算法——查找一个整数数组中第二大数

https://www.cnblogs.com/dootoo/p/4473958.html 题目&#xff1a; 实现一个函数&#xff0c;查找一个整数数组中第二大数。 算法思想&#xff1a; 设置两个变量max1和max2&#xff0c;用来保存最大数和第二大数&#xff0c;然后将数组剩余的数依次与这两个数比较&#xff0c;如…

进程间关系和守护进程

一. 进程组/作业/会话 1.进程组 每一个进程除了有一个进程ID之外, 还属于一个进程组. 进程是一个或多个进程的集合. 通常, 它们与同一个作业向关联, 可以接收来自同一个终端下的各种命令,信号. 每一个进程组都有唯一的进程组 ID. 每一个进程组都可以有一个组长进程. 组长进程的…

猴子偷桃问题

http://blog.csdn.net/snow_5288/article/details/52561882 问题描述&#xff1a; /*有一群猴子&#xff0c;去摘了一堆桃子*/ /*商量之后决定每天吃剩余桃子的一半*/ /*当每天大家吃完桃子之后&#xff0c;有个贪心的小猴都会偷偷再吃一个桃子*/ /*按照这样的方式猴子们每天都…