Linux 线程如何实现同步与互斥_在unix中线程应该通过什么机制实现互斥或进步

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

{
//4个线程
pthread_t tid[4];

int i, ret;
//2、互斥变量的初始化一定要放在线程创建之前
pthread\_mutex\_init(&mutex, NULL);
for (i = 0; i < 4; ++i)
{
    //创建4个线程并执行
    ret = pthread\_create(&tid[i], NULL, thr_scalpers, NULL);
    if (ret != 0)
    {
        printf("thread create error");
        return -1;
    }
}

for (i = 0; i < 4; ++i)
{
    pthread\_join(tid[i], NULL);
}
//5、一定要在所以线程不使用互斥变量后在销毁
pthread\_mutex\_destroy(&mutex);

return 0;

}


![在这里插入图片描述](https://img-blog.csdnimg.cn/20210331135656538.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NDQzOTg2,size_16,color_FFFFFF,t_70)  
 我们发现都是一个线程在执行,也就是说互斥并不保证资源分配的合理性,只保证了资源分配的安全性


#### 条件变量


**条件变量实现同步原理**:是去判断当前线程是否满足获取资源的条件,当线程获取条件不满足的时候,调用阻塞接口,使线程阻塞,将pcb挂到等待队列上。等到条件满足的时候通过唤醒接口唤醒等待队列上的阻塞了的线程


条件变量提供了一个pcb等待队列,以及使线程阻塞的接口和唤醒线程的接口


条件的判断是用户进行的操作,判断线程是否满足条件,不满足的时候调用条件变量接口使线程等待


**操作流程**


1. **定义条件变量** `pthread_cond_t cond`
2. **初始化条件变量** `pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr)`  
 定义并初始化:`pthread_cond_t cond = PTHREAD_COND_INITIALIZER`
3. **获取资源条件,不满足时挂起休眠**`pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)`条件变量时搭配互斥变量一起使用的(原因是条件变量本事也是一个临界资源,需要被保护)  
 `int pthread_cond_timedwait(pthread_cond_t *cond, 一直等待添加满足后唤醒pthread_mutex_t *mutex, const struct timespec *abstime)`这个接口是可以设置阻塞超时后可以自行唤醒
4. **唤醒线程** `pthread_cond_signal(pthread_cond_t *cond)`唤醒至少一个线程; `pthread_cond_broadcast(pthread_cond_t *cond)`唤醒所有等待的线程
5. **销毁条件变量** `pthread_cond_destroy(pthread_cond_t *cond)`


我们拿顾客和厨师来模拟实现同步的过程(保证资源利用合理性)  
 顾客(线程A) 厨师(线程B) 碗(资源)  
 顾客操作:  
 1、预定这个碗(**加锁**)  
 2、看碗是否有饭,没有饭就等着,把碗给厨师(访问资源前先判断是否满足访问条件,**解锁**,不满足就**阻塞等待**)  
 3、碗有饭,吃饭(**加锁**,满足访问条件,利用并占有该资源)  
 4、吃完后,奖励自己再来一碗(释放资源,**唤醒**另一个线程)  
 5、把碗还给厨师(**解锁**)


厨师操作:  
 1、把饭放到指定碗中(**加锁**)  
 2、看碗是否有饭,有饭就等着,把碗给顾客(访问资源前先判断是否满足访问条件,**解锁**,不满足就**阻塞等待**)  
 3、碗没有饭,做饭(**加锁**,满足访问条件,利用并占有该资源)  
 4、做完饭后叫顾客吃饭(释放资源,**唤醒**另一个线程)  
 5、把碗给客户(**解锁**)


**代码实现**



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

//默认0表示碗中没有饭
int bowl = 0;

//实现线程间对bowl变量访问的同步操作
pthread_cond_t cond;
//保护bowl变量的访问操作
pthread_mutex_t mutex;

void *thr_cook(void *arg)
{
while (1)
{
//加锁
pthread_mutex_lock(&mutex);
if (bowl != 0)//表示有饭,不满足做饭条件
{
//解锁,并让厨师线程等待,被唤醒后再加锁
//在该接口中解锁和休眠操作是一步完成的,保证操作的原子性
pthread_cond_wait(&cond, &mutex);//3步操作一个接口完成

    }
    bowl = 1;//能够走下来表示没饭,做完后置为1
    printf("i made a bowl of rice\n");
    //唤醒顾客吃饭
    pthread\_cond\_signal(&cond);
    //解锁
    pthread\_mutex\_unlock(&mutex);
}
return NULL;

}
void *thr_customer(void *arg)
{
while (1)
{
//加锁
pthread_mutex_lock(&mutex);
if (bowl != 1)//没有饭,不满足吃饭条件
{
//没有饭,先解锁,后等待
pthread_cond_wait(&cond, &mutex);
}
bowl = 0;//能够走下来表示有饭,吃完后置为0
printf(“i had a bowl of rice, it was delicious\n”);
//唤醒厨师做饭
pthread_cond_signal(&cond);
//解锁
pthread_mutex_unlock(&mutex);
}
return NULL;
}

int main()
{
//厨师线程
pthread_t cook_tid;
//顾客线程
pthread_t customer_tid;

int ret;
pthread\_mutex\_init(&mutex, NULL);
pthread\_cond\_init(&cond, NULL);
ret = pthread\_create(&cook_tid, NULL, thr_cook, NULL);
if (ret != 0)
{
    printf("pthread\_create error\n");
    return -1;
}
ret = pthread\_create(&customer_tid, NULL, thr_customer, NULL);
if (ret != 0)
{
    printf("pthread\_create error\n");
    return -1;
}

//等待厨师和顾客线程
pthread\_join(cook_tid, NULL);
pthread\_join(customer_tid, NULL);       	
pthread\_mutex\_destroy(&mutex);
pthread\_cond\_destroy(&cond);
return 0;

}


运行结果:我们发现通过同步与互斥,就可以实现对临界资源访问的安全性和合理性,不会出现没饭也吃的情况  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210331190944297.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NDQzOTg2,size_16,color_FFFFFF,t_70)  
 但是如果存在多个顾客和多个厨师,会发生什么情况呢?  
 只提供修改主函数中的代码,其他代码和上面一样



