多线程编程中关于“死锁”的相关概念问题

死锁

在这里插入图片描述

死锁的概念

  在多线程编程中,我们为了防止多线程竞争共享资源而导致数据错乱,都会在操作共享资源之前加上互斥锁,只有成功获得到锁的线程,才能操作共享资源,获取不到锁的线程就只能等待,直到锁被释放。

   那么,当两个线程为了保护两个不同的共享资源而使用了两个互斥锁,那么这两个互斥锁应用不当的时候,可能会造成两个线程都在等待对方释放锁,在没有外力的作用下,这些线程会一直相互等待,就没办法继续运行,这种情况就是发生了死锁

死锁只有同时满足以下四个条件才会发生:

  • 互斥条件;
  • 持有并等待条件;
  • 不可剥夺条件;
  • 环路等待条件;

互斥条件

  互斥条件是指多个线程不能同时使用同一个资源

  比如下图,如果线程 A 已经持有的资源,不能再同时被线程 B 持有,如果线程 B 请求获取线程 A 已经占用的资源,那线程 B 只能等待,直到线程 A 释放了资源。

在这里插入图片描述

持有并等待条件

  持有并等待条件是指,当线程 A 已经持有了资源 1,又想申请资源 2,而资源 2 已经被线程 C 持有了,所以线程 A 就会处于等待状态,但是线程 A 在等待资源 2 的同时并不会释放自己已经持有的资源 1
在这里插入图片描述

不可剥夺条件

  不可剥夺条件是指,当线程已经持有了资源 ,在自己使用完之前不能被其他线程获取,线程 B 如果也想使用此资源,则只能在线程 A 使用完并释放后才能获取。

在这里插入图片描述

环路等待条件

​ 环路等待条件指的是,在死锁发生的时候,两个线程获取资源的顺序构成了环形链

​ 比如,线程 A 已经持有资源 2,而想请求资源 1, 线程 B 已经获取了资源 1,而想请求资源 2,这就形成资源请求等待的环形图。

在这里插入图片描述

  

  
  

死锁、饥饿、死循环的异同

  • 死锁

  至少有两个或两个以上的进程同时发生死锁。另外,发生死锁的进程一定处于阻塞态。

  因为死锁一定是“循环等待对方手里的资源”导致的。

  • 饥饿

  可能只有一个进程发生饥饿。发生饥饿的进程既可能是阻塞态(如长期得不到需要的I/0设备),也可能是就绪态。(长期得不到处理机)

  比如:在短进程优先(SPF)算法中,若有源源不断的短进程到来,则长进程将一直得不到处理机,从而发生长进程“饥饿”。有优先就可能有饥饿

  • 死循环

  可能只有一个进程发生死循环。死循环的进程可以上处理机运行(可以是运行态),只不过无法像期待的那样顺利推进。

  死循环有时是因为程序逻辑bug导致的,有时是程序员故意设计的(如服务器中代码逻辑设置为死循环)。

不同点:

​  死锁饥饿问题是由于操作系统分配资源的策略不合理导致

​  死循环是由代码逻辑(用户)的错误导致的

相同点:

​  都是进程无法顺利向前推进的现象(故意设计的死循环除外)

  
  

模拟死锁问题的产生

  首先,我们先创建 2 个线程,分别为线程 A 和 线程 B,然后有两个互斥锁,分别是 mutex_A 和 mutex_B,代码如下:

/*************************************************************************
        > File Name: test.c
        > Author: luzelin
        > Mail: luzelin1024@163.com
        > Created Time: Wed 12 Oct 2022 01:48:30 PM CST
 ************************************************************************/

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

pthread_mutex_t mutex_A;
pthread_mutex_t mutex_B;

//线程函数 A
void* threadA_proc(void *data) {
    printf("thread A waiting get ResourceA \n");
    pthread_mutex_lock(&mutex_A);
    printf("thread A got ResourceA \n");

    sleep(1);

    printf("thread A waiting get ResourceB \n");
    pthread_mutex_lock(&mutex_B);
    printf("thread A got ResourceB \n");

    pthread_mutex_unlock(&mutex_B);
    pthread_mutex_unlock(&mutex_A);
    return (void *)0;
}

