线程同步

由于线程共享内存,访问共享数据(全局变量、堆内存)必须进行同步,以防止竞态条件(Race Conditions)导致数据不一致或程序崩溃。

子线程没有独立的地址空间,数据通常是共享的;如果同时访问数据会导致混乱,需要进行同步控制。

同步的核心思想是让某个线程在需要时获取资源(锁),其他线程在资源不可用时等待,直到资源可用再继续执行。
线程之间的同步通常通过两种锁来表达关系:

  • 互斥锁(mutex):确保同一时间只有一个线程能够访问共享资源。
  • 条件变量(condition variable):用于在某个条件成立时通知等待中的线程继续执行。

线程序列的执行需要保证执行的先后次序,常用的策略包括:

  • 先获取锁、再检查条件、在必要时等待(阻塞);
  • 条件满足时发出通知,唤醒等待的线程继续执行。
  • 线程之间的协作与耦合要尽量降低,避免过度依赖全局状态,减少死锁风险。
  • 资源管理要清晰,避免“资源占用-等待-再次占用”的循环,使并发程序保持高效。

理解futex

  • futex 是 Linux 提供的一个系统调用 (futex()) 和一种同步原语的构建思想

  • 它设计用于在用户空间和内核空间之间实现高效的同步(如互斥锁、读写锁、条件变量、信号量等)。

核心思想

  • 无竞争时走快速路径(用户空间): 在锁空闲(或条件满足)的情况下,同步操作(如加锁、解锁)完全在用户空间通过原子操作(如 cmpxchg)完成。这避免了昂贵的系统调用和内核态/用户态切换的开销。

  • 竞争时走慢速路径(内核): 当操作无法立即完成(如锁已被占用、条件未满足),需要阻塞线程时,才会通过 futex() 系统调用进入内核。内核负责将线程挂起在 futex 关联的地址(uaddr)上,并在必要时(如解锁操作唤醒、条件满足)将其唤醒。

  • futex 本身只关注对一个用户空间地址 (uaddr) 的等待 (FUTEX_WAIT) 和唤醒 (FUTEX_WAKE) 操作。更复杂的同步原语(如互斥锁)是利用这个基础能力在用户空间构建的。

与线程的关系?

  • 高效同步的基石: 现代 Linux POSIX 线程库(NPTL)使用 futex 作为其所有主要同步机制(互斥锁、条件变量、读写锁、屏障)的基础实现。

  • 性能关键: 多线程程序中同步操作非常频繁。futex 的设计极大地降低了无竞争或低竞争情况下同步操作的开销,这是实现高性能多线程程序的关键。

  • API 透明性: 开发者仍然使用标准的 pthread_mutex_lock() 等 Pthreads API。futex 是这些 API 在 Linux 上高性能实现的底层秘密武器。

  • 核心作用: 提供一种用户态优先的机制,使得在 Linux 上实现高性能的线程同步原语成为可能。它是 NPTL 高性能的关键因素之一。

简单来说

  • futex: Linux 实现高效同步的底层原语。pthread_mutex_lock/unlock(),
  • 你用的锁和条件变量,在 Linux 里靠 futex 才能那么快。

互斥锁

  • (pthread_mutex_t)

  • 最基本的同步原语。保证同一时刻只有一个线程能进入被保护的临界区 (Critical Section)。

  • 实现: NPTL 利用 futex 实现高效的互斥锁。无竞争时,加锁/解锁操作完全在用户空间完成(原子操作)。当发生竞争需要阻塞线程时,才会陷入内核。

例:
在这里插入图片描述

  • a++ 需要执行 3 个步骤:
    • 取 a 的值
    • 计算 a+1
    • a+1 赋值给 a
  • 某一时刻,若全局变量 a 的值为 5,线程 A 和线程 B 中都要执行 a++,若线程 A 在执行 a+1 赋值给 a 之前,这时线程 B 执行了 a++,线程 B 取到的 a 的值仍是 5,这时,线程 A 和 B 赋给 a 的值都是 6。值为 5 的变量 a 经过两次 a++ 结果却是 6,显然出现了错误。
  • 多线程竞争操作共享变量的这段代码也会出错,俗称临界区。多个进程同时操作临界区会产生错误,所以这段代码应该互斥,当一个线程执行临界区时,应该阻止其他线程进入临界区。
  • 为避免线程更进一步共享变量时出现问题,可以使用互斥量(mutex 是 mutual exclusion 的缩写)来确保同一时刻只有一个线程可以访问共享资源。
  • 互斥量的作用类似于一个“锁”,当一个线程请求共享资源时,它必须先获取互斥量的锁。如果互斥量当前没有被其他线程占用,那么这条线程就获得锁,并可以访问共享资源;如果互斷量已经被其他线程占用,那么该线程将被阻塞,直到互斥量的锁被释放。
  • 互斥量有两种状态:已锁定(locked)和未锁定(unlocked)。至多只有一个线程可以锁定该互斥量,试图再次锁定时将会阻塞,等待前一个线程释放锁。
    在这里插入图片描述