int main()
{
//厨师线程
pthread_t cook_tid[4];
//顾客线程
pthread_t customer_tid[4];

int ret, i;
pthread\_mutex\_init(&mutex, NULL);
pthread\_cond\_init(&cond, NULL);
for (i = 0; i < 4; ++i){
    ret = pthread\_create(&cook_tid[i], NULL, thr_cook, NULL);
    if (ret != 0)
    {
        printf("pthread\_create error\n");
        return -1;
    }
}
for (i = 0; i < 4; ++i){
    ret = pthread\_create(&customer_tid[i], NULL, thr_customer, NULL);
    if (ret != 0)
    {
        printf("pthread\_create error\n");
        return -1;
    }
}
pthread\_join(cook_tid[0], NULL);
pthread\_join(customer_tid[0], NULL);
pthread\_mutex\_destroy(&mutex);
pthread\_cond\_destroy(&cond);
return 0;

}


运行结果:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210331191732446.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NDQzOTg2,size_16,color_FFFFFF,t_70)  
 我们发现,当存在多个顾客和多个厨师时,出现了没饭也吃,有饭也做的情况,这肯定不是我们想要的,那原因处在哪呢?


一开始有多个顾客线程,因为没有饭都进行等待,当一个厨师做好饭后,因为调用的是`pthread_cond_signal`接口唤醒顾客,唤醒了至少1个顾客,也就是多个顾客,当有一个顾客加锁成功后,就去吃饭,剩下的顾客线程只能卡在加锁这里。加锁成功吃完饭的顾客线程去唤醒厨师并进行解锁,由于cpu是时间片调用线程的,加锁的不一定时厨师,有可能是卡在加锁那块的顾客线程,当顾客线程加锁后就去吃饭,但是此刻是没有饭的。所以就发生了错误如何避免这个错误呢?在第二步的条件判断应该改为循环判断,这样即使顾客加锁成功被唤醒后发现没饭也不会去吃了,而是看大没饭就继续休眠,有饭再吃


只提供修改两个线程入口函数的代码,其他代码和上面一样



void *thr_cook(void *arg)
{
while (1)
{
//加锁
pthread_mutex_lock(&mutex);
while (bowl != 0)//表示有饭,不满足做饭条件
{
//解锁,并让厨师线程等待,被唤醒后再加锁
//在该接口中解锁和休眠操作是一步完成的,保证操作的原子性
pthread_cond_wait(&cond, &mutex);//3步操作一个接口完成

    }
    bowl = 1;//能够走下来表示没饭,做完后置为1
    printf("i made a bowl of rice\n");
    //唤醒顾客吃饭
    pthread\_cond\_signal(&cond);
    //解锁
    pthread\_mutex\_unlock(&mutex);
}
return NULL;

}
void *thr_customer(void *arg)
{
while (1)
{
//加锁
pthread_mutex_lock(&mutex);
while (bowl != 1)//没有饭,不满足吃饭条件
{
//没有饭,先解锁,后等待
pthread_cond_wait(&cond, &mutex);
}
bowl = 0;//能够走下来表示有饭,吃完后置为0
printf(“i had a bowl of rice, it was delicious\n”);
//唤醒厨师做饭
pthread_cond_signal(&cond);
//解锁
pthread_mutex_unlock(&mutex);
}
return NULL;
}


