【Linux】线程同步

【Linux】线程同步

📝前言:

上篇文章我们讲解了【Linux】线程互斥,这篇文章我们来讲讲Linux——线程同步

🎬个人简介:努力学习ing
📋个人专栏:Linux
🎀ZEEKLOG主页 愚润求学
🌄其他专栏:C++学习笔记C语言入门基础python入门基础C++刷题专栏

目录

一,同步定义

在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源。我们可以回忆一下管道,管道的读写行为是具有顺序性的,这就是一种同步。

为什么需要同步?
当两个线程(一个读端,一个写端)同时访问一块内存的时候,为了保证线程安全性,需要加锁。但是,如果此时这块内存写端还没有写入内容,读端一直占据着锁(频繁申请,且每次都竞争到锁),写端就无法写入内容,读端也无法读到内容。

为此,我们可以让读端没读到内容的时候就进入等待(把锁给释放),然后唤醒写端(让写端拿锁)

如果有多个读端,又一个读端“离锁最近”一直在读,其他读端读不到怎么办?

这时候我们可以设计一个“等待队列”,让每个读端申请完一次锁以后,就要重新进入队列的末尾开始重新排队。

条件变量就具有这样的唤醒 + 等待队列的功能

二,条件变量

1. 基本介绍

解决线程间因共享资源状态变化而需要互相通知的问题,且保证公平性。

作用

  • 让线程等待某个条件满足(如共享资源可用、数据准备完成等)。
  • 当条件满足时,通知等待的线程继续执行。

关键操作

  • 等待(Wait):线程阻塞自己,放入指定条件变量的等待队列,等待条件变量被唤醒。
  • 通知(Signal/Broadcast):其他线程在条件满足时唤醒等待的线程。

与互斥锁的配合

  • 条件变量本身不具备锁的功能,需配合互斥锁使用,确保对共享资源状态的检查和修改是原子操作。

2. 接口

基本上和mutex的接口一样。

2.1 初始化和销毁

初始化

静态分配

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
  • pthread_cond_t:条件变量cond的类型

动态分配

intpthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
  • cond:要初始化的条件变量
  • attr:nullptr
销毁
intpthread_cond_destroy(pthread_cond_t *cond)

2.2 等待和通知

等待

cond_wait包含两步操作:解锁 + 等待(类比--是3条汇编)

为什么条件变量要配合锁使用?

如果解锁 + 等待是两个动作,不是原子的,则会出现以下问题:

  1. 线程 A 条件不满足 → 解锁
  2. 线程 A 被切换,线程 B 唤醒线程 A(但是线程 A 还未进入等待,收不到这个唤醒,唤醒被错过)
  3. 切回线程 A ,A 进入等待,但接受不到唤醒信号了,变成死锁

常用接口
无限期阻塞等待
(只有唤醒才醒来)

