linux:线程同步

在这里插入图片描述

个人主页 : 个人主页
个人专栏 : 《数据结构》 《C语言》《C++》《Linux》

文章目录

  • 前言
  • 线程同步
    • 条件变量接口
    • 简单示例
      • pthread_cond_wait为什么要有mutex
      • 伪唤醒问题的解决 (if->while)
  • 总结


前言

本文作为我对于线程同步知识总结


线程同步

  • 同步:在保证数据安全的前提下,让线程能够按照某种顺序访问临界资源,从而有效避免饥饿问题,叫做同步
  • 竞态条件:是指多个线程同时访问系统共享资源时,由于其执行顺序或时间上的不确定性,导致数据不一致或其它不可预料的结果(一般发生在对称多处理环境,中断和异常处理,内核态抢占,并发执行…)

看了上面两个概念,你可能还是不太理解同步是什么,为什么要有同步。下面我们就举一个例子。
我们假定有两个人,一个人A将苹果放在桌子上,另一个人B蒙着眼睛去桌子上拿苹果,桌子每次只允许有一个人,因为B不清楚桌子上的情况是什么(有一个苹果,没有苹果,桌子上全是苹果),那为了保险起见,B只能疯狂的去桌子上拿苹果,以保证B拿到所有的苹果。那如果桌子上没有苹果,B仍然疯狂去桌子上拿苹果,此时A是不是就不能去桌子上放苹果,那此时B是不是再做无用工,而且导致了A不能放苹果,B拿苹果的效率降低。如果我们将A,B换成线程,苹果看出共享资源(某种任务),桌子看成临界区,那B是不是就是一直在做申请锁,再释放锁的无效工作,并且导致了A的饥饿问题。这时我们就需要保证A,B之间的顺序问题,如在B访问过桌子后,不能立即再次访问桌子,要等待A访问桌子后。这是不是就会使A,B拿苹果的效率提升。而这就是为什么要有同步的理由

条件变量接口

初始化条件变量

  • 静态初始化在这里插入图片描述
    与互斥锁类似,定义一个全局的条件变量,用PTHREAD_COND_INITIALIZER宏来初始化,系统自动释放该条件变量。该宏一般存放在 /usr/include/pthread.h 路径下
    在这里插入图片描述
  • 动态初始化
    在这里插入图片描述
    restrict是C语言的一个关键字,表示该指针是唯一的访问其指向对象的指针。
    cond将被初始化的条件变量,attr 为nullptr,使用默认属性初始化条件变量。
    如果函数成功执行,返回0; 如果函数执行失败,则返回错误码,如EAGAIN(资源暂时不可用),ENOMEM(内存不足)

销毁条件变量
在这里插入图片描述
cond 表示将被销毁的条件变量,需要注意在调用pthread_cond_destroy后,该指针本身并未被销毁,只是所指向的条件变量被销毁,记得将指针置空
如果函数成功执行,返回0; 如果函数失败,返回错误码。如EBUSY,该条件变量正在被使用


等待条件变量
在这里插入图片描述
cond 表示将要等待的条件变量,mutex 表示线程所持有的互斥锁
如果函数成功执行,返回0;如果函数执行失败,返回错误码,如EINVAL 无效的参数(条件变量 or 互斥锁为初始化…)

关于该函数的参数为什么会有锁,有什么注意事项,在下面代码示例,我们再解释。


唤醒等待

在这里插入图片描述
cond要发生信号的条件变量指针。
如果函数执行成功,返回0; 如果函数执行失败,返回错误码,如ENVAL(无效的参数)
需要注意的是,pthread_cond_signal只用于唤醒在cond条件变量的阻塞队列中等待的一个线程。

在这里插入图片描述
该函数的参数与返回值与pthread_cond_signal相同,只不过该函数唤醒所有在cond条件变量的阻塞队列中等待的所有线程,而那些线程先执行,取决于操作系统的调度策略。


简单示例

