死锁、同步、条件变量

死锁

多个执行流对锁资源进行争抢访问,但是因为推进顺序不当,而导致互相等待。最终造成程序流程无法继续的情况。

举一个日常生活中的例子来说明:
有两个人一起吃饭,只有拿到两只筷子才能吃饭。但是一人只抢到一根筷子,两个人都不愿意放手,都想拿到对方的筷子,结果两个人都吃不了饭。造成死锁。

死锁产生的必要条件:

必须具备的条件,如果不具备就无法造成死锁。知道了必要条件,我们就可以预防以及避免。

  • 1.互斥条件
    一个锁不能大家同时加,一个人加了锁,别人就不能再加了。同一时间只有一个线程能够加锁。
  • 2.不可剥夺条件
    我加的锁,别人不能解锁,只有我能解锁。
  • 3.请求和保持条件
    吃着碗里的,看着锅里的。 线程1抢到了锁A,然后去抢锁B。抢不到锁B,也不愿意释放锁A。
  • 4.环路等待条件
    线程1抢到了锁A,然后去抢锁B,但是抢不到锁B。
    线程2抢到了锁B,然后去抢锁A,但是抢不到锁A。
    在这里插入图片描述

如何预防死锁:在编写代码过程中注意破坏死锁产生的必要条件即可
如何避免产生死锁:死锁检测算法和银行家算法
银行家算法:将系统的运行分成了两种状态:安全/不安全
1.当前都有哪些资源
2.哪些资源已经分配给了谁
3.现在谁都想要,都要获取哪些资源

若给你分配你想要的资源,是否会造成系统处于不安全状态。分配给一个线程想要的锁资源,是否会造成环路等待。
如果有可能造成,当前就是不安全的。分配给一个线程想要的锁资源,是否会造成环路等待。
如果有可能造成,当前就是不安全的,则不能分配。已经加的锁考虑是否释放掉。

加锁的时候采用非阻塞加锁的方式。
破坏请求与保持条件
a.若不能加锁,则将已经加的锁释放掉。
b.要按序加锁。

同步的实现:通过条件变量实现

线程同步是指线程之间具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。

条件变量:实现同步的思路向用户提供两个接口(一个是让线程陷入阻塞休眠的接口。一个是唤醒线程休眠的接口)+pcb等待队列

在这里插入图片描述
同步:通过条件判断(什么时候能访问资源,什么时候不能访问。若不能访问就要使线程阻塞。若能访问就要唤醒线程),实现线程对资源访问的合理性。

条件变量:只是向外提供了等待与唤醒的接口,却没有提供条件判断的功能。(条件变量本身并不具备判断什么时候该等待,什么时候该唤醒)意味着,条件判断需要用户自己来完成。

条件变量提供的接口功能:

  • 1.定义条件变量
    pthread_cond_t cond
  • 2.初始化条件变量
    pthread_coud_init(pthread_cont_t *cond,pthread_condattr_t *attr);
    coud=PTHREAD_COND_INITIALIZER
  • 3.若不能访问,则调用pthread_coud_wait进行等待:
    pthread_coud_wait(pthread_coud_t *cond,pthread_mutex_t *mutex);
    这个变量是搭配互斥锁使用的:条件变量并不提供条件判断的功能,需要用户去判断(通常条件的判断是一个临界资源的访问)。因此这个临界资源的访问,就需要受互斥锁的保护。
    pthread_cond_timedwait(pthread_coud_t *cond,pthread_mutex_t *mutex,struct timespec *abstime);
    限制等待时长的阻塞操作:等待一段指定的时间,时间到了调用就会保存返回–ETIMEDOUT
  • 4.其他线程若是促使条件满足了,通过条件变量唤醒等待的线程:
    pthread_cond_signal(pthread_coud_t *cond);
    pthread_cond_signal(pthread_coud_t *cond);
  • 5.若不使用条件了则销毁释放资源:
    pthread_cond_destroy(pthread_coud_t *cond);