运行结果:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/2021033119270193.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NDQzOTg2,size_16,color_FFFFFF,t_70)  
 虽然不会出现没饭吃饭,有饭做饭的情况,但是我们发现程序发生了阻塞,不继续往下执行了,这又是什么原因呢?  
 条件变量只有一个,意味着等待队列只有一个,顾客没饭吃就挂到等待队列上,厨师不能做饭也要挂到等待队列上,假设一个厨师线程做完饭后,要去唤醒一个顾客线程去吃饭,但是却唤醒的是厨师线程(顾客线程和厨师线程都在同一个队列),唤醒的厨师线程发现有饭,又重新挂载等待队列中,从而导致程序阻塞如何避免这种情况呢?不同角色的线程应该在不同的等待队列上进行等待,这样等唤醒的时候,就不会唤醒同类型的线程了,因此存在多个角色线程,就应该设置多个条件变量(一个条件变量对应一个等待队列)


完整正确代码:



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

//默认0表示碗中没有饭
int bowl = 0;

//实现线程间对bowl变量访问的同步操作
pthread_cond_t cook_cond;
pthread_cond_t customer_cond;
//保护bowl变量的访问操作
pthread_mutex_t mutex;

void *thr_cook(void *arg)
{
while (1)
{
//加锁
pthread_mutex_lock(&mutex);
while (bowl != 0)//表示有饭,不满足做饭条件
{
//解锁,并让厨师线程等待,被唤醒后再加锁
//在该接口中解锁和休眠操作是一步完成的,保证操作的原子性
pthread_cond_wait(&cook_cond, &mutex);//3步操作一个接口完成

    }
    bowl = 1;//能够走下来表示没饭,做完后置为1
    printf("i made a bowl of rice\n");
    //唤醒顾客吃饭
    pthread\_cond\_signal(&customer_cond);
    //解锁
    pthread\_mutex\_unlock(&mutex);
}
return NULL;

}
void *thr_customer(void *arg)
{
while (1)
{
//加锁
pthread_mutex_lock(&mutex);
while (bowl != 1)//没有饭,不满足吃饭条件
{
//没有饭,先解锁,后等待
pthread_cond_wait(&customer_cond, &mutex);
}
bowl = 0;//能够走下来表示有饭,吃完后置为0
printf(“i had a bowl of rice, it was delicious\n”);
//唤醒厨师做饭
pthread_cond_signal(&cook_cond);
//解锁
pthread_mutex_unlock(&mutex);
}
return NULL;
}

int main()
{
//厨师线程
pthread_t cook_tid[4];
//顾客线程
pthread_t customer_tid[4];

int ret, i;
pthread\_mutex\_init(&mutex, NULL);
pthread\_cond\_init(&cook_cond, NULL);
pthread\_cond\_init(&customer_cond, NULL);
for (i = 0; i < 4; ++i){
    ret = pthread\_create(&cook_tid[i], NULL, thr_cook, NULL);
    if (ret != 0)
    {
        printf("pthread\_create error\n");
        return -1;
    }
}
for (i = 0; i < 4; ++i){
    ret = pthread\_create(&customer_tid[i], NULL, thr_customer, NULL);
    if (ret != 0)
    {
        printf("pthread\_create error\n");
        return -1;
    }
}
pthread\_join(cook_tid[0], NULL);
pthread\_join(customer_tid[0], NULL);
pthread\_mutex\_destroy(&mutex);
pthread\_cond\_destroy(&cook_cond);
pthread\_cond\_destroy(&customer_cond);
return 0;

}


运行结果:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210331194124613.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NDQzOTg2,size_16,color_FFFFFF,t_70)  
 使用注意事项总结:  
 1、条件变量需要搭配互斥锁一起使用  
 2、在每一个有可能退出线程的地方都需要解锁  
 3、条件变量使用时对条件的判断应该使用while循环来判断  
 4、多种角色线程应该使用多个条件变量


练习:[生产者与消费者模型](https://bbs.csdn.net/topics/618668825)


![img](https://img-blog.csdnimg.cn/img_convert/815b59f6a7b525bd5f42eac1acde9aca.png)
![img](https://img-blog.csdnimg.cn/img_convert/cb0019585f0c541b2601250ce789f926.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**

使用时对条件的判断应该使用while循环来判断  
 4、多种角色线程应该使用多个条件变量


练习:[生产者与消费者模型](https://bbs.csdn.net/topics/618668825)


[外链图片转存中...(img-6qvz6UfN-1715766438024)]
[外链图片转存中...(img-9lP8qj4B-1715766438024)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值