线程学习(四):线程的同步

线程的同步

在线程中如果有一个资源他是有限的,比如超市里的鸡腿,卖光了就没了,必须要进货才能买,但是进货的途中你不能拿,中途拿了那不叫买,叫抢劫。

所以在进货途中需要有人保护啊,这个时候互斥锁又来当保镖了,我进货的时候都老实点,全都不许动,要买等着,等我进了货你们再买,没了就等下一批鸡腿。

上面那个例子就是线程同步,消费者只能等生产者生产了才能消费,必须要有一个时序性。加入消费者凭空买了一个鸡腿,鸡腿数量变-1了,然后生产者生产了一个鸡腿把数量变成0,这就不合理了,所以时序性是必须的。

条件变量

条件变量能够实现线程同步,主要是管理以下的情况:

  • 当某一个线程拿到资源访问的权限的时候,发现他虽然进来了,但是里面没有资源,在其他线程进行操作之前,他什么也做不了,所以只能归还资源访问权限,暂时挂起等待。
  • 许多线程都拿到这个资源访问权限了,他们都和第一点中的线程一样,全都等待,等待也有先后顺序,需要一个队列,也由条件变量维护。

条件变量配合互斥锁

条件变量需要互斥锁配合使用,为什么需要配合互斥锁?说白了就是一句话,保证临界资源的安全性。

在最开始的卖鸡腿的例子里,互斥锁担任的是保镖的任务,其实就是这么个道理,在进货的时候不能被其他人获取资源,要保证数据的安全性,这和线程互斥的时候道理是一样的,资源被修改的时候需要保证资源的唯一访问性,因此这个工作就交给互斥锁来完成。

同时,这里说一下条件变量的wait函数,wait函数在等待的同时还有一个解锁操作,为什么有这个解锁操作?

因为wait都是需要获取资源的线程调用的,如果没有资源,那么就会陷入等待,等待其他线程生产资源,这个时候如果不把互斥锁释放,就会死锁,所以需要在调用wait时候就给解锁了,但是还有一个问题,为什么不像下面这么做,也可以实现,但是却不这么做?

//这段代码是错误的
pthread_mutex_unlock(&mutex);//先解锁
pthread_cond_wait(&cond);//然后挂起等待

代码的这种思路是错误的,因为等待之后再执行解锁操作这个操作过程不是原子操作,可能在解锁之后,挂起等待之前,资源已经被增加,鸡腿在解锁之后就可以买了,但是这个线程不知道,如果不明白,看下面的例子:
就好像我要买一个鸡腿,结果超市告诉我没有了,于是我离开了货柜,准备抽根烟等补货,结果我点烟的时候补货了(点烟的时候我还没开始等,算是我从解锁状态到等待状态的间隙),然后超市说有货了,我没注意到,然后一直在抽烟一直抽烟,等半天等不到鸡腿。

所以我们需要解锁和挂起等待这个过程是一个原子操作,大佬们把这个挂起等待和解锁封装到一个函数内,做了一些操作让其变成原子操作,方便我们使用,不会出现上面的情况。

条件变量接口

线程同步借助条件变量来实现,下面介绍条件变量的一些接口:

  1. 初始化条件变量
    两种初始方式:一用函数,需要释放,二赋值初始化,不需要释放
//函数初始化
int pthread_cond_init(pthread_cond_t *restrict cond,
              const pthread_condattr_t *restrict attr);
//cond:要初始化的条件变量
//attr:给NULL就行
//返回值:成功 0 失败 errno

//赋值初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
  1. 条件变量的等待
int pthread_cond_destroy(pthread_cond_t *cond);
//cond:需要销毁的条件变量
//返回值:成功 0 失败 errno
  1. 条件变量的等待
    除了普通的阻塞等待还有限时等待,如果超时就停止等待
int pthread_cond_wait(pthread_cond_t *restrict cond,
              pthread_mutex_t *restrict mutex);
//cond:操作的条件变量
//mutex:辅助的互斥锁
//返回值:成功 0 错误 errno
  1. 唤醒等待条件变量的线程
//唤醒所有等待条件变量的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
//唤醒第一个等待的线程
int pthread_cond_signal(pthread_cond_t *cond);
//cond:操作的条件变量
//返回值:成功 0 失败 errno

卖鸡腿代码

github地址

//使用pthread_cond和pthread_mutex实现同步
//生产者生产鸡腿,消费者买鸡腿
//生产者有生产计划,生产完了就不卖了
//消费者没鸡腿买了很伤心
//程序运行结束,干掉消费者和生产者

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>

pthread_mutex_t mutex;
pthread_cond_t cond;
//1. 定义条件变量
//  条件变量的初始化有两种方式
//      1. 定义赋值初始化,不需释放
//      2. 函数接口初始化, 需要释放
//      pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int chicken_leg = 0;
int production_plan = 10;

void* produce()
{
    while (1)
    {
        //给生产者加互斥锁,没鸡腿的时候防止消费者进来买
        pthread_mutex_lock(&mutex);
        chicken_leg++;
        production_plan--;
        printf("Produce chicken leg~~\n");
        if (production_plan == 0)
        {
            //完成生产计划,休息,解锁,别忘了,不然死锁
            printf("All purduction plan complete, have a rest~~\n");
            pthread_mutex_unlock(&mutex);
            //解锁,并告诉生产者生产完毕
            pthread_cond_broadcast(&cond);
            break;
        }
        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond);
        //int pthread_cond_broadcast(pthread_cond_t *cond);
        //  唤醒所有等待在条件变量上的线程
        //int pthread_cond_signal(pthread_cond_t *cond);
        //  唤醒第一个等待在条件变量上的线程
        sleep(2);
    }
    return NULL;
}

void* consume()
{
    //没有鸡腿就要等待
    //int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
    //  pthread_cond_wait就是用来睡眠的
    //  条件变量是和互斥锁搭配使用的
    //  是先对互斥锁做了一个判断是否加锁,如果加锁了就解锁
    //  然后陷入等待*******整个过程是原子操作
    //
    //  要防止的情况就是:假如没有鸡腿,但是消费者又速度比较快,
    //  先拿到锁了,那么生产者将拿不到锁,没法生产将会造成双方卡死
    //  所以如果消费者如果先获取到锁,那么在陷入等待之前需要解锁
    //  而这里的锁的存在是为了保护这个全局的条件的操作是受保护的。(basket)
    while (1)
    {
        //如果消费者进来,先加锁
        pthread_mutex_lock(&mutex);
        if (chicken_leg == 0)
        {
            //没鸡腿卖,那让消费者先等着,并解锁
            pthread_cond_wait(&cond, &mutex);
        }
        printf("Producer buy a chicken leg~~\n");
        chicken_leg--;
        pthread_mutex_unlock(&mutex);
        if (production_plan == 0)
        {
            //鸡腿卖完了,伤心
            printf("No more chciken leg to buy -_-||, sad-----\n");
            break;
        }
        sleep(1);
    }
    return NULL;
}

int main()
{
    pthread_t consumer, producer;
    int ret;
	//初始化条件变量和互斥锁
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);
    ret = pthread_create(&producer, NULL, produce, NULL);
    if (ret < 0)
    {
        perror("Pthread_create failed");
        return -1;
    }
    ret = pthread_create(&consumer, NULL, consume, NULL);
    if (ret < 0)
    {
        perror("Pthread_create failed");
        return -1;
    }
    pthread_join(producer, NULL);
    pthread_join(consumer, NULL);
    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&mutex);
    return 0;
}

执行结果
线程同步

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值