操作流程:用户不能访问资源的时候调用接口陷入等待,其他线程产生资源,然后调用接口唤醒等待队列中的线程。是否能够访问的条件判断需要用户自己完成,并且需要互斥保护。

  • 1.加锁。
  • 2.用户自己进行条件判断,如果不能访问,调用pthread_cond_wait陷入等待。
  • 3.被唤醒之后能够访问,则访问数据,获取资源。
  • 4.唤醒生产资源的线程。
  • 5.解锁。

在这里插入图片描述
注意:

  • pthread_cond_wait中包含了三步操作(解锁+休眠+被唤醒后加锁。解锁和休眠是一个原子操作。)

  • 用户自己进行条件判断需要使用while循环判断。不然会导致唤醒两个顾客:一个吃面,一个卡在锁上。吃完面的,解锁之后,应该厨师做面,但是解锁后谁都可能抢到。因此另一个顾客有可能在没有面的情况下抢到锁吃面。

  • 不同的角色应该等待在不同的条件变量上,做到唤醒分明。厨师唤醒顾客队列。顾客唤醒厨师队列。

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

int bowl = 0; // 初始为0,表示没有面
pthread_mutex_t mutex;
pthread_cond_t consumer_cond;
pthread_cond_t cook_cond;

void *thr_customer(void *arg)
{
    //这是一个顾客的流程
    while(1) {
        //0. 加锁操作
        pthread_mutex_lock(&mutex);
        while (bowl == 0)
         {
           //顾客加锁成功,发现没有饭,则要陷入休眠,如果没解锁就会导致厨师无法做饭
            //pthread_mutex_unlock();
            //如果厨师在这期间做好饭,唤醒就白唤醒了,因为顾客还没睡
            //pause();//休眠等待唤醒
            //pthread_mutex_lock();
            //如果没饭,则要等待,因为已经加过锁了,因此等待之前要解锁,被唤醒之后要加锁
            //因此pthread_cond_wait集合了三步操作:解锁/挂起/加锁
            //解锁和挂起是一个原子操作-不可被打断
            //顾客解锁,还没来得及挂起休眠;这时候厨师进来做饭,做好后唤醒顾客(实际顾客还没休眠)
            //会导致顾客这时候拿到时间片休眠彻底卡死(厨师不会进行第二次唤醒)
            pthread_cond_wait(&consumer_cond, &mutex);
        }
        //能走下来应该表示有饭了,bowl是等于1的
        printf("It's delicious.\n");
        bowl = 0; // 饭被吃完了
        //唤醒厨师,再做一碗
        pthread_cond_signal(&cook_cond);
        //解锁操作
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}
void *thr_cook(void *arg)
{
    //这是一个厨师的流程
    while(1) {
        //0. 加锁操作,因为要对碗进行操作
        pthread_mutex_lock(&mutex);
        while (bowl == 1) {
            //表示有饭,则不能做,陷入等待
            pthread_cond_wait(&cook_cond, &mutex);
        }
        //能走下来表示没饭,则做饭  bowl=0
        printf("Made a bowl of delicious rice\n");
        bowl = 1;//做了一碗饭
        //唤醒顾客
        pthread_cond_signal(&consumer_cond);
        //解锁操作
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}
int main()
{
    pthread_t ctid[2]; 
    int ret, i;

    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&consumer_cond, NULL);
    pthread_cond_init(&cook_cond, NULL);

    for (i = 0; i < 4; i++) {
        ret = pthread_create(&ctid[0], NULL, thr_customer, NULL);
        if (ret != 0) {
            printf("create thread failed!!\n");
            return -1;
        }
    }
    for (i = 0; i < 4; i++) {
        ret = pthread_create(&ctid[1], NULL, thr_cook, NULL);
        if (ret != 0) {
            printf("create thread failed!!\n");
            return -1;
        }
    }

    pthread_join(ctid[0], NULL);
    pthread_join(ctid[1], NULL);

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&consumer_cond);
    pthread_cond_destroy(&cook_cond);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值