您的位置:

死锁必要条件的详细阐述

一、互斥条件

互斥条件指进程对所分配的资源进行排他性使用,即在一段时间内某资源只有一个进程使用。如果一个资源可以同时被多个进程使用,那么死锁就不会发生。


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

pthread_mutex_t forks[5]; //定义五个互斥量

void philosopher(void *arg)
{
    int id = *((int*)arg);
    int left = id;
    int right = (id + 1) % 5;

    while(1) {
        printf("Philosopher %d is thinking.\n", id);
        sleep(rand() % 5);
        printf("Philosopher %d is hungry.\n", id);
        pthread_mutex_lock(&forks[left]); //尝试拿左手边的叉子
        printf("Philosopher %d get the fork %d.\n", id, left);
        pthread_mutex_lock(&forks[right]); //尝试拿右手边的叉子
        printf("Philosopher %d get the fork %d.\n", id, right);
        printf("Philosopher %d is eating.\n", id);
        sleep(rand() % 5);
        pthread_mutex_unlock(&forks[left]); //放下左边的叉子
        pthread_mutex_unlock(&forks[right]); //放下右边的叉子
    }
}

int main()
{
    pthread_t threads[5];
    int ids[5] = {0, 1, 2, 3, 4};

    //初始化互斥量
    for(int i = 0; i < 5; ++i)
        pthread_mutex_init(&forks[i], NULL);

    //创建线程
    for(int i = 0; i < 5; ++i)
        pthread_create(&threads[i], NULL, (void*)philosopher, &ids[i]);

    //等待线程结束
    for(int i = 0; i < 5; ++i)
        pthread_join(threads[i], NULL);

    //销毁互斥量
    for(int i = 0; i < 5; ++i)
        pthread_mutex_destroy(&forks[i]);

    return 0;
}

在上述代码中,每个哲学家都需要使用左右两边的叉子才能进行进餐,而每个叉子只能被一个哲学家使用,因此满足了互斥条件。这种情况下,如果所有哲学家同时拿着自己左手边的叉子等待右手边的叉子,就会发生死锁。

二、请求和保持条件

请求和保持条件指进程在请求资源时保持对已经占有的资源的不释放。如果一个进程在等待另一个进程占有的资源时,自己还占有一些资源,那么请求和保持条件就成立,就有可能会发生死锁。


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

pthread_mutex_t mutex; //定义互斥量
int resource = 1; //初始值为1

void* thread1(void* arg)
{
    while(1) {
        pthread_mutex_lock(&mutex); //申请互斥锁
        printf("Thread1 gets the mutex.\n");
        sleep(rand() % 5);
        if(resource > 0) { //请求资源,但保持已经占用的资源
            resource--;
            printf("Thread1 gets the resource, resource = %d.\n", resource);
        }
        pthread_mutex_unlock(&mutex); //释放互斥锁
        sleep(rand() % 5);
    }
}

void* thread2(void* arg)
{
    while(1) {
        pthread_mutex_lock(&mutex); //申请互斥锁
        printf("Thread2 gets the mutex.\n");
        sleep(rand() % 5);
        if(resource < 5) { //请求资源,但保持已经占用的资源
            resource++;
            printf("Thread2 gets the resource, resource = %d.\n", resource);
        }
        pthread_mutex_unlock(&mutex); //释放互斥锁
        sleep(rand() % 5);
    }
}

int main()
{
    pthread_t thread[2];

    //初始化互斥量
    pthread_mutex_init(&mutex, NULL);

    //创建线程
    pthread_create(&thread[0], NULL, thread1, NULL);
    pthread_create(&thread[1], NULL, thread2, NULL);

    //等待线程结束
    pthread_join(thread[0], NULL);
    pthread_join(thread[1], NULL);

    //销毁互斥量
    pthread_mutex_destroy(&mutex);

    return 0;
}

在上述代码中,两个线程都需要先申请到互斥锁才能进行后续操作。线程1每次在获得互斥锁之后会尝试获取一个资源,但不释放已经占用的资源;线程2每次在获得互斥锁之后会尝试释放一个资源,但保持已经占用的资源。这样当线程1占用了1个资源,线程2占用了4个资源时,就会发生死锁。

三、不剥夺条件

不剥夺条件指进程已经占有的资源不能被剥夺,只能由该进程自己释放。如果一个进程已经占有某些资源,但是在等待另外一些资源的时候,被系统强制剥夺了它所占有的资源,那么就可能会发生死锁。


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