//线程函数 B
void *threadB_proc(void *data) {
    printf("thread B waiting get ResourceB \n");
    pthread_mutex_lock(&mutex_B);
    printf("thread B got ResourceB \n");

    sleep(1);

    printf("thread B waiting  get ResourceA \n");
    pthread_mutex_lock(&mutex_A);
    printf("thread B got ResourceA \n");

    pthread_mutex_unlock(&mutex_A);
    pthread_mutex_unlock(&mutex_B);
    return (void *)0;
}

int main() {
    pthread_t tidA, tidB;

    // create two pthread
    pthread_create(&tidA, NULL, threadA_proc, NULL);
    pthread_create(&tidB, NULL, threadB_proc, NULL);

    pthread_join(tidA, NULL);
    pthread_join(tidB, NULL);

    printf("exit\n");
    return 0;
}

可以看到,线程 A 函数的过程:

  • 先获取互斥锁 A,然后睡眠 1 秒;
  • 再获取互斥锁 B,然后释放互斥锁 B;
  • 最后释放互斥锁 A;

可以看到,线程 B 函数的过程:

  • 先获取互斥锁 B,然后睡眠 1 秒;
  • 再获取互斥锁 A,然后释放互斥锁 A;
  • 最后释放互斥锁 B;

然后,我们运行这个程序,运行结果如下:

thread A waiting get ResourceA
thread A got ResourceA
thread B waiting get ResourceB
thread B got ResourceB
thread A waiting get ResourceB
thread B waiting  get ResourceA
// 阻塞中

  可以看到线程 B 在等待互斥锁 A 的释放,线程 A 在等待互斥锁 B 的释放,双方都在等待对方资源的释放,很明显,产生了死锁问题。
  
  
  

避免死锁问题的发生

  前面我们提到,产生死锁的四个必要条件是:互斥条件、持有并等待条件、不可剥夺条件、环路等待条件。

  那么避免死锁问题就只需要破环其中一个条件就可以,最常见的并且可行的就是使用资源有序分配法,来破环环路等待条件

  那什么是资源有序分配法呢?

  线程 A 和 线程 B 获取资源的顺序要一样,当线程 A 是先尝试获取资源 A,然后尝试获取资源 B 的时候,线程 B 同样也是先尝试获取资源 A,然后尝试获取资源 B。也就是说,线程 A 和 线程 B 总是以相同的顺序申请自己想要的资源。

  我们使用资源有序分配法的方式来修改前面发生死锁的代码,我们可以不改动线程 A 的代码。

  我们先要清楚线程 A 获取资源的顺序,它是先获取互斥锁 A,然后获取互斥锁 B。

  所以我们只需将线程 B 改成以相同顺序的获取资源,就可以打破死锁了。

在这里插入图片描述

线程 B 函数改进后的代码如下:

//线程函数 B
void *threadB_proc(void *data) {
    printf("thread B waiting  get ResourceA \n");
    pthread_mutex_lock(&mutex_A);
    printf("thread B got ResourceA \n");

    sleep(1);

    printf("thread B waiting get ResourceB \n");
    pthread_mutex_lock(&mutex_B);
    printf("thread B got ResourceB \n");

    pthread_mutex_unlock(&mutex_A);
    pthread_mutex_unlock(&mutex_B);
    return (void *)0;
}

执行结果如下,可以看,没有发生死锁。

thread A waiting get ResourceA
thread A got ResourceA
thread B waiting  get ResourceA
thread A waiting get ResourceB
thread A got ResourceB
thread B got ResourceA
thread B waiting get ResourceB
thread B got ResourceB
exit

当然还有其它方法避免死锁,如:

(1)资源一次性分配。从而破坏持有等待条件

(2)当进程新的资源未得到满足时,释放已有的资源 从而破坏不可剥夺条件

  
  

总结

  简单来说,死锁问题的产生是由两个或者以上线程并行执行的时候,争夺资源而互相等待造成的。

  死锁只有同时满足互斥、持有并等待、不可剥夺、环路等待这四个条件的时候才会发生。

  所以要避免死锁问题,就是要破坏其中一个条件即可,最常用的方法就是使用资源有序分配法来破坏环路等待条件。

  
  
  

参考:

小林coding — 图解系统 — 5.4怎么避免死锁

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值