我们先来看看下面代码,3个线程争夺ticket资源。当三个线程检测到ticket == 0时,三个线程都将等待。我们主线程每过5秒,使ticket += 10。

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>using namespace std;pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int ticket = 100;void* threadRoutine(void *args)
{const string threadname = static_cast<char*>(args);while(true){   usleep(1000);pthread_mutex_lock(&mutex);if(ticket > 0){ticket--;cout << threadname << ", get a ticket: " << ticket << endl;}else{cout << threadname << ", ticket == 0" << endl;pthread_cond_wait(&cond, &mutex);}pthread_mutex_unlock(&mutex);}return nullptr;
}int main()
{pthread_t td1;pthread_create(&td1, nullptr, threadRoutine, (void*)"thread-1");pthread_t td2;pthread_create(&td2, nullptr, threadRoutine, (void*)"thread-2");pthread_t td3;pthread_create(&td3, nullptr, threadRoutine, (void*)"thread-3");while(true){sleep(5);pthread_mutex_lock(&mutex);ticket += 10;pthread_mutex_unlock(&mutex);pthread_cond_signal(&cond);}pthread_join(td1, nullptr);pthread_join(td2, nullptr);pthread_join(td3, nullptr);return 0;
}

在这里插入图片描述
当线程2,线程3,线程1先后检测到ticket == 0时,在cond的等待队列中,线程以2,3,1的顺序排队。那当调用pthread_cond_signal函数时,线程2会先执行,执行完再检测到ticket==0,排到等待队列尾部
在这里插入图片描述
再调用pthread_cond_signal函数时,线程3会执行。
在这里插入图片描述
再调用pthread_cond_signal函数时,线程1会执行。
在这里插入图片描述
这样,我们多线程就可以按某种特定顺序来执行。
那如果我们使用pthread_cond_broadcast函数,会有什么情况?
在这里插入图片描述
我们会发现,三个线程都被唤醒,来争抢ticket资源,其先后顺序由操作系统决定(优先级,竞争锁的能力…)。


pthread_cond_wait为什么要有mutex

此时不知道你是否有一个疑问,我们假定线程-1先争抢到ticket,检测到ticket == 0时,该线程-1要在cond的等待队列中等待,然后其它两个线程在进入临界区,检测到ticket==0,在等待队列中排队。但是线程-1是持有锁进入等待队列的,那其它两个线程是如何进入临界区的?答案很明显,那就是线程-1在cond等待中,一定释放了其持有的锁,从而使其余两个线程可以持有锁进入临界区。这就是为什么pthread_cond_wait函数的参数要有互斥锁的存在。
那我们在深入想一想,调用pthread_cond_wait函数后,该线程是先释放锁,再进入等待队列中;还是先进入等待队列中,再释放锁?答案是都不是。释放锁和进入等待队列是同时进行的!!!这也表示pthread_cond_wait函数是原子的,在调用pthread_cond_wait时,涉及的互斥锁释放和进入等待队列的操作是作为一个不可分割的整体来执行。确保了线程在调用该函数时不会遇到竞态条件,即线程在释放锁和进入等待队列之间不会被其它线程打断。
以下是线程调用pthread_cond_wait的过程

  1. 线程必须已经持有锁:在调用pthread_cond_wait时,线程必须已经锁定了某个互斥锁,这是该函数的前提条件

  2. 自动释放互斥锁:当线程调用pthread_cond_wait时,它会自动释放它当前持有的互斥锁。这一步是为了允许其它线程有机会获取该互斥锁并修改共享资源,从而可能改变条件变量的状态

  3. 加入等待队列:释放互斥锁之后,线程会接着被添加到条件变量的等待队列中,并在此处等待

  4. 等待被唤醒:线程在等待队列中等待,直到其它线程调用pthread_cond_signal 或 pthread_cond_broadcast来唤醒它

  5. 重新获取互斥锁:当线程被唤醒时,线程会尝试重新获取之前释放的互斥锁。如果锁此时没有被其它线程持有,线程将成功获取锁并继续执行。如果锁仍然被其它线程持有,线程将阻塞(在申请锁的地方阻塞),直到能够获取锁时。

具体来说,pthread_cond_wait函数的内部实现保证了2,3步骤的原子性。


伪唤醒问题的解决 (if->while)

