Linux 线程同步

news2024/10/1 5:34:08

前言

上一期我们介绍了线程互斥,并通过加锁解决了多线程并发访问下的数据不一致问题!本期我们来介绍一下同步问题!

目录

前言

一、线程同步

• 线程同步的引入

• 同步的概念

理解同步和饥饿问题

• 条件变量

理解条件变量

• 同步的相关操作

条件变量的创建与销毁

等待条件

唤醒线程

简单的同步测试用例


一、线程同步

• 线程同步的引入

上一期加锁之后的抢票Demo中,我们发现虽然不会出现多卖出票的情况了,但是我么发现一个线程可以连续抢到很多的票,搞得我们有些线程像是黄牛了~!

上面某个线程一直执行抢票动作,而抢票的过程是互斥的,也就是加了锁的!在它抢票期间,其他的线程是一直得阻塞等待的,如果那些阻塞等待的线程一直竞争不到锁,就会造成线程长时间无法被调度的饥饿问题!为了解决上述的黄牛式的抢票的问题,让其他的线程有机会被调度!我们就引入了线程的同步~!

• 同步的概念

在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题的机制,叫做同步

竞态条件:因为时序问题而导致程序出现异常

理解同步和饥饿问题

OK,我们直接上一个例子解释:

话接上回,张三早上6:00就欢快的抢到VIP自习室的钥匙,进去自习了!过了一会,来了好多想要进VIP自习室自习的人!但是由于张三没出来,他们没有钥匙就进不去!

他们都在等着张三出来归还钥匙,但是张三不急不慢的自习到中午的12:00,此时他有点饿了,想出去吃个饭,出去吃饭就意味着仔细结束,得把钥匙归还!张三刚到门口把钥匙挂到门上,回头一看这么多等待的人,张三一想,我这把钥匙挂上去,吃个饭回来,我就是这些等待的人中的一个,又不知道到得等到啥时候~!所以张三心一横,又把钥匙拿了下来,又进去学习了;刚进去没几分钟张三就饿得不行了,于是去把钥匙挂到门上,回头一看这么多的人就又拿下来,强撑着进去了!就这样张三反复进出,直到晚上的8 : 00 ,张三自己不仅没有吃上饭、没专心的学习,而且还导致其他人也无法进入自习室学习!

但是按照规定张三并没有错,且符合自习室只允许持有钥匙的一人进入的规则!只不过他的这种行为极其不合理(现实中估计家人不保)!因为张三这种不合理的行为,导致了自习室的资源浪费,其他同学没机会进VIP自习室自习,从而陷入了 饥饿状态!为此,管理员连夜修改了规则:

• 在外面等待的同学必须排队等待

• 所有自习完的同学在归还玩钥匙之后,不能立即申请,下次申请,需要排队

新规则出来以后,自习室的使用都是按照一定的顺序进行申请使用,再也没有了以前的饥饿问题!上述的旧规则时期每张三反复进出的,导致其他人长时间不能进入自习室,就是导致饥饿问题!新规则后多人按照一定顺序申请使用自习室,就是同步


• 条件变量

原生的线程库 提供了 条件变量 来实现 线程同步

通过条件变量 -> 实现新线程同步 -> 解决了饥饿问题

 • 条件变量 :当一个线程互斥的访问某个变量时,他可能发现在其他线程改变状态之前,什么也做不了

比如当一个线程访问队列时,发现队列为空,它只能等待,直到其他线程往队列中添加数据,此时就可以考虑使用 条件变量

理解条件变量

现在有A和B两人协作的一个拿放苹果游戏

规则是A得蒙着眼睛,B不能说话!B向盘子放苹果,A从盘子拿苹果;盘子任意时刻只能由一人使用,最终哪个组拿的最多就获胜,奖励一个iphone16;

有很多组参加,你(李四)和你的搭档张三,想得第一名,整个iphone玩一玩!于是就报名参加了结果上半场,由于你不知道盘子是否有苹果而频繁的申请盘子,检测盘子;导致了你的搭档张三根本就无法拿到盘子,更别说放苹果了!所以,上半场以0个苹果结束!