intpthread_cond_wait( pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
  • cond:在哪个条件变量上等待
  • mutex:对应的互斥锁
  • 在进入等待的时候会释放锁,等待被唤醒后要重新申请锁
    • 申请成功,就会接着运行后面的语句
    • 申请不成功,就会在“锁上”阻塞(直到拿到锁)
  • 被唤醒就是在临界区被唤醒,然后继续往后执行

时间片等待(超时了就醒来)

intpthread_cond_timedwait( pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex,conststructtimespec*restrict abstime );
  • 绝对超时时间(如果到这个时间还没有被唤醒,就醒来)
通知(唤醒)

全部唤醒

intpthread_cond_broadcast(pthread_cond_t *cond);

如果cond里面没有能唤醒的,则什么都不做。

只唤醒条件变量的队头

intpthread_cond_signal(pthread_cond_t *cond);

三,使用经典规范

1. 等待代码

pthread_mutex_lock(&mutex);// 访问临界区先加锁while(条件为假) // 判断条件是否满足,实现同步pthread_cond_wait(cond, mutex);// 条件不满足:释放锁 + 等待 访问临界资源;// 修改共享资源(条件可能再次变为不满足)pthread_mutex_unlock(&mutex);// 访问完临界区解锁

为什么用 while 而不是 if
虚假唤醒:

  • pthread_cond_wait执行错误(没有进入等待),然后返回
  • pthread_cond_wait一下被唤醒多个线程,但是一个线程执行完以后,条件再次变为不满足。此时其他线程过了if判断且被唤醒,再次向下执行,就会出现错误。所以要循环重新检查条件

2. 唤醒代码

在另一个线程内:

pthread_mutex_lock(&mutex);// 代表也是在加锁的临界区内 访问临界资源(条件);// 使条件为真pthread_cond_signal(&cond);// (当条件为真,且有可以唤醒的)唤醒一个等待的线程pthread_mutex_unlock(&mutex);

四,生产者消费者模型

1. 基本介绍

生产者消费者模式就是通过⼀个容器(交易场所)来解决生产者和消费者的强耦合问题。生产者往交易场所生产数据,消费者从交易场所获取数据,生产者的生产不需要等待消费者处理完数据。从而实现生产和消费的并发运行。

在这里插入图片描述

生产者与消费者关系:

  • 生产者和生产者:互斥
  • 消费者与消费者:互斥
  • 生产者和消费者:互斥 + 同步

特点:

  • 3 个要素:生产者,消费者,一个交易场所(临界资源)
  • 3 个关系:(上面提到的生产者与消费者的关系)
  • 2 种角色:生产者和消费者(分别由线程承担)
  • 1 个交易场所:以特定结构构成的一种“内存”(临界资源)

优势

  • 支持生产和消费解耦
  • 忙闲不均
  • 提高效率

2. 使用示例

我们实现一个基于BlockingQueue的生产者消费者模型
BlockingQueue核心要点:

  • Pop 的时候,如果队列里面没东西,则阻塞等待生产者Push
  • Push 的时候,如果队列满了,则阻塞等待消费者Pop
// BlockQueue.hpp#include<pthread.h>#include<iostream>#include<string>#include<queue>template<typenameT>classBlockQueue{private:boolEmpty(){return blockqueue.size()==0;}boolFull(){return blockqueue.size()== _cap;}public:BlockQueue(int cap):_prod_wait_num(0),_coms_wait_num(0),_cap(cap){pthread_mutex_init(&_mutex,nullptr);pthread_cond_init(&_full_cond,nullptr);pthread_cond_init(&_empty_cond,nullptr);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_full_cond);pthread_cond_destroy(&_empty_cond);}voidPush(const T &task){pthread_mutex_lock(&_mutex);while(Full()){ _prod_wait_num++;pthread_cond_wait(&_full_cond,&_mutex); _prod_wait_num--;} blockqueue.push(task);// 修改临界资源 std::cout <<"生产者: "<<pthread_self()<<" 生产了一个任务"<< std::endl;if(_coms_wait_num >0)pthread_cond_signal(&_empty_cond);pthread_mutex_unlock(&_mutex);} T Pop(){pthread_mutex_lock(&_mutex);// 等待while(Empty()){ _coms_wait_num++;// 如果唤醒,就在临界区被唤醒,然后继续往下执行pthread_cond_wait(&_empty_cond,&_mutex); _coms_wait_num--;} T task = blockqueue.front(); blockqueue.pop(); std::cout <<"消费者: "<<pthread_self()<<" 处理了一个任务"<< std::endl;// 唤醒if(_prod_wait_num >0)pthread_cond_signal(&_full_cond);pthread_mutex_unlock(&_mutex);return task;}private: std::queue<T> blockqueue; pthread_mutex_t _mutex; pthread_cond_t _full_cond; pthread_cond_t _empty_cond;int _cap;// 设置容量上限int _prod_wait_num;int _coms_wait_num;};// Main.cpp#include"BlockQueue.hpp"#include<vector>#include<unistd.h>#include<atomic>#include"Task.hpp"void*Prod(void* args){ BlockQueue<task_t>* ptr =static_cast<BlockQueue<task_t>*>(args);while(true){// sleep(1); ptr->Push(Download);}returnnullptr;}void*Coms(void* args){ BlockQueue<task_t>* ptr =static_cast<BlockQueue<task_t>*>(args);while(true){ task_t task = ptr->Pop();// 获取任务// cout 输出也会有并发问题,暂时放在Pop里面task();}returnnullptr;}intmain(){ BlockQueue<int>super(3);// 单生产者,单消费者 pthread_t p[1], c[1];pthread_create(&p[0],nullptr, Prod,&super);// 生成者pthread_create(&c[0],nullptr, Coms,&super);// 消费者pthread_join(p[0],nullptr);pthread_join(c[0],nullptr);// // 多生产者,多消费者// pthread_t p[2], c[2];// pthread_create(&p[0], nullptr, Prod, &super); // 生成者// pthread_create(&c[0], nullptr, Coms, &super); // 消费者// pthread_create(&p[1], nullptr, Prod, &super); // 生成者// pthread_create(&c[1], nullptr, Coms, &super); // 消费者// pthread_join(p[0], nullptr);// pthread_join(c[0], nullptr);// pthread_join(p[1], nullptr);// pthread_join(c[1], nullptr);return0;}

当然,我的代码还有很多未处理的BUG,仅供参考。

运行结果(单生产者和单消费者):
生成者快,消费者慢:先生产一堆,然后消费一个生产一个。

在这里插入图片描述


生产者满,消费者慢,生产一个消费一个。

在这里插入图片描述

🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!

Read more

安装篇--Ubuntu24.04.2详细安装教程

安装篇--Ubuntu24.04.2详细安装教程

一、准备工作 建议前往ubuntu官网 (https://ubuntu.com/download/desktop) 选择LTS (Long-Term Support) 版本,稳定性更好,支持周期更长 二、创建新的虚拟机 1.点击 “创建新的虚拟机” 2.新建虚拟机向导 - 选择配置类型: * 在向导的第一步,选择 “自定义(高级)” (Custom (advanced))。 * 点击“下一步(N)”。 3.选择虚拟机硬件兼容性: 如果你没有特殊需求,保持默认选项,点击“下一步(N)” 4. 选择安装来源 选择“稍后安装操作系统”,点击“下一步(N)” 5. 选择客户机操作系统 选择“Linux”

By Ne0inhk
08-OpenClaw自动化与定时任务

08-OpenClaw自动化与定时任务

OpenClaw 自动化与定时任务 免费专栏全套教程:OpenClaw从入门到精通 OpenClaw 提供了一套完整的自动化系统,包括 Heartbeat 心跳机制、Cron 定时任务、Hooks 事件钩子和 Webhook 外部触发。本章将详细介绍这些机制的概念、配置和实战应用。 目录 1. 自动化工作流概念 2. Heartbeat 心跳机制 3. Cron 定时任务配置 4. Hooks 事件钩子 5. Webhook 外部触发 6. 实战案例 7. 故障排查 1. 自动化工作流概念 1.1 核心组件 OpenClaw 的自动化系统由四个核心组件构成: 组件用途触发方式适用场景Heartbeat周期性检查自动定时批量检查、上下文感知监控Cron精确定时任务时间驱动固定时间执行、独立任务Hooks事件驱动响应事件触发命令响应、生命周期管理Webhook外部系统集成HTTP 请求第三方系统对接、推送接收 1.

By Ne0inhk
Flutter 三方库 sort_json 的鸿蒙化适配指南 - 实现 JSON 键值的自动化递归排序、支持规范化输出与项目配置文件清理

Flutter 三方库 sort_json 的鸿蒙化适配指南 - 实现 JSON 键值的自动化递归排序、支持规范化输出与项目配置文件清理

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 sort_json 的鸿蒙化适配指南 - 实现 JSON 键值的自动化递归排序、支持规范化输出与项目配置文件清理 前言 在进行 Flutter for OpenHarmony 的工程化开发时,保持项目配置文件(如 package.json、.json5 或各种国际化语言文件)的条理性是至关重要的。特别是在多人协作或版本控制(Git)中,无序的 JSON 键值会导致严重的冲突。sort_json 是一个专注于将 JSON 字符串或文件重新排版并按字母顺序排序的库。本文将探讨如何利用该工具优化鸿蒙项目的配置管理。 一、原理解析 / 概念介绍 1.1 基础原理 sort_json 通过将输入的 JSON

By Ne0inhk
【Linux】线程池(一)C++ 手写线程池:基于策略模式实现高性能日志模块

【Linux】线程池(一)C++ 手写线程池:基于策略模式实现高性能日志模块

文章目录 * 池化技术 * 线程池的日志模块 * 日志与策略模式 * 日志模块 * 两个核心问题 * 设计文件等级 * 刷新策略 * 获取日志时间 * logger类实现 * 内部类LogMessage实现 * 日志刷新流程图及源码 池化技术 池化技术可以减少很多的底层重复工作,例如创建进程、线程、申请内存空间时的系统调用和初始化工作,例如线程池,先预先创建好一些线程,当任务到来时直接将预先创建好的线程唤醒去处理任务,效率会远远高于任务到来时临时创建线程。例如内存池,但我们要用1mb空间时内存池会一次性申请20mb空间,效率会远远高于用多少空间申请多少空间(申请空间会调用系统调用)。 线程池是执行流级别的池化技术,STL中的空间配置器和内存池是内存块管理级别的池化技术。 线程池的日志模块 下⾯开始,我们结合我们之前所做的所有封装,进⾏⼀个线程池的设计。在写之前,我们要做如下准备。 * 准备线程的封装 * 准备锁和条件变量的封装 * 引⼊日志,对线程进⾏封装 日志与策略

By Ne0inhk