伪唤醒问题是指:在多线程环境中,当线程等待某个条件变量时,它可能会在没有到达预期的情况下被唤醒。
对于这一问题,我们可以在判断的时候,将if 变为 while,使其被唤醒后任然进行条件判断,如果条件满足,线程继续向后执行,如果条件不被满足,线程继续在该条件变量下等待。

pthread_mutex_lock(&mutex);
// 访问临界区 
while(条件为假)pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);

总结

以上就是我对于线程同步的总结。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uniapp对接萤石云 实现监控播放、云台控制、截图、录像、历史映像等功能

萤石云开发平台地址&#xff1a;文档概述 萤石开放平台API文档 (ys7.com) 萤石云监控播放 首先引入萤石云js js地址&#xff1a;GitHub - Ezviz-OpenBiz/EZUIKit-JavaScript-npm: 轻应用npm版本&#xff0c;降低接入难度&#xff0c;适配自定义UI&#xff0c;适配主流框架 vi…

速通汇编(二)汇编mov、addsub指令

一&#xff0c;mov指令 mov指令的全称是move&#xff0c;从字面上去理解&#xff0c;作用是移动&#xff08;比较确切的说是复制&#xff09;数据&#xff0c;mov指令可以有以下几种形式 无论哪种形式&#xff0c;都是把右边的值移动到左边 mov 寄存器&#xff0c;数据&#…

JavaScript极速入门(1)

初识JavaScript JavaScript是什么 JavaScript(简称JS),是一个脚本语言,解释型或者即时编译型语言.虽然它是作为开发Web页面的脚本语言而著名,但是也应用到了很多非浏览器的环境中. 看似这门语言叫JavaScript,其实在最初发明之初,这门语言的名字其实是在蹭Java的热度,实际上和…

打PTA (15分)(JAVA)

目录 题目描述 输入格式&#xff1a; 输出格式&#xff1a; 输入样例&#xff1a; 输出样例&#xff1a; 题解 题目描述 传说这是集美大学的学生对话。本题要求你做一个简单的自动问答机&#xff0c;对任何一个问句&#xff0c;只要其中包含 PTA 就回答 Yes!&#xff0c;其…

JavaScript高级 —— 学习(一)

目录 一、作用域 &#xff08;一&#xff09;局部作用域 1.函数作用域 2.块作用域 &#xff08;二&#xff09;全局作用域 二、垃圾回收机制 GC &#xff08;一&#xff09;生命周期 1.内存分配 2.内存使用 3.内存回收 4.特殊情况——内存泄漏&#xff1a; 注意&…

Manjaro 安装全新 Linux 版微信,从此告别 Wine

目前已经基本上使用 Manjaro 来工作&#xff0c;而工作离不开微信作为日常的工作沟通工具。因为微信官方一直没有 Linux 版本的&#xff0c;所以之前都只能够使用 Wine 版本&#xff0c;然后踩了不少坑&#xff0c;但还算能勉强使用。 最近听说微信终于要发布 Linux 版本的&am…

Linux之冯诺依曼体系,操作系统,进程的理解,进程状态,以及进程的优先级

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言进阶 数据结构初阶 Linux C初阶 算法 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂 目录 一.冯诺依曼体系 二.操作系统 2.1概念 2.2结构示意图&…

基于Axios封装请求---防止接口重复请求解决方案

一、引言 前端接口防止重复请求的实现方案主要基于以下几个原因&#xff1a; 用户体验&#xff1a;重复发送请求可能导致页面长时间无响应或加载缓慢&#xff0c;从而影响用户的体验。特别是在网络不稳定或请求处理时间较长的情况下&#xff0c;这个问题尤为突出。 服务器压力…

树状数组与线段树基础3

本来想练练线段树的&#xff0c;没想到有许多细节忘了&#xff0c;加上今天的金工实习坐牢坐穿了&#xff0c;于是再复习一下吧。 首先介绍一下树状数组&#xff08;貌似第一篇就讲了&#xff0c;不过那个东西真是一坨Shit,当时还没有怎么理解就写了&#xff09; 首先它的复杂…

知识图谱与大数据:区别、联系与应用

