一、死锁
1、什么是死锁?
两个或两个以上的线程或进程,因争夺资源而造成的一种互相等待的现象。
2、死锁检测的方法:
-
- 代码审查法:通过分析代码定位死锁位置,该方法对开发人员的代码理解能力要求较高。
-
- 有向图检测法:基于资源分配和请求情况构建有向图,检测图中是否存在环路来判定死锁。
-
- 资源分配图法:一种利用资源分配关系的有向图分析方法。图中包含两类节点(资源节点与进程节点)和两类有向边(分配边与请求边)。
-
- 银行家算法:通过模拟资源分配过程,提前预测系统是否可能进入不安全状态。
二、具体实现
1、死锁的构建:
定义三个线程,三个互斥锁
pthread_t t1, t2, t3;
pthread_create(&t1, NULL, Funct1, NULL);
pthread_create(&t2, NULL, Funct2, NULL);
pthread_create(&t3, NULL, Funct3, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
/*....*/
void *Funct1(void *arg)
{
printf("thread1: %ld\n", pthread_self());
pthread_mutex_lock(&r1);
sleep(2);
pthread_mutex_lock(&r2);
pthread_mutex_unlock(&r2);
pthread_mutex_unlock(&r1);
}
void *Funct2(void *arg){
/*...*/
}
void *Funct3(void *arg){
/*...*/
}
2、建立有向图
采用邻接链表法建立有向图
/*用于表示图中的节点类型。它有两个值:PROCESS 和 RESOURCE,
*分别表示节点可以是一个进程或一个资源。
*/
enum Type {PROCESS, RESOURCE};
typedef struct source_type{
uint64 id;
enum Type type;
uint64 lock_id;
int degress;
}source_type;
typedef struct vertex{
source_type s;
vertex *next;
}vertex;
typedef struct graph{
vertex list[MAX_NUM];
int num;
source_type locklist[MAX_NUM];
int lockidx;
pthread_mutex_t _mutex;
}graph;
3、检测有向图是否成环
1、加锁之前先查表,看是否已占用
否,正常进行, 有向图中增加一条边
是,查找该线程id self ---------> tid
/**
* @brief 在加锁前记录锁的信息
*
* 在尝试获取锁之前,记录锁的信息和与之相关联的进程信息,包括进程ID和锁ID,以及锁的度数(即获取该锁的进程数量)。
* 如果当前进程和锁相关联的进程之间尚未建立边,则添加边。
*
* @param tid 进程ID
* @param lockaddr 锁的地址
*/
void lock_before(uint64 tid, uint64 lockaddr)
{
int idx = 0;
for (idx = 0; idx < tg->lockidx; idx++)
{
if (tg->locklist[idx].lock_id == lockaddr)
{ //
struct source_type from;
from.id = tid;
from.type = PROCESS;
add_vertex(from);
struct source_type to;
to.id = tg->locklist[idx].id;
to.type = PROCESS;
add_vertex(to);
tg->locklist[idx].degress++;
if (!verify_edge(from, to))
add_edge(from, to);
}
}
}
2、加锁之后,更新表中的关系
/**
* @brief 在给定线程ID和锁地址后锁定该锁
*
* 如果锁地址对应的锁不存在,则在锁列表中创建一个新的锁,并将其与给定线程ID关联。
* 如果锁地址对应的锁已经存在,则将给定线程ID与该锁关联,并更新相关数据结构。
*
* @param tid 线程ID
* @param lockaddr 锁地址
*/
void lock_after(uint64 tid, uint64 lockaddr)
{
int idx = 0;
if (-1 == (idx = search_lock(lockaddr)))
{ //
int eidx = search_empty_lock(lockaddr);
tg->locklist[eidx].id = tid;
tg->locklist[eidx].lock_id = lockaddr;
tg->lockidx++;
}
else
{
struct source_type from;
from.id = tid;
from.type = PROCESS;
add_vertex(from);
struct source_type to;
to.id = tg->locklist[idx].id;
to.type = PROCESS;
add_vertex(to);
tg->locklist[idx].degress--;
if (verify_edge(from, to))
remove_edge(from, to);
tg->locklist[idx].id = tid;
}
}
3、加锁之后解锁,删除表中的该关系
/**
* @brief 在指定的线程解锁后,更新锁列表中的信息
*
* 根据给定的线程ID和锁地址,在锁列表中搜索对应的锁,并更新其状态。
* 如果该锁已经被完全解锁(即所有持有该锁的线程都已经解锁),则将该锁从锁列表中移除。
*
* @param tid 线程ID
* @param lockaddr 锁地址
*/
void unlock_after(uint64 tid, uint64 lockaddr)
{
int idx = search_lock(lockaddr);
if (tg->locklist[idx].degress == 0)
{
tg->locklist[idx].id = 0;
tg->locklist[idx].lock_id = 0;
}
}
4、检测是否成环,主要是使用DFS算法
三、总结
- 1、两个或两个以上的线程或进程,因争夺资源而造成的一种互相等待的现象,叫做死锁。
- 2、可以通过资源分配和请求,可以构造有向图,然后通过检测有向图是否成环。
- 3、代码过于冗长,本来是想封装成一个有向图文件,再调用,没想到出现各种传值问题,暂时只能放在一个文件里面,后面再重新整合下
四、问题
1、如何通过资源找到对应的线程
通过一张资源-线程表来确定。
2、如何知道线程想占用资源
加锁之前,记录线程的信息以及想要获取资源的信息;加锁之后,更新资源-线程表。