目录
一:什么是死锁?
1、资源的分配(锁)
我们现在所碰到关于锁的操作,基本上都是在多线程中怕碰到的,比如一个线程使用一把锁,在每次使用资源之前都要先加锁的操作。
2、死锁的产生
现在有两个线程(A,B),两把锁(a,b),现在A占用a,B占用b,当都占用之后,现在A还想占用b的资源,而B也想占用a的资源,但是A要占用b之前需要等待B释放b,但是此时的B却是在等待A释放a,A和B都在互相等待,互不释放锁,因此产生了死锁。配一张图可以更好的理解。当然也可以通过代码实现死锁。
//其中的sleep一定要先拿到一个锁之后再sleep,这样保证两个线程都会拿到第一个锁
void *t1_cb(void *arg) {
printf("t1: %ld\n", pthread_self());
pthread_mutex_lock(&r1);
sleep(1);
pthread_mutex_lock(&r2);
pthread_mutex_unlock(&r2);
pthread_mutex_unlock(&r1);
}
void *t2_cb(void *arg) {
printf("t2: %ld\n", pthread_self());
pthread_mutex_lock(&r2);
sleep(1);
pthread_mutex_lock(&r1);
pthread_mutex_unlock(&r1);
pthread_mutex_unlock(&r2);
//两方都在互相等待,于是产生了死锁
}
二:怎么寻找死锁?
1、什么类型可以导致死锁
我们上面是两个线程,当出现多个线程的时候是什么样子呢?如果上面的函数再增加两个,然后互相等待,A等待B,B等待C,C等待D,D等待A,这样形成了一个圆圈,大家都在等,类似于一个圆这种类型的,就可以导致死锁的产生。
2、有向图的使用
对于上面的死锁会形成一个圆,那么我们学到的一个结构也就是有向图,使用这个结构就可以检测是否成为一个圆。
3、对于锁的存储
3.1、加锁前:为了避免成为死锁,当我们每次加锁之前,要在一个表中进行检查,查看这个锁是不是已经被使用了。如果没有被使用,那么我们就可以进行加锁。如果已经使用了,那么我们就向有向图中添加一条边指向已经占用这个锁的线程id,表示我们想拥有你所占用的锁。
3.2、加锁后:加锁后为了方便其他线程进行检查,要保存当前的线程id和锁的id,表示我加了一个锁。并且将有向图的一条边进行删除操作,因为我已经得到我想要的锁了,因此可以删掉。
3.3、解锁后:当不需要这个锁了,解锁后,要将存放在表中的线程id和锁的id进行删除。
其实我们通过上面所描述的,已经是这个死锁检测中最重要的函数了,其他重要的就是有向图的创建和检查是否成为了圆。
三:死锁检测的构成
1、有向图的重要函数
1.1、结点的搜索
int search_vertex(struct source_type type) {
int i = 0;
for (i = 0;i < tg->num;i ++) {
if (tg->list[i].s.type == type.type && tg->list[i].s.id == type.id) {
return i;//如果存在则返回
}
}
return -1;
}
1.2、结点的添加
void add_vertex(struct source_type type) {
if (search_vertex(type) == -1) {
tg->list[tg->num].s = type;
tg->list[tg->num].next = NULL;
tg->num ++;
}
}
1.3、边的添加
int add_edge(struct source_type from, struct source_type to) {
add_vertex(from);
add_vertex(to);
struct vertex *v = &(tg->list[search_vertex(from)]);
while (v->next != NULL) {
v = v->next;
}
v->next = create_vertex(to);
}
1.4、圆圈的搜索
int search_for_cycle(int idx) {
struct vertex *ver = &tg->list[idx];
visited[idx] = 1;
k = 0;
path[k++] = idx;
while (ver->next != NULL) {
int i = 0;
for (i = 0;i < tg->num;i ++) {
if (i == idx) continue;
visited[i] = 0;
}
for (i = 1;i <= MAX;i ++) {
path[i] = -1;
}
k = 1;
DFS(search_vertex(ver->next->s));//这里是深度优先遍历
ver = ver->next;
}
}
2、关于锁的函数
对于锁的一些操作,有单独的结构体(线程id、锁的id)来存储,和有向图是没关系的。
2.1、锁的查找
pthread_t search_rela_table(pthread_mutex_t *mtx) {
int i = 0;
for (i = 0;i < MAX;i ++) {
if (mtx == rela_table[i].mtx) {
return rela_table[i].thid;
}
}
return 0;
}
2.2、锁的添加和删除
int del_rela_table(pthread_mutex_t *mtx, pthread_t tid) {
int i = 0;
for (i = 0;i < MAX;i ++) {
if ((mtx == rela_table[i].mtx) && (tid == rela_table[i].thid)) {
rela_table[i].mtx = NULL;
rela_table[i].thid = 0;
return 0;
}
}
return -1;
}
int add_rela_table(pthread_mutex_t *mtx, pthread_t tid) {
int i = 0;
for (i = 0;i < MAX;i ++) {
if ((rela_table[i].mtx == NULL) && (rela_table[i].thid == 0)) {
rela_table[i].mtx = mtx;
rela_table[i].thid = tid;
return 0;
}
}
return -1;
}
2.3、加锁之前的操作
void before_lock(pthread_t tid, pthread_mutex_t *mtx) {
pthread_t otherid = search_rela_table(mtx);//先通过上面的搜索函数,进行搜索,当发现这把锁已经被别人使用,那么就向有向图中添加一条边指向这个锁的线程id,表示我想拥有这把锁
if (otherid != 0) {
struct source_type from;
from.id = tid;
from.type = PROCESS;
struct source_type to;
to.id = otherid;
to.type = PROCESS;
add_edge(from, to); //添加边,从我到你
}
}
2.4、加锁之后的操作
void after_lock(pthread_t tid, pthread_mutex_t *mtx) {
pthread_t otherid = search_rela_table(mtx);
if (otherid != 0) {
struct source_type from;
from.id = tid;
from.type = PROCESS;
struct source_type to;
to.id = otherid;
to.type = PROCESS;
//加锁后,表示我想拥有的已经拥有了,那么就可以把我之前添加的内一条边进行删除了
//先从上面搜索一下,然后验证是否有这条边,如果有就进行删除。
if (verify_edge(from, to)) {
remove_edge(from, to);
}
}
add_rela_table(mtx, tid); //已经加锁了,那就从加锁的表中添加
}
2.5、解锁之后的操作
void after_unlock(pthread_t tid, pthread_mutex_t *mtx) {
del_rela_table(mtx, tid); //解锁后从表中删除数据
}
2.6、加锁、解锁的函数重载
int pthread_mutex_lock(pthread_mutex_t *mtx) {
printf("before pthread_mutex_lock %ld, %p\n", pthread_self(), mtx);
before_lock(pthread_self(),mtx);
pthread_mutex_lock_f(mtx); //通过函数指针,执行系统的加锁操作。
printf("after pthread_mutex_lock\n");
after_lock(pthread_self(),mtx);
}
int pthread_mutex_unlock(pthread_mutex_t *mtx) {
pthread_mutex_unlock_f(mtx);//通过函数指针,执行系统的解锁操作。
printf("after pthread_mutex_unlock %ld\n, %p", pthread_self(), mtx);
after_unlock(pthread_self(),mtx);
}
然后使用最开始举的例子,就可以检测是否产生死锁了,当然可以单独起一个线程,每隔几秒就检查是否死锁。感谢大家的观看!https://xxetb.xetslk.com/s/2D96kH