在中场休息的时间,你很仔细阅读了规则,发现没说不能使用工具,所以,你俩就整了个铃铛,张三给你说,李四啊,你如果拿到盘子没有苹果,你就把盘子还回去,不要再拿了,你就定定的等着,当我把苹果放到盘子了,就敲响一声铃铛,你就来拿,你拿完继续等着!果然下半场,利用这个机制,你们拿下了很多的苹果!

但是,下半场结束后,有一组和你们的苹果数一样的多,所以有增加了一场,但是这一场的规则稍有变化,就是给每一组增加一位拿苹果的人,看最后哪一组拿的多,就谁赢!

有了上半场的经验,你和张三以及新队友王五,一起提前开会,你们说好了,由于多了一个人,这次铃铛的规则也变了,当敲一声时一个人来拿,当敲两声时,都来拿(但实际只有一个人能拿到盘子,所以两人得竞争,得拼手速)!于是就开始了,但这次显然你们比对手准备的好,你们最后比他们拿的多!

上述的盘子就是临界资源,一次只能一个人那盘子就是互斥铃铛就是条件变量!此时,当你的搭档不敲铃铛时,你啥也做不了,就在那等着!

条件变量的本质可以理解为 衡量或指示访问资源状态的一种机制

所以,条件变量内部必须实现两个东西:1、需要一个等待队列 2、需要一个通知机制!

• 同步的相关操作

条件变量的创建与销毁

条件变量互斥锁都是原生线程库中的,他的接口风格和互斥锁极其相似,例如:

互斥量的类型: pthread_mutex_t   条件变量的类型: pthread_cond_t

定义全局的条件变量

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

注意在全局创建条件变量时,初始化为 PTHREAD_COND_INITIALIZER自动销毁

定义局部的条件变量

#include <pthread.h>

pthread_cond_t cond; // 定义一个条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,
      const pthread_condattr_t *restrict attr);

参数

restrict cond 表示要初始化的条条件变量

restrict attr 表示初始化时的相关属性,直接设置为nullptr即可

返回值

成功,返回0

失败,返回错误码

注意:这些接口的返回值都是一样的,后续不再介绍!

条件变量的销毁

#include <pthread.h>

int pthread_cond_destroy(pthread_cond_t *cond);

参数

cond 表示要销毁的条件变量

等待条件

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *restrict cond,
       pthread_mutex_t *restrict mutex);

参数

cond :表示要等待的条件变量

restrict mutex :互斥锁,用于辅助的条件变量

为什么等待时需要一把互斥锁?

1、条件变量也是临界资源,也需要被保护

2、当条件不满足时,需要将线程挂起到特定的阻塞队列,但是其已持有锁资源,为了避免死锁,条件变量内部再把该线程挂起前,要对该锁资源进行释放,会用到它!

唤醒线程

当条件变量满足时,需要唤醒阻塞队列中等待该条件变量的线程,可以唤醒一个,也可以唤醒全部,这就是我们上述所说的通知机制!

#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);//唤醒一个

int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒所有

第一个表示唤醒等待条件变量阻塞队列的队头的那一个线程,第二个时唤醒阻塞队列中所有线程!

注意:当全部唤醒之后,所有的线程也会先去竞争锁,如果持有锁了继续后续的操作,如果竞争失败了,去锁那里等待锁资源!

简单的同步测试用例

我们写一个小Demo,让num个线程都在进入临界区之后等待,主线程唤醒之后再去执行!

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>

const int num = 5;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;    // 创建一个全局的条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 创建一个全局的互斥量

void *Wait(void *args)
{
    const char *name = static_cast<char *>(args);
    while (true)
    {
        // 加锁
        pthread_mutex_lock(&mutex);
        // 让所有线程一进来就再cond的条件下等待
        pthread_cond_wait(&cond, &mutex);
        std::cout << name << ", 正在运行....." << std::endl;
        // 解锁
        pthread_mutex_unlock(&mutex);
    }

    return nullptr;
}

int main()
{
    pthread_t tid[num];// 启动5个线程
    for(int i = 0; i < num; i++)
    {
        char* name = new char[128];
        snprintf(name, 128, "thread_%d", i+1);
        pthread_create(tid+i, nullptr, Wait, (void*)name);//创建5个线程
    }

    sleep(3);//d等所有的线程起来

    // 主线程唤醒新线程
    while(true)
    {
        std::cout << "Main wake up new thread: " << std::endl;
        pthread_cond_signal(&cond);// 唤醒一个
    }

    // 等待线程
    for(int i = 0; i < num; i++)
    {
        pthread_join(*(tid+i), nullptr);
    }

    return 0;
}