死锁现象

当多个线程为了保护多个共享资源而使用了互斥锁,如果多个互斥锁使用不当,就可能造成,多个线程一直等待对方的锁释放,这就是死锁现象
在这里插入图片描述
死锁产生的四个必要条件

  • 互斥条件:资源只能被一个进程占用
  • 持有并等待条件:线程1已经持有资源A,可以申请持有资源B,如果资源B已经被线程2持有,这时线程1持有资源并等待资源B
  • 不可剥夺条件:一个线程持有资源,只能自己释放后其他线程才能使用。其他线程不能强制收回资源
  • 环路等待条件:多个线程互相等待资源,形成一个环形等待链

避免死锁的常用方法如下

  • 锁的粒度控制:破坏请求与保持条件,尽量减少持有锁的时间,降低发生死锁的可能性
  • 资源有序分配:破坏环路等待条件,规定线程使用资源的顺序,按规定顺序给资源加锁
  • 重试机制:破坏不可剥夺条件,如果尝试获取资源失败,放弃已持有的资源后重试

条件变量

条件变量,通知状态的改变。 条件变量允许一个线程就某个共享变量的状态变化通知其他线程,并让其他线程等待(阻塞于)这一通知。

条件变量是结合互斥量使用。 条件变量就共享变量的状态改变发出通知,而互斁量则提供对该共享变量访问的互斥。

演示程序

  • 定义一个变量 a,线程 1 当 a 为 0 时对 a+1,线程 2 当 a 为 1 时对 a-1,循环 10 次, a 的结果应为 0 和 1 交替。

两个线程需要不断的轮询结果,造成 CPU 浪费。可以使用条件变量解决:线程 1 不满足运行条件时,先休眠等待,其他线程运行到满足条件时,通知并喚醒线程 2 继续执行。

  • (pthread_cond_t)

  • 用于线程间的等待/通知机制。允许线程在某个条件不满足时阻塞自己,并等待其他线程在条件可能变为真时唤醒它。

  • 必须与互斥锁配合使用等待操作 (pthread_cond_wait) 会原子地释放互斥锁并阻塞线程。当被唤醒时,它会重新获取互斥锁。

读写锁

读写锁, 中读锁和写锁两部分组成, 读取资源时用读锁, 修改资源时用写锁。 其特性为:写独占,读共享(读先锁)。 读写适合多写少写的场景。

读写锁的工作原理

  • 没有线程持有写锁时, 所有线程都可以一起持有读锁
  • 有线程持有写锁时, 所有的读锁和写锁都会阻塞

读写优先锁: 有线程持有读锁, 这时有一个读线程和一个写线程想要获取锁, 读线程会优先获取锁 就是读优先锁, 反过来就是写优先锁。

  • (pthread_rwlock_t)

  • 允许多个读线程同时访问共享资源,但只允许一个写线程独占访问。适用于读多写少的场景。

  • 实现: 同样基于 futex 或类似机制。

场景分析

  • 持有读锁时,申请读锁: 全部直接加锁成功,不需要等待
  • 持有写锁时,申请写锁: 申请写锁阻塞等待,写锁释放再申请加锁
  • 持有读锁时, 申请写锁: 写锁阻塞
  • 持有写锁时, 申请读锁: 读锁阻塞
  • 持有读锁时, 申请写锁和读锁: 申请的读锁和写锁都会阻塞,当持有的写锁释放时,读锁先加锁成功
  • 持有读锁时, 申请写锁和读锁: 申请的写锁阻塞,读锁加锁成功, 写锁阻塞到读锁全部解锁才能加锁,在此期间可能一直有读锁申请,会导致写锁一直无法申请成功,造成锁死

自旋锁

  • (pthread_spinlock_t)

  • 一种忙等待的锁。线程在尝试获取锁失败时,不会立即阻塞,而是在一个循环中不断检查锁的状态(“自旋”),直到锁可用。

  • 适用场景: 临界区非常短,且线程持有锁的时间极短,预期等待时间小于线程阻塞/唤醒的开销。在用户空间,自旋锁通常只适用于非抢占式内核或绑定到特定 CPU 的线程,否则在单核上或持有锁的线程被抢占可能导致其他线程长时间无意义自旋浪费 CPU。

  • 内核中广泛使用