目录 前言1 知识图谱1.1 定义1.2 特点1.3 应用 2 大数据2.1 定义2.2 应用 3. 区别与联系3.1 区别3.2 联系 结语 前言 在当今信息爆炸的时代&#xff0c;数据成为了我们生活和工作中不可或缺的资源。知识图谱和大数据是两个关键概念&#xff0c;它们在人工智能、数据科学和信息…

30-3 越权漏洞 - 水平越权(横向越权)

环境准备:构建完善的安全渗透测试环境:推荐工具、资源和下载链接_渗透测试靶机下载-CSDN博客 一、定义 攻击者可以访问和操作与其拥有同级权限的用户资源。 示例: 学生A在教务系统上正常只能修改自己的作业内容,但由于不合理的权限校验规则等原因,学生A可以修改学生B的内…

【python】flask执行上下文context,请求上下文和应用上下文原理解析

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

【Java - 框架 - Lombok】(2) SpringBoot整合Lombok完成日志的创建使用 - 快速上手;

"SpringBoot"整合"Lombok"完成日志的创建使用 - 快速上手&#xff1b; 环境 “Java"版本"1.8.0_202”&#xff1b;“Lombok"版本"1.18.20”&#xff1b;“Spring Boot"版本"2.5.9”&#xff1b;“Windows 11 专业版_22621…

厨余垃圾处理设备工业监控PLC连接APP小程序智能软硬件开发之功能原理篇

接着上一篇《厨余垃圾处理设备工业监控PLC连接APP小程序智能软硬件开发之功能结构篇》继续总结一下厨余垃圾处理设备智能软硬件统的原理。所有的软硬件系统全是自己一人独自开发&#xff0c;看法和角度难免有局限性。希望抛砖引玉&#xff0c;将该智能软硬件系统分享给更多有类…

Web APIs

文章目录 Web APIs1. DOM1.1 介绍DOM 树DOM 节点document 1.2 获取DOM对象1.3 操作元素内容1.4 操作元素属性常用属性修改控制样式属性操作表单元素属性自定义属性 1.5 间歇函数1.6 事件事件监听事件类型事件处理程序 1.7 事件类型鼠标事件键盘事件焦点事件文本框输入事件 1.8 …

数据分析之Power BI

POWER QUERY 获取清洗 POWER PIVOT建模分析 如何加载power pivot 文件-选项-加载项-com加载项-转到 POWER VIEW 可视呈现 如何加载power view 文件-选项-自定义功能区-不在功能区中的命令-新建组-power view-添加-确定 POWER MAP可视地图

vue3-pinia使用(末尾有彩蛋)

什么是 pinia Pinia 是 Vue 的专属状态管理库&#xff0c;它允许你跨组件或页面共享状态。 之前用的是 vuex&#xff0c;后面 vue 官方团队不维护了&#xff0c;推荐使用 pinia 安装 yarn add pinia # 或者使用 npm npm install piniapnpm install piniaStore 是什么&#xf…

AIGC-Stable Diffusion发展及原理总结

目录 一. AIGC介绍 1. 介绍 2. AIGC商业化方向 3. AIGC是技术集合 4. AIGC发展三要素 4.1 数据 4.2 算力 4.3 算法 4.3.1 多模态模型CLIP 4.3.2 图像生成模型 二. Stable Diffusion 稳定扩散模型 1. 介绍 1.1 文生图功能&#xff08;Txt2Img) 1.2 图生图功能&…

八大技术趋势案例(区块链量子计算)

科技巨变,未来已来,八大技术趋势引领数字化时代。信息技术的迅猛发展,深刻改变了我们的生活、工作和生产方式。人工智能、物联网、云计算、大数据、虚拟现实、增强现实、区块链、量子计算等新兴技术在各行各业得到广泛应用,为各个领域带来了新的活力和变革。 为了更好地了解…

C语言中如何动态分配内存并进行操作

C语言文章更新目录 C语言学习资源汇总&#xff0c;史上最全面总结&#xff0c;没有之一 C/C学习资源&#xff08;百度云盘链接&#xff09; 计算机二级资料&#xff08;过级专用&#xff09; C语言学习路线&#xff08;从入门到实战&#xff09; 编写C语言程序的7个步骤和编程…