线程池前置知识之线程同步

2024-06-14 23:12
文章标签 线程 知识 同步 前置

本文主要是介绍线程池前置知识之线程同步,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        在线程池中存在两个队列,一个任务队列用来缓存任务,一个线程队列用来缓存线程。在多线程环境下,需要考虑两个队列的线程同步问题。


线程互斥

        要判断一段代码是否能够被多线程执行,要看这段代码中是否存在竞态条件

        竞态条件(Race Condition)是指在并发环境中,当有多个线程或进程同时访问同一个临界资源时,由于多个线程的并发执行顺序的不确定,从而导致程序输出结果的不确定。这种情况发生在计算的正确性取决于多个线程的交替执行时序时。竞态条件可能导致程序运行顺序的改变,进而影响最终结果,产生超出预期的情况。 

        如果该代码片段存在竞态条件,那么则称这段代码区域为临界区,临界区是不能被并发访问且不可重入的,需要保证它的原子操作

        如果该代码片段不存在竞态条件,那么则称这段代码区域是可重入的。

        想要保证临界区的原子操作,可以使用互斥锁mutex或者原子类型atomic

        在进入临界区之前获取互斥锁、在离开临界区释放互斥锁,就能够保证在同一时间只有持有互斥锁的一个线程能够进入临界区执行代码而其他申请锁的线程只能挂起等待锁的释放,这样就能够保证临界区的原子操作。如果临界区不大,获取锁的速度非常快,也可以使用乐观锁来代替悲观锁,此时当持有锁的线程正在临界区执行代码,而其余线程在申请锁时就不会被挂起,而是一直申请锁,这样就不会让其余线程刚被阻塞挂起没多久就因锁被释放而唤醒。

        除了互斥锁,C++11还提供了CAS操作(无锁机制) atomic。无锁机制并不是不使用锁,而是使用一种基于活锁CAS(Compare And Swap)操作来实现。

        atomic可以使变量的++或--操作变成原子操作,那么如果临界区内只是对某个变量进行++或--操作,我们就可以将该变量变成atomic原子类型,这样就可以避免申请和释放锁的开销了。

#include <iostream>
#include <thread>
#include <atomic>
#include <vector>
using namespace std;int main() {int i = 0;atomic<int> j = 0;vector<thread> threads;threads.emplace_back([&i](int n)->void {while (n--) { ++i; } }, 100000);threads.emplace_back([&i](int n)->void {while (n--) { ++i; } }, 100000);threads.emplace_back([&j](int n)->void {while (n--) { ++j; } }, 100000);threads.emplace_back([&j](int n)->void {while (n--) { ++j; } }, 100000);for (auto& thread : threads) {thread.join();}cout << "i = " << i << " " << "j = " << j << endl;return 0;
}


线程通信

        现有线程1、线程2两个执行流,线程1执行的任务1依赖于线程2执行的任务2得到的结果,虽然图上任务2的执行顺序在任务1之前,但是由于线程的调度完全是由系统内核的调度算法决定的,所以CPU先执行哪个任务是不确定的,为了保证任务2在任务1之前被执行,就需要线程之间进行通信,如果任务1先被调度执行就进入等待状态,等任务2执行完毕了再来继续执行。为了完成线程间通信,我们可以借助条件变量condition_variable信号量semaphore

        谈到线程间通信我们就需要想到多线程模型中的生产者消费者模型。 生产者消费者模型可以被称为“三二一原则”,

        三指的是三种关系

        ① 生产者与生产者之间的互斥关系;

        ② 生产者与消费者之间的互斥且同步关系;

        ③ 消费者与消费者之间的互斥关系。

        二指的是两种角色:生产者和消费者。

        一指的是一个交易场所:缓冲区。

        生产者消费者模型可以让生产者和消费者进行解耦,也支持并发和忙闲不均

        借助条件变量和互斥锁(条件变量必须要和互斥锁结合起来使用,这也使条件变量的控制精细程度比信号量要好),我们可以来实现生产者消费者模型:

        详细可以参考:

        用条件变量构建的阻塞队列来完成生产消费者模型

        信号量是一种用于控制对共享资源的访问的机制。它可以用来限制同时访问某个资源的线程数量。在上文中我们谈到一把互斥锁最多只能被一个线程所获取,那么我们就可以把互斥锁看作是一个资源计数只能是0或1的资源。那为什么现在要说这个呢?是因为我们可以把信号量看作一个资源计数没有限制的互斥锁,当信号量只在0和1之间变动时(互斥量、二元信号量),就可以把信号量看作成一把轻量的互斥锁。但是互斥量实现的互斥锁还是与真正的互斥锁有所区别的,互斥锁只能是哪个线程获取的锁,哪个线程释放;而互斥量可以由不同的线程来acquire和release

        信号量一般不依赖于互斥锁,它可以独自使用,如本文线程通信的导入问题,让任务2先于任务1执行就可以用信号量来解决。但是信号量也可以与互斥锁一起使用来实现生产者消费者模型:

        注: 在使用信号量时,一定要先申请信号量再申请互斥锁。因为互斥锁只能由一个线程持有,如果先申请互斥锁的话,那么除了持有互斥锁的线程,其余的线程就会被阻塞在互斥锁处,而去申请信号量,那么信号量就只会被持有锁的线程申请和释放,信号量就形同虚设了。

        详情可以参考:

        用posix信号量实验环形队列并完成生产消费者模型 

        C++20 semaphore(信号量) 详解 

        总结起来,信号量主要用于控制对共享资源的访问,而条件变量主要用于线程间的通信和协作。它们在不同的场景下有不同的作用和用途。 