屏障

  • (pthread_barrier_t)

  • 允许多个线程在一个公共点(屏障)上相互等待,直到所有参与的线程都到达屏障点,然后所有线程再继续执行。

信号量

信号量是操作系统提供的一种协调共享资源访问的方法。信号量是由内核维护的整形变量(sem),它的值表示可用资源的数量

  • (sem_t - POSIX 有名/无名信号量)

  • 一种更通用的同步机制,维护一个计数器。可以用来控制对多个共享资源的访问(计数信号量),也可以用作二值信号量(类似互斥锁)。

  • 内核信号量 (down(), up()) 与用户空间信号量 (sem_wait(), sem_post()) 不同。 用户空间 POSIX 信号量有进程共享选项。

对信号量的原子操作

  • P操作
    • 如果有可用资源(sem>0),占用一个资源(sem-1)
    • 如果没有可用资源(sem=0),进程或线程阻塞,直到有资源
  • V操作
    • 如果没有进程或线程在等待资源,释放一个资源(sem+1)
    • 如果有进程或线程在等待资源,唤醒一个进程或线程

P操作和V操作成对出现,进入临界区前进行P操作,离开临界区后进行V操作
在这里插入图片描述

生产者消费者模型

线程同步典型案例即为生产消费者模型,而借助条件变量来实现这一模型,是比较常见的一种方法。假定有两个线程,一个模拟生产者行为,一个模拟消费者行为。两个线程同时操作一个共享资源(一般称之为汇聚),生产者向其中添加产品,消费者从中消费掉产品。
在这里插入图片描述
相较于互斥量而言,条件变量可以减少竞争。如直接使用互斥量,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥量是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。

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

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

相关文章

世界模型的典型框架与分类

1.概述 人类和动物智能的一个重要方面是我们对世界的内部模型。我们使用这个模型来预测我们的行为将如何影响我们的环境,预测未来的事件,并计划复杂的行动序列以实现目标。当前大多数机器学习研究都集中在被动理解数据的模型上,例如图像分类…

【Day 35】Linux-Mysql错误总结

