《Linux从练气到飞升》No.28 Linux中的线程同步

news2024/11/17 0:48:32

🕺作者: 主页

我的专栏
C语言从0到1
探秘C++
数据结构从0到1
探秘Linux
菜鸟刷题集

😘欢迎关注:👍点赞🙌收藏✍️留言

🏇码字不易,你的👍点赞🙌收藏❤️关注对我真的很重要,有问题可在评论区提出,感谢阅读!!!

文章目录

    • 前言
    • 1 相关概念
      • 1.1 条件变量
      • 1.2 同步概念与竞态条件
      • 1.3 条件变量函数
    • 2 实际应用(见见猪跑
      • 2.1 模拟加锁未加条件变量(小迷给小芒煮饭且只有一个碗
      • 2.2 模拟加锁且加上条件变量
      • 2.3 模拟加锁且加条件变量(小迷给多个人做饭 只有一个碗
    • 3 条件变量关于等待接口的几个问题
      • 3.1 条件变量对的等待接口参数为什么需要互斥锁?
      • 3.2 pthread_cond_wait函数的实现原理
      • 3.3 线程等待的时候,被唤醒之后需要做什么事?

前言

当谈到多线程编程时,线程同步是一个至关重要的话题。在多线程环境中,我们需要确保不同线程之间的数据访问和操作能够正确、有序地进行,以避免出现竞争条件和数据不一致的情况。因此,线程同步成为了保障多线程程序正确性和可靠性的重要手段。

在本篇博客中,我将深入探讨线程同步的概念、原理和常用的同步机制,帮助读者更好地理解多线程编程中的挑战和解决方案。无论是初学者还是有一定经验的开发人员,都可以通过本文获得对线程同步的全面了解,并学习如何在实际项目中应用这些技术来确保多线程程序的稳定性和性能。

让我们一起深入研究线程同步,探索其中的奥秘,为多线程编程的世界增添一抹精彩的色彩。

1 相关概念

1.1 条件变量

  • 当一个线程互斥的访问某个变量时,它可能发现在其他线程改变状态之前,它什么也做不了
  • 例如一个线程访问队列时,发现队列为空,它只能等待,只到其他线程将一个节点添加到队列中,这种情况就需要用到条件变量

1.2 同步概念与竞态条件

  • 同步:在保证数据安全的前提下,让线程能够按照某种特定顺序访问临界资源,从而有效避免饥饿问题,这叫做同步。
  • 竞态条件:因为时序问题,而导致程序异常。我们称之为竞态条件。在线程场景下,这种问题也不难理解

1.3 条件变量函数

  1. 初始化
动态初始化:
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrictattr);🔄  ❓
参数:
	cond:要初始化的条件变量
	attr:NULL
静态初始化:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
  1. 销毁
int pthread_cond_destroy(pthread_cond_t *cond)
  1. 等待条件满足
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
	cond:要在这个条件变量上等待
	mutex:互斥量,之前的博客解释过
作用:谁调用该接口,就将谁放入PCB等待队列中
  1. 唤醒等待
唤醒PCB等待队列当中的所有线程:
	int pthread_cond_broadcast(pthread_cond_t *cond);
唤醒PCB等待队列当中至少一个线程:
	int pthread_cond_signal(pthread_cond_t *cond);

2 实际应用(见见猪跑

2.1 模拟加锁未加条件变量(小迷给小芒煮饭且只有一个碗

代码如下:

#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;

int g_bowl=1;
pthread_mutex_t g_lock;

void* Eat(void* arg){
    (void)arg;

    while(1){
        pthread_mutex_lock(&g_lock);
        g_bowl--;
        cout<<"I am "<<pthread_self()<<"I eat "<<g_bowl<<endl;
        pthread_mutex_unlock(&g_lock);
    }
    return NULL;
}

void* MakeRice(void* arg){
    (void)arg;
    while(1){
        pthread_mutex_lock(&g_lock);
        g_bowl++;
        cout<<"I am "<<pthread_self()<<"I make "<<g_bowl<<endl;
        pthread_mutex_unlock(&g_lock);
    }
    return NULL;
}

int main(){
    pthread_mutex_init(&g_lock,NULL);
    pthread_t tid_eat;
    pthread_t tid_make;
    int ret = pthread_create(&tid_eat,NULL,Eat,NULL);
    if(ret<0){
        cout<<"thread create failed"<<endl;
    }

    ret = pthread_create(&tid_make,NULL,MakeRice,NULL);
    if(ret < 0){
        cout<<"thread create failed"<<endl;
    }

    while(1){
        sleep(1);
    }

    pthread_mutex_destroy(&g_lock);
    return 0;
}

结果:
image.png
可以观察到bowl已经减为负数,这是因为小芒负责吃,当小芒拿到CPU的资源时,即使碗里面没有饭,它还是持续吃饭,最后居然出现了没有饭还能吃饭的情况,这显然是不合理的所以需要一个条件变量来控制能否吃,以及能否做

2.2 模拟加锁且加上条件变量

给小迷加上条件变量,bowl 里面有饭就不做饭,给小芒加上条件变量,bowl 没有饭就不吃饭。
代码如下:

#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;

int g_bowl=1;
pthread_mutex_t g_lock;

pthread_cond_t g_eat_cond;
pthread_cond_t g_make_cond;


void* Eat(void* arg){
    (void)arg;

    while(1){
        pthread_mutex_lock(&g_lock);
        if(g_bowl<=0){
            pthread_cond_wait(&g_eat_cond,&g_lock);//等待小迷做好饭
        }
        g_bowl--;
        cout<<"I am "<<pthread_self()<<"I eat "<<g_bowl<<endl;
        pthread_mutex_unlock(&g_lock);

        pthread_cond_signal(&g_make_cond);//通知小迷做饭
    }
    return NULL;
}

void* MakeRice(void* arg){
    (void)arg;
    while(1){
        pthread_mutex_lock(&g_lock);
        if(g_bowl>=1){
            pthread_cond_wait(&g_make_cond,&g_lock);//等待小芒吃饭 空出碗
        }
        g_bowl++;
        cout<<"I am "<<pthread_self()<<"I make "<<g_bowl<<endl;
        pthread_mutex_unlock(&g_lock);

        pthread_cond_signal(&g_eat_cond);//通知小芒吃饭
    }
    return NULL;
}

int main(){
    pthread_mutex_init(&g_lock,NULL);

    pthread_cond_init(&g_eat_cond,NULL);
    pthread_cond_init(&g_make_cond,NULL);

    pthread_t tid_eat;
    pthread_t tid_make;
    int ret = pthread_create(&tid_eat,NULL,Eat,NULL);
    if(ret<0){
        cout<<"thread create failed"<<endl;
    }

    ret = pthread_create(&tid_make,NULL,MakeRice,NULL);
    if(ret < 0){
        cout<<"thread create failed"<<endl;
    }

    while(1){
        sleep(1);
    }

    pthread_mutex_destroy(&g_lock);

    pthread_cond_destroy(&g_eat_cond);
    pthread_cond_destroy(&g_make_cond);
    return 0;
}

结果如下:
image.png

2.3 模拟加锁且加条件变量(小迷给多个人做饭 只有一个碗

#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;

int g_bowl=1;
pthread_mutex_t g_lock;

pthread_cond_t g_eat_cond;
pthread_cond_t g_make_cond;


void* Eat(void* arg){
    (void)arg;

    while(1){
        pthread_mutex_lock(&g_lock);
        if(g_bowl<=0){
            pthread_cond_wait(&g_eat_cond,&g_lock);
        }
        g_bowl--;
        cout<<"I am "<<pthread_self()<<"I eat "<<g_bowl<<endl;
        pthread_mutex_unlock(&g_lock);

        pthread_cond_signal(&g_make_cond);
    }
    return NULL;
}

void* MakeRice(void* arg){
    (void)arg;
    while(1){
        pthread_mutex_lock(&g_lock);
        if(g_bowl>0){
            pthread_cond_wait(&g_make_cond,&g_lock);
        }
        g_bowl++;
        cout<<"I am "<<pthread_self()<<"I make "<<g_bowl<<endl;
        pthread_mutex_unlock(&g_lock);

        pthread_cond_signal(&g_eat_cond);
    }
    return NULL;
}

int main(){
    pthread_mutex_init(&g_lock,NULL);

    pthread_cond_init(&g_eat_cond,NULL);
    pthread_cond_init(&g_make_cond,NULL);

    pthread_t tid_make;
    int ret = pthread_create(&tid_make,NULL,MakeRice,NULL);
    if(ret < 0){
        cout<<"thread create failed"<<endl;
    }

    for(int i=0;i<3;++i){
        pthread_t tid_eat;
        int ret = pthread_create(&tid_eat,NULL,Eat,NULL);
        if(ret<0){
            cout<<"thread create failed"<<endl;
        }
    }

    while(1){
        sleep(1);
    }

    pthread_mutex_destroy(&g_lock);

    pthread_cond_destroy(&g_eat_cond);
    pthread_cond_destroy(&g_make_cond);
    return 0;
}

结果:
image.png
可以看到出现了负数的情况,这是为什么?
这是因为我们是使用if语句来判断条件的,可能线程刚好在这个时候进行了切换,导致多个eat线程拿到了锁,从而发生了这样的现象,想要解决这个问题只需要改为while语句即可

#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;

int g_bowl=1;
pthread_mutex_t g_lock;

pthread_cond_t g_eat_cond;
pthread_cond_t g_make_cond;


void* Eat(void* arg){
    (void)arg;

    while(1){
        pthread_mutex_lock(&g_lock);
        while(g_bowl<=0){
            pthread_cond_wait(&g_eat_cond,&g_lock);
        }
        g_bowl--;
        cout<<"I am "<<pthread_self()<<" I eat "<<g_bowl<<endl;
        pthread_mutex_unlock(&g_lock);

        pthread_cond_signal(&g_make_cond);
    }
    return NULL;
}

void* MakeRice(void* arg){
    (void)arg;
    while(1){
        pthread_mutex_lock(&g_lock);
        while(g_bowl>0){
            pthread_cond_wait(&g_make_cond,&g_lock);
        }
        g_bowl++;
        cout<<"I am "<<pthread_self()<<" I make "<<g_bowl<<endl;
        pthread_mutex_unlock(&g_lock);

        pthread_cond_signal(&g_eat_cond);
    }
    return NULL;
}

int main(){
    pthread_mutex_init(&g_lock,NULL);

    pthread_cond_init(&g_eat_cond,NULL);
    pthread_cond_init(&g_make_cond,NULL);

    pthread_t tid_make;
    int ret = pthread_create(&tid_make,NULL,MakeRice,NULL);
    if(ret < 0){
        cout<<"thread create failed"<<endl;
    }

    for(int i=0;i<3;++i){
        pthread_t tid_eat;
        int ret = pthread_create(&tid_eat,NULL,Eat,NULL);
        if(ret<0){
            cout<<"thread create failed"<<endl;
        }
    }

    while(1){
        sleep(1);
    }

    pthread_mutex_destroy(&g_lock);

    pthread_cond_destroy(&g_eat_cond);
    pthread_cond_destroy(&g_make_cond);
    return 0;
}

结果:
image.png

3 条件变量关于等待接口的几个问题

3.1 条件变量对的等待接口参数为什么需要互斥锁?

pthread_cond_wait函数的内部,需要释放互斥锁。释放之后,其他线程就可以正常加锁操作了。
eg:就像之前小芒发现碗里面没有饭,则需要将自己放到PCB等待队列中,调用了pthread_cond_wait函数之后,需要将拿到互斥锁释放掉,小迷就可以访问到碗这个临界资源开始做饭。

3.2 pthread_cond_wait函数的实现原理

pthread_cond_wait函数内部,是先释放互斥锁,还是先将PCB放到等待队列中呢?
假设先释放互斥锁,此时可能做饭的小迷就已经将饭做好了,但是小芒还没有到等待队列中,小迷通知小芒吃饭,但是发现等待队列中为空,但是同时发现碗里面有饭,它就会将自己放入等待队列中等待,此时小芒也才将自己放入等待队列中,那么此时小迷和小芒就都在等待队列中进行等待,所以不能先释放互斥锁。

3.3 线程等待的时候,被唤醒之后需要做什么事?

  1. 移动出PCB等待队列
  2. 抢互斥锁
    1. 抢到了:pthread_cond_wait函数返回了
    2. 没抢到:pthread_cond_wait函数没有返回,等待抢锁

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1211571.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mfc140u.dll丢失怎么修复?4种亲测有效的方法分享

在计算机使用过程中&#xff0c;我们可能会遇到各种各样的问题&#xff0c;其中之一就是某些重要的dll文件丢失。DLL文件是动态链接库文件&#xff0c;它们包含了许多程序运行所需的函数和资源。当这些文件丢失或损坏时&#xff0c;可能会导致程序无法正常运行。本文将详细介绍…

【Python】operator模块

Python中operator模块提供了一套与 Python 的内置运算符对应的高效率函数。 不仅对应内置运算符&#xff0c;还可以获取方法。可优化涉及回调函数的运算性能&#xff0c;比lambda、Python函数的开销小、速度快。 import operator[x for x in dir(operator) if not x.startswi…

ucrtbase.dll缺失的解决方法,全面分析ucrtbase.dll丢失是什么原因

ucrtbase.dll是Windows操作系统中的一个动态链接库文件&#xff0c;它包含了许多常用的函数和资源&#xff0c;用于支持应用程序的正常运行。当ucrtbase.dll丢失时&#xff0c;可能会导致以下问题&#xff1a; 1. 程序无法启动&#xff1a;当应用程序需要调用ucrtbase.dll中的…

局域网监控软件如何防止数据泄密

局域网监控软件在防止数据泄密方面扮演着重要的角色。以下是一些电脑监控软件可以采取的措施&#xff1a; 1、审计聊天内容&#xff1a;一些电脑监控软件可以审计通过聊天工具外发的所有内容&#xff0c;包括文字、图片、文件和视频等。这可以帮助企业及时发现和防止敏感数据的…

骨传导蓝牙耳机哪款好?这五款骨传导耳机闭眼入都不会错!

随着科技的发展&#xff0c;数码产品更新换代的速度也是越来越快&#xff0c;如今无线蓝牙耳机已经占据主流&#xff0c;特别是运动爱好者&#xff0c;很多人都会为自己挑选一款好用的运动耳机&#xff0c;而骨传导耳机异军突起&#xff0c;凭借听歌不入耳、佩戴舒适稳固等特性…

《Linux从练气到飞升》No.27 Linux中的线程互斥

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的…

springBoot 入门一 :创建springBoot项目

创建springBoot项目 配置maven 项目报错处理

MyBatis配置与映射文件深度解析

文章目录 MyBatis配置文件解析配置文件的组成部分配置数据源和数据库连接信息MyBatis的属性和类型别名 MyBatis映射文件详解映射文件的作用编写简单的映射文件resultMap和resultType的区别 结语 &#x1f388;个人主页&#xff1a;程序员 小侯 &#x1f390;CSDN新晋作者 &…

unity UGUI无限循环滚动居中

最近在做一个ui循环滚动的功能&#xff0c;网上找了半天脚本感觉都和我实际需求不太符合&#xff0c;自己花费一些时间完成了这个功能记录一下。下面开始正题 &#xff0c;我是采用unity自带组件Scroll View来完成&#xff0c;首先设置Scroll View如下图 面板层级结构如下 然…

zookeeper的安装部署

目录 简介 Zookeeper架构设计及原理 1.Zookeeper定义 2.Zookeeper的特点 3.Zookeeper的基本架构 4.Zookeeper的工作原理 5.Zookeeper的数据模型 &#xff08;1&#xff09;临时节点 &#xff08;2&#xff09;顺序节点 &#xff08;3&#xff09;观察机制 Zookeeper集…

ICC2/innovus merge gds

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口 calibre merge gds的方法示例参考往期文章: Calibre Merge GDS ICC2: write_gds -merge_files "std.gds sram.gds io.gds ip.gds ... ..." innovus: streamout -…

Postman接口Mock Servier服务器

近期在复习Postman的基础知识&#xff0c;在小破站上跟着百里老师系统复习了一遍&#xff0c;也做了一些笔记&#xff0c;希望可以给大家一点点启发。 应用场景&#xff1a;后端的接口还没有开发完成&#xff0c;前端的业务需要调用后端的接口&#xff0c;可以使用mock模拟。 一…

开发板上网详细教程

开发板上网详细教程 PC端操作开发板操作 写在前面 今天想配置开发板的boa服务器&#xff0c;需要下载sudo apt-get install bison flex&#xff0c;但是一直报错&#xff0c;就蒙蔽了&#xff0c;后来想想真不应该啊&#xff0c;电脑和开发板通信没问题&#xff0c;但也只是如此…

VueEcharts的使用简解以及常用网站

目录 一&#xff1a;前言 二&#xff1a;实现 1、安装echarts依赖 2、创建图表 1&#xff09;全局引入 2&#xff09;按需引入 三&#xff1a;结尾 一&#xff1a;前言 VueEcharts 是项目开发中可视化的一个重要知识部分。其涵盖了柱状图&#xff0c;饼状图&#xff0c;…

LoadRunner脚本编写之三(事务函数)

关于脚本的这块&#xff0c;前两篇都在讲C语言&#xff0c;其实&#xff0c;要整理点实用的东西挺难&#xff0c;在应用中多对录制的脚本分析&#xff0c;但对于新手学脚本确实无从下手。 先贴一个脚本&#xff1a; 完整代码&#xff1a; 重点代码部分&#xff1a; Action(…

Redis快速入门(基础篇)

简介&#xff1a; 是一个高性能的 key-value数据库。 存在内存中 与其他 key-value 缓存产品有以下三个特点&#xff1a; Redis支持数据的持久化&#xff0c;可以将内存中的数据保持在磁盘中&#xff0c;重启的时候可以再次加载进行使用。 Redis不仅仅支持简单的key-value类…

浅了解下:运营商大数据如何挖掘电销同行网站,APP,精准获客 ?

今天我们要讲的是运营商精准大数据营销。运营商精准大数据营销只是精准营销的一种&#xff0c;精准营销筛选包含了电话营销这个词。那么电话营销如何通过运营商大数据找到精准的客户&#xff1f;电销如何通过大数据找到准确的客户来源&#xff1f; 在全网时代&#xff0c;大数…

TYUST-RM2023-NewMaker哨兵视觉代码

目录 1 TYUST-RM2023-NewMaker哨兵视觉代码 1.1 说明 1.2 算法设计 1.3 识别思路 TYUST-RM2023-NewMaker哨兵视觉代码 说明 本套代码是太原科技大学NewMaker战队2023赛季哨兵开源代码 本套代码主要含有&#xff1a;TYUST-RM2023赛季哨兵视觉代码&#xff0c;主要模块…

科研学习|科研软件——SPSS:卡方检验(交叉表)

第一步 打开SPSS软件&#xff0c;在工具栏中选中【打开-文件-数据】&#xff0c;然后选择一份要打开的数据表(如图所示)。 第二步 在工具栏中找到【分析-描述统计-交叉表】打开交叉表对话框(如图所示)。 第三步 接着将【行-列】相关变量放在对应对话框中(如图所示)。 第四步 在…

ubuntu 20.04+ORB_SLAM3 安装配库教程

目录 安装ros(如果只是运行ORB-SLAM3&#xff0c;可以跳过安装)0. ros 安装教程1. 安装opencv2. 安装Pangolin3. 安装Eigen34.安装Python & libssl-dev5.安装boost库6.安装ceres库&#xff08;不必须&#xff09;7.安装Sophus库&#xff08;不必须&#xff09;8. 安装g20库…