这篇关于线程池前置知识之线程同步的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1061767

相关文章

Java中如何正确的停掉线程

《Java中如何正确的停掉线程》Java通过interrupt()通知线程停止而非强制,确保线程自主处理中断,避免数据损坏,线程池的shutdown()等待任务完成,shutdownNow()强制中断... 目录为什么不强制停止为什么 Java 不提供强制停止线程的能力呢?如何用interrupt停止线程s

Python与MySQL实现数据库实时同步的详细步骤

《Python与MySQL实现数据库实时同步的详细步骤》在日常开发中,数据同步是一项常见的需求,本篇文章将使用Python和MySQL来实现数据库实时同步,我们将围绕数据变更捕获、数据处理和数据写入这... 目录前言摘要概述:数据同步方案1. 基本思路2. mysql Binlog 简介实现步骤与代码示例1

python 线程池顺序执行的方法实现

《python线程池顺序执行的方法实现》在Python中,线程池默认是并发执行任务的,但若需要实现任务的顺序执行,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋... 目录方案一:强制单线程(伪顺序执行)方案二:按提交顺序获取结果方案三:任务间依赖控制方案四:队列顺序消

Unity新手入门学习殿堂级知识详细讲解(图文)

《Unity新手入门学习殿堂级知识详细讲解(图文)》Unity是一款跨平台游戏引擎,支持2D/3D及VR/AR开发,核心功能模块包括图形、音频、物理等,通过可视化编辑器与脚本扩展实现开发,项目结构含A... 目录入门概述什么是 UnityUnity引擎基础认知编辑器核心操作Unity 编辑器项目模式分类工程

C#控制台程序同步调用WebApi实现方式

《C#控制台程序同步调用WebApi实现方式》控制台程序作为Job时,需同步调用WebApi以确保获取返回结果后执行后续操作,否则会引发TaskCanceledException异常,同步处理可避免异... 目录同步调用WebApi方法Cls001类里面的写法总结控制台程序一般当作Job使用,有时候需要控制

SpringBoot实现虚拟线程的方案

《SpringBoot实现虚拟线程的方案》Java19引入虚拟线程,本文就来介绍一下SpringBoot实现虚拟线程的方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录什么是虚拟线程虚拟线程和普通线程的区别SpringBoot使用虚拟线程配置@Async性能对比H

在Java中实现线程之间的数据共享的几种方式总结

《在Java中实现线程之间的数据共享的几种方式总结》在Java中实现线程间数据共享是并发编程的核心需求,但需要谨慎处理同步问题以避免竞态条件,本文通过代码示例给大家介绍了几种主要实现方式及其最佳实践,... 目录1. 共享变量与同步机制2. 轻量级通信机制3. 线程安全容器4. 线程局部变量(ThreadL

Linux线程同步/互斥过程详解

《Linux线程同步/互斥过程详解》文章讲解多线程并发访问导致竞态条件,需通过互斥锁、原子操作和条件变量实现线程安全与同步,分析死锁条件及避免方法,并介绍RAII封装技术提升资源管理效率... 目录01. 资源共享问题1.1 多线程并发访问1.2 临界区与临界资源1.3 锁的引入02. 多线程案例2.1 为

Java中的xxl-job调度器线程池工作机制

《Java中的xxl-job调度器线程池工作机制》xxl-job通过快慢线程池分离短时与长时任务,动态降级超时任务至慢池,结合异步触发和资源隔离机制,提升高频调度的性能与稳定性,支撑高并发场景下的可靠... 目录⚙️ 一、调度器线程池的核心设计 二、线程池的工作流程 三、线程池配置参数与优化 四、总结:线程

WinForm跨线程访问UI及UI卡死的解决方案

《WinForm跨线程访问UI及UI卡死的解决方案》在WinForm开发过程中,跨线程访问UI控件和界面卡死是常见的技术难题,由于Windows窗体应用程序的UI控件默认只能在主线程(UI线程)上操作... 目录前言正文案例1:直接线程操作(无UI访问)案例2:BeginInvoke访问UI(错误用法)案例