(一)MySQL 基础操作与服务故障类 连接层错误(客户端与服务器的连接建立失败) 解决 socket 路径、文件存在性及服务可用性问题。 1、MySQL 客户端连接失败(报错 “Cant connect to local MySQL server throgh socket…

MYSQL速通(2/5)

六、多表查询1、多表关系①、一对多(多对一)举例:一个部门对多个员工实现:多的那边建立外键,指向一的那边的主键②、多对多举例:一个学生可选多门课,一门课可被多个学生选实现:建立中…

CRM、ERP、HRP系统有啥区别?

要理解CRM、ERP、HRP系统,需先明确三者的核心定位(聚焦客户、企业全资源、特定领域资源),再从管理范围、目标、用户等维度区分。以下是详细解析: 一、各系统核心定义与核心模块 1. CRM系统:客户关系管理系统…

【系统分析师】高分论文:论系统测试技术及应用

【摘要】 2022 年 7月,我作为项目负贵人,参加了某银行的统计数据发布系统建设项目。该项目合同金额230 万元,合同工期为半年。统计数据发布系统的主要目标是为该行建设一个企业级的数据统计、分析、发布平台,实现定制化的数据应用…

第5篇 c++ 函数的多返回值实现-返回多个值

c 函数的多返回值实现std::tuple<Mat, int, double, std::string> AuatoPafackSydstem::GetMatchingValue(Mat mat_img, std::string img_template_path) {Mat a;return {a,1,0.001,""}; }std::tuple<Mat, int, double, std::string> GetMatchingValue(M…

C++基础(⑤删除链表中的重复节点(链表 + 遍历))

题目描述 给定一个排序好的链表&#xff08;升序&#xff09;&#xff0c;删除所有重复的元素&#xff0c;使每个元素只出现一次。 示例&#xff1a; 输入&#xff1a;1 → 1 → 2 → 3 → 3 输出&#xff1a;1 → 2 → 3 解题思路 核心观察&#xff1a;链表已排序&#xff0c;…

摩搭api 实现

AI图片生成器前端实现详解本文详细解析一个功能完整的AI图片生成器前端实现&#xff0c;包含主题切换、参数配置、图片生成与预览等核心功能。项目概述 这是一个基于ModelScope平台的AI图片生成器前端实现&#xff0c;用户可以通过输入提示词、选择模型和调整参数来生成高质量图…

c++--线程休眠/sleep

线程休眠<unistd.h>--sleep()<thread>--std::this_thread::sleep_for()ros--rclcpp::sleep_for()以上的三个sleep也就是休眠会占用CPU吗简单直接的回答是&#xff1a;不会。一个正确使用、正在休眠的线程不会占用CPU资源。核心原理当你调用像 std::this_thread::sl…

人工智能基础概念

The brain happens to be a meat machine. —— Marvin Lee Minsky 目录 人类智能 人工智能 三个阶段 三大学派 四个要素 关系辨析 维度分类 发展简史 技术方向 应用领域 产业图谱 发展趋势 人类智能 人工智能的目的是通过探索智慧的实质&#xff0c;扩展人类智能…

企业数字资产管理怎么转型更高效?一文打通策略与实践

核心要点 问题&#xff1a; 传统的内容管理方式效率低、协作难、版本混乱&#xff0c;企业转型 DAM 系统面临选型难和落地难两大挑战。 答案&#xff1a; 采用阶段性转型策略&#xff0c;结合智能化工具和标准化流程&#xff0c;能帮助企业构建可扩展、易协作、强治理的内容资…

python pyqt5开发DoIP上位机【源码】

目录 文章合集 文章合集 python pyqt5开发DoIP上位机【介绍】 python pyqt5开发DoIP上位机【代码里发送常用uds命令是怎么实现的?】 python pyqt5开发DoIP上位机【代码里是怎么获取诊断回复的?】 python pyqt5开发DoIP上位机【诊断回复的函数都是怎么调用的?】 python …

IIC (I2C)通信

一、IIC的特点I2C&#xff08;Inter IC Bus&#xff09;又成为&#xff08;爱方C I2C&#xff09;是由Philips公司开发的一种通用数据总线两根通信线&#xff1a;SCL&#xff08;Serial Clock&#xff09;、SDA&#xff08;Serial Data&#xff09;同步&#xff0c;半双工带数据…

【图像处理 - 基础知识】ISP(Image Signal Processor)处理

文章目录1. 图像传感背景2. 图像数据格式2.1. 【RAW】Bayer滤色阵列&#xff08;Bayer Filter Array&#xff09;2.2. 【RGB】2.3. 【YUV】3. ISP&#xff08;Image Signal Processor&#xff09;3.1. ISP定义3.2. 分类概括1 RAW 域&#xff08;Raw Domain&#xff09;2 RGB 域…

PostgreSQL 数据库灾备要点与举例说明**

PostgreSQL 数据库灾备要点与举例说明 数据库灾备(Disaster Recovery, DR)是确保业务连续性的核心环节,其目标是在主生产环境遭遇计划外中断(如硬件故障、自然灾害、人为错误、数据损坏)时,能快速恢复数据和服务。PostgreSQL 因其强大和开源特性,提供了多种高效的灾备方…

【GaussDB】在逻辑复制中剔除指定用户的事务

【GaussDB】在逻辑复制中剔除指定用户的事务 1. 需求背景 在逻辑复制中&#xff0c;期望对源端指定用户的所有操作不复制到目标端。然而WAL日志中没有用户信息可用于过滤&#xff0c;因此考虑结合审计日志来实现这一需求。 2. 解决方案 2.1 配置审计日志 首先需要开启事务…

C/C++---预定义常量

在C/C开发中&#xff0c;预定义常量是编译器与标准库提供的“隐形工具集”&#xff0c;无需开发者显式定义&#xff0c;却在跨平台兼容、调试定位、数值计算安全等场景中发挥核心作用。这些常量涵盖编译环境标识、语言标准判断、代码定位信息、数值边界限制等多个维度&#xff…

嵌入式学习笔记--LINUX系统编程--DAY03进程控制

1.进程概述进程和程序的区别程序&#xff1a;程序是存储在存储介质上的一个可执行文件---静态的进程&#xff1a;进程是程序的执行实例。可以说进程就是正在执行的程序。程序是一些指令的集合&#xff0c;而进程是程序的执行过程&#xff0c;这个过程的状态是变化的&#xff0c…

【90页PPT】新能源汽车数字化转型SAP解决方案(附下载方式)

篇幅所限&#xff0c;本文只提供部分资料内容&#xff0c;完整资料请看下面链接 https://download.csdn.net/download/2501_92808859/91777751 资料解读&#xff1a;《新能源汽车数字化转型SAP解决方案》 ​​详细资料请看本解读文章的最后内容​​ 在全球汽车产业加速向电…

LSM6DS3姿态芯片和LIS2MD磁力计芯片数据读取(stm32)

上代码main.c 代码示例#include "lsm6ds3.h" #include "lis2md.h"int16_t Acc[3] {0}, GYO[3] {0},Mag[3] {0};int main(void) {I2C_GPIO_Config(); //配置IIC使用端口Delayms(500); //延时Lsm6ds3_Init( ); …