OK,此时是主线程一次唤醒一个线程,我们看看效果:

我们再来试试全部唤醒:

OK,没有问题!好兄弟我是cp我们下期再见!

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

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

相关文章

TypeScript 算法手册 【数组基础知识】

文章目录 1. 数组简介1.1 数组定义1.2 数组特点 2. 数组的基本操作2.1 访问元素2.2 添加元素2.3 删除元素2.4 修改元素2.5 查找元素 3. 数组的常见方法3.1 数组的创建3.2 数组的遍历3.3 数组的映射3.4 数组的过滤3.5 数组的归约3.6 数组的查找3.7 数组的排序3.8 数组的反转3.9 …

AI写作赋能数据采集,开启无限可能性

由人工智能 AI 掀起的新一轮科技革命浪潮&#xff0c;正在不断推动社会进步、各行各业升级发展&#xff0c;深刻影响人们的生活方式&#xff0c;引领我们进入一个充满无限可能的新时代。 那么在数据采集方面&#xff0c;人工智能 AI 可以做什么呢&#xff1f; 下面是搜集网络…

开源在线表结构设计工具

Free, simple, and intuitive database design tool and SQL generator. drawDB在线体验 Discord X drawDB DrawDB is a robust and user-friendly database entity relationship (DBER) editor right in your browser. Build diagrams with a few clicks, export sql scri…

若依--文件上传前端

前端 ry的前端文件上传单独写了一个FileUpload.Vue文件。在main.js中进行了全局的注册&#xff0c;可以在页面中直接使用文件上传的组件。全局导入 在main.js中 import 组件名称 from /components/FileUpLoadapp.compoent(组件名称) //全局挂载组件在项目中使用 组件命令 中…

定时器定时中断定时器外部中断

TIM的函数 // 恢复缺省设置 void TIM_DeInit(TIM_TypeDef* TIMx); // 时基单元初始化&#xff0c;第一个参数TIMx选择某个定时器&#xff0c;第二个参数是结构体&#xff0c;包含了配置时基单元的一些参数。 void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDe…

28 Vue3之搭建公司级项目规范

可以看到保存的时候ref这行被提到了最前面的一行 要求内置库放在组件的前面称为auto fix&#xff0c;数组new arry改成了字面量&#xff0c;这就是我们配置的规范 js规范使用的是airbnb规范模块使用的是antfu 组合prettier&eslint airbnb规范&#xff1a; https://github…

《More Effective C++》的学习

引用与指针 没有所谓的null reference reference一定需要代表某个对象&#xff0c;所以C要求reference必须有初值。 QString &s; 使用reference可能比使用pointer更高效。 因为reference一定是有效的&#xff0c;而指针可能为空&#xff08;需要多加一个判断&#xff0…

Springboot3 + MyBatis-Plus + MySql + Vue + ProTable + TS 实现后台管理商品分类(最新教程附源码)

Springboot3 MyBatis-Plus MySql Uniapp 商品加入购物车功能实现&#xff08;针对上一篇sku&#xff09; 1、效果展示2、数据库设计3、后端源码3.1 application.yml 方便 AliOssUtil.java 读取3.2 model 层3.2.1 BaseEntity3.2.1 GoodsType3.2.3 GoodsTypeSonVo3.3 Controll…

论文翻译 | LLaMA-Adapter :具有零初始化注意的语言模型的有效微调

摘要 我们提出了一种轻量级的自适应方法&#xff0c;可以有效地将LLaMA微调为指令遵循模型。lama - adapter采用52K自指导演示&#xff0c;在冻结的LLaMA 7B模型上只引入1.2M可学习参数&#xff0c;在8个A100 gpu上进行微调花费不到一个小时。具体来说&#xff0c;我们采用了一…

Vue3+Antv X6流程图基本使用

安装 antv/X6 npm i antv/x6 <template><div class"homes"><div class"Shang">上</div><div class"Zhong"><div id"container"></div></div><div class"Xia">下<…