pthread_mutex_t mutex1, mutex2; //定义两个互斥量

void* thread1(void* arg)
{
    pthread_mutex_lock(&mutex1); //先申请锁1
    printf("Thread1 gets mutex1.\n");
    sleep(rand() % 5);
    pthread_mutex_lock(&mutex2); //再申请锁2
    printf("Thread1 gets mutex2.\n");
    pthread_mutex_unlock(&mutex2); //释放锁2
    printf("Thread1 releases mutex2.\n");
    pthread_mutex_unlock(&mutex1); //释放锁1
    printf("Thread1 releases mutex1.\n");
    return NULL;
}

void* thread2(void* arg)
{
    pthread_mutex_lock(&mutex2); //先申请锁2
    printf("Thread2 gets mutex2.\n");
    sleep(rand() % 5);
    pthread_mutex_lock(&mutex1); //再申请锁1
    printf("Thread2 gets mutex1.\n");
    pthread_mutex_unlock(&mutex1); //释放锁1
    printf("Thread2 releases mutex1.\n");
    pthread_mutex_unlock(&mutex2); //释放锁2
    printf("Thread2 releases mutex2.\n");
    return NULL;
}

int main()
{
    pthread_t thread[2];

    //初始化互斥量
    pthread_mutex_init(&mutex1, NULL);
    pthread_mutex_init(&mutex2, NULL);

    //创建线程
    pthread_create(&thread[0], NULL, thread1, NULL);
    pthread_create(&thread[1], NULL, thread2, NULL);

    //等待线程结束
    pthread_join(thread[0], NULL);
    pthread_join(thread[1], NULL);

    //销毁互斥量
    pthread_mutex_destroy(&mutex1);
    pthread_mutex_destroy(&mutex2);

    return 0;
}

在上述代码中,线程1先申请到互斥锁1,再申请到互斥锁2,线程2则相反。如果在程序运行过程中,系统剥夺某一个线程的其中一个锁,那么就会出现死锁。

四、循环等待条件

循环等待条件指进程申请资源时形成了一个头尾相接的循环等待链。这种情况下,每个进程都在等待一个资源而被其他进程占用,从而形成了一个死循环。如果所有进程都按照一个确定的顺序来申请资源,那么循环等待条件就不会发生死锁。


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

pthread_mutex_t mutex[5]; //定义五个互斥量

void* thread(void* arg)
{
    int id = *((int*)arg);
    int prev = (id + 4) % 5; //前面一个线程的id
    int next = (id + 1) % 5; //后面一个线程的id

    //每个线程都先申请前面的互斥锁
    if(id < prev)
        pthread_mutex_lock(&mutex[id]);
    else {
        pthread_mutex_lock(&mutex[prev]);
        pthread_mutex_lock(&mutex[id]);
    }
    printf("Thread %d gets mutex %d.\n", id, prev);
    sleep(rand() % 5);

    //然后申请后面的互斥锁
    if(id < next)
        pthread_mutex_lock(&mutex[id]);
    else {
        pthread_mutex_lock(&mutex[id]);
        pthread_mutex_lock(&mutex[next]);
    }
    printf("Thread %d gets mutex %d.\n", id, id);

    //释放锁
    pthread_mutex_unlock(&mutex[id]);
    printf("Thread %d releases mutex %d.\n", id, id);
    if(id < prev) {
        pthread_mutex_unlock(&mutex[prev]);
        printf("Thread %d releases mutex %d.\n", id, prev);
    }
    else
        pthread_mutex_unlock(&mutex[id-1]);

    return NULL;
}

int main()
{
    pthread_t threads[5];
    int ids[5] = {0, 1, 2, 3, 4};

    //初始化互斥量
    for(int i = 0; i < 5; ++i)
        pthread_mutex_init(&mutex[i], NULL);

    //创建线程
    for(int i = 0; i < 5; ++i)
        pthread_create(&threads[i], NULL, thread, &ids[i]);

    //等待线程结束
    for(int i = 0; i < 5; ++i)
        pthread_join(threads[i], NULL);

    //销毁互斥量
    for(int i = 0; i < 5; ++i)
        pthread_mutex_destroy(&mutex[i]);

    return 0;
}

在上述代码中,五个线程分别持有编号为0、1、2、3、4的互斥锁,并且每个线程都先申请前面一个线程的互斥锁,再申请自己的互斥锁。这样以来,如果五个线程按照一定的顺序来启动,死锁就不会出现。