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

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

相关文章

[职场] 公务员的利弊分析 #知识分享#经验分享#其他

公务员的利弊分析     公务员作为一种稳定的职业选择,一直备受人们的关注。然而,就像任何其他职业一样,公务员职位也有其利与弊。本文将对公务员的利弊进行分析,帮助读者更好地了解这一职业的特点。 利: 1. 稳定的职业:公务员职位通常具有较高的稳定性,一旦进入公务员队伍,往往可以享受到稳定的工作环境和薪资待遇。这对于那些追求稳定的人来说,是一个很大的优势。 2. 薪资福利优厚:公务员的薪资和

时间服务器中,适用于国内的 NTP 服务器地址,可用于时间同步或 Android 加速 GPS 定位

NTP 是什么?   NTP 是网络时间协议(Network Time Protocol),它用来同步网络设备【如计算机、手机】的时间的协议。 NTP 实现什么目的?   目的很简单,就是为了提供准确时间。因为我们的手表、设备等,经常会时间跑着跑着就有误差,或快或慢的少几秒,时间长了甚至误差过分钟。 NTP 服务器列表 最常见、熟知的就是 www.pool.ntp.org/zo

Java线程面试题(50)

不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题。Java语言一个重要的特点就是内置了对并发的支持,让Java大受企业和程序员的欢迎。大多数待遇丰厚的Java开发职位都要求开发者精通多线程技术并且有丰富的Java程序开发、调试、优化经验,所以线程相关的问题在面试中经常会被提到。 在典型的Java面试中, 面试官会从线程的基本概念问起, 如:为什么你需要使用线程,

线程池ThreadPoolExecutor类源码分析

Java并发编程:线程池的使用   在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:   如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。   那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?

线程Lock

线程Lock   在上一篇文章中我们讲到了如何使用关键字synchronized来实现同步访问。本文我们继续来探讨这个问题,从Java 5之后,在java.util.concurrent.locks包下提供了另外一种方式来实现同步访问,那就是Lock。   也许有朋友会问,既然都可以通过synchronized来实现同步访问了,那么为什么还需要提供Lock?这个问题将在下面进行阐述。本文先从s

线程封装,互斥

文章目录 线程封装线程互斥加锁、解锁认识接口解决问题理解锁 线程封装 C/C++代码混编引起的问题 此处pthread_create函数要求传入参数为void * func(void * )类型,按理来说ThreadRoutine满足,但是 这是在内类完成封装,所以ThreadRoutine函数实际是两个参数,第一个参数Thread* this不显示 解决方法: 第

Linux-笔记 线程同步机制

目录 前言 实现 信号量(Semaphore) 计数型信号量 二值信号量  信号量的原语操作 无名信号量的操作函数 例子 互斥锁(mutex) 互斥锁的操作函数 例子 自旋锁 (Spinlock) 自旋锁与互斥锁的区别 自旋锁的操作函数 例子 前言         线程同步是为了对共享资源的访问进行保护,确保数据的一致性,由于进程中会有多个线程的存在,

jmeter之Thread Group(线程组)

Thread Group(线程组) 1.线程组,或者可以叫用户组,进行性能测试时的用户资源池。 2.是任何一个测试计划执行的开始点。 3.上一篇提到的“控制器”和“HTTP请求”(采集器)必须在线程组内;监听器等其他组件,可以直接放在测试计划下。 线程组设置参数的意义 我们以下图为例,进行详细说明。见下图:  区域1(在取样器错误后要执行的动作) 这个区域的主要作用很明显,在线程内

如何在Android中实现多线程与线程池?

目录 一、Android介绍二、什么是多线程三、什么是线程池四、如何在Android中实现多线程与线程池 一、Android介绍 Android是一种基于Linux内核的开源操作系统,由Google公司领导开发。它最初于2007年发布,旨在为移动设备提供一种统一、可扩展的操作系统。Android系统以其高度的可定制性和丰富的应用生态而受到广泛欢迎,如今已经成为全球最流行的

java同步锁以及级别升级的理解

首先简单说下先偏向锁、轻量级锁、重量级锁三者各自的应用场景: 偏向锁:只有一个线程进入临界区;轻量级锁:多个线程交替进入临界区;重量级锁:多个线程同时进入临界区。 还要明确的是,偏向锁、轻量级锁都是JVM引入的锁优化手段,目的是降低线程同步的开销。比如以下的同步代码块:   synchronized (lockObject) { // do something } 上述同步代码块