wordpress Contact form 7发件人邮箱设置

此教程仅适用于演示站有留言的主题&#xff0c;演示站没有留言的主题&#xff0c;就别往下看了&#xff0c;免费浪费时间。 使用了Contact form 7插件的简站WordPress主题&#xff0c;在有人留言时&#xff0c;就会发邮件到网站的系统邮箱(一般与管理员邮箱为同一个)里。上面显…

Java | Leetcode Java题解之第448题找到所有数组中消失的数字

题目&#xff1a; 题解&#xff1a; class Solution {public List<Integer> findDisappearedNumbers(int[] nums) {int n nums.length;for (int num : nums) {int x (num - 1) % n;nums[x] n;}List<Integer> ret new ArrayList<Integer>();for (int i …

传奇外网架设教程带图文解说—Gee引擎

架设前准备工作&#xff1a; ①通过百度网盘下载版本、补丁、客户端和DBC2000。版本解压到D盘&#xff0c;客户端解压到D盘或是E盘&#xff0c;补丁先不解压 ②安装和配置DBC2000&#xff0c;有些版本不一定用的是DBC2000数据库&#xff0c;看引擎默认的数据库是哪个 DBC数据…

【机器学习基础】Transformer学习

Transformer学习 梯度消失FeedForward层激活函数的主要作用是在网络中加入非线性变换 梯度消失 梯度爆炸 FeedForward层 Transformer结构: Transformer结构主要分为两大部分: 一是Encoder层结构:Encoder 的输入由 Input Embedding 和 Positional Embedding 求和输入Multi…

【SpringBoot详细教程】-08-MybatisPlus详细教程以及SpringBoot整合Mybatis-plus【持续更新】

目录 🌲 MyBatis Plus 简介 🌾入门案例 🌾 MP 简介 🌲 MP 的CRUD 🌾 新增 🌾 删除 🌾 修改在进行 🌾 根据ID查询 🌾 查询所有 🌲 分页功能 🌾 设置分页参数 🌾 设置分页拦截器 🌲 优化启动 🌾 取消mbatisPlusBanner 🌾 取消Sprin…

仿真设计|基于51单片机的路口交通灯控制系统仿真

目录 具体实现功能 设计介绍 51单片机简介 资料内容 仿真实现&#xff08;protues8.7&#xff09; 程序&#xff08;Keil5&#xff09; 全部内容 资料获取 具体实现功能 &#xff08;1&#xff09;东西向右转和直行绿灯20S&#xff0c;左转红灯&#xff1b;南北向直行和…

若依从redis中获取用户列表

因为若依放入用户的时候&#xff0c;会在减值中添加随机串&#xff0c;所以用户的key会在redis中变成&#xff1a; login_tokens:6af07052-b76d-44dd-a296-1335af03b2a6 这样的样子。 如果用 Set<Object> items redisService.redisTemplate.keys("login_tokens&…

wordpress重置密码的方法

通过phpMyAdmin直接修改数据库&#xff1a; 登录到phpMyAdmin(通常在cPanel中找到)&#xff0c;找到WordPress数据库&#xff0c;进入wp_users表。 找到对应的用户ID行&#xff0c;修改user_pass字段为新的密码值&#xff0c;并保存更改。 比如&#xff0c;把值改为&#xff…

Mysql ONLY_FULL_GROUP_BY模式详解、group by非查询字段报错

文章目录 一、问题报错二、ONLY_FULL_GROUP_BY模式2.1、什么是ONLY_FULL_GROUP_BY&#xff1f;2.2、为什么要使用ONLY_FULL_GROUP_BY&#xff1f;2.3、查看sql_mode 三、解决方法3.1、关闭only_full_group_by模式3.1.1、方法一&#xff1a;关闭当前会话中的only_full_group_by3…

电商选品/分析| 亚马逊常见插件爬虫实战之-helium插件

说明 插件爬虫相当于二次爬虫,二次加工信息,因为大部分插件信息也是从正规网上去获取数据,这次列举helium插件爬虫案例,其他插件爬虫也是类似这个方式. 需求 1、⽤⾕歌浏览器&#xff0c;下载chrome extension&#xff1a;“Helium 10 2、登录helium10 3、打开 打开Amazo…