当前位置: 首页 > news >正文

c语言中实现线程同步的操作

线程

常见问题

同步权限

在多线程 / 多进程并发时,为避免共享资源(如内存变量、硬件设备、文件)被同时修改导致的数据不一致,需要通过 “同步机制” 控制谁能访问资源 ——“获取同步权限” 就是线程 / 进程申请这种访问资格的过程。

v4 = _InterlockedCompareExchange(a1, 1, 0);
  • 第一个参数 a1:指向目标内存地址的指针(通常是一个共享变量,如 LONG* 类型),即要操作的 “共享资源标记”。
  • 第二个参数 1:当比较成功时,要写入目标内存地址的 “新值”。
  • 第三个参数 0:“预期值”,即我们认为目标内存当前应该有的值。
  • 返回值是操作前目标内存地址(a1 指向的地址)中的原始值
// 定义一个共享的“锁标记”,0表示未占用,1表示已占用
LONG lock_flag = 0;// 线程A尝试获取锁
LONG original = _InterlockedCompareExchange(&lock_flag, 1, 0);
if (original == 0) {//获取到权限// 执行临界区操作...// 操作完成后释放锁(如将lock_flag设回0)
} else {// 获取锁失败(锁已被其他线程占用)// 可选择等待、重试或放弃
}

临界区

临界区(Critical Section) 指的是一段 “不能被多个线程同时执行” 的代码片段,当一个线程正在执行临界区代码时,其他线程必须等待该线程执行完毕,才能进入同一临界区。

原子性

“原子”(Atomic)描述的是一个不可分割、不可中断的操作单元。一个 “原子操作” 要么完整地执行完毕,要么完全不执行,中间不会被任何其他线程、进程或中断打断,不存在 “执行到一半” 的中间状态。

假设两个线程(Thread A、Thread B)同时对共享变量 count(初始值为 0)执行 count += 1 操作。
在底层会拆分为 3 个 CPU 指令:

  1. 从内存读取 count 的值到 CPU 寄存器(如 mov eax, [count]);
  2. 寄存器中的值加 1(如 inc eax);
  3. 将寄存器的值写回内存(如 mov [count], eax)。

如果操作不原子,可能出现以下 “交错执行”:

  • Thread A 执行步骤 1:读取 count=0 到寄存器;
  • 此时 CPU 切换到 Thread B,Thread B 执行步骤 1-3:读取 count=0 → 加 1→ 写回 count=1
  • CPU 切回 Thread A,继续执行步骤 2-3:寄存器值加 1(0→1)→ 写回 count=1

最终 count 的结果是 1,但预期是 2。

原子操作的本质

“原子性” 需要硬件(CPU)提供底层支持,再配合软件(操作系统、编程语言库)封装成易用的接口。

硬件层:CPU 的原子指令支持
不同架构的 CPU 会提供专门的 “原子操作指令”,确保单个指令的不可分割性:

  • x86/x86_64 架构:通过 lock 前缀实现原子性(如 lock cmpxchglock inc)。lock 前缀会让 CPU 在执行指令期间 “锁定系统总线”,阻止其他 CPU 核心同时访问该内存地址,确保指令执行不被打断;
  • ARM 架构:提供 ldrex(原子加载)、strex(原子存储)等指令,通过 “独占访问内存” 机制实现原子性;
  • RISC-V 架构:通过 amoswap.wamoadd.w 等 “原子内存操作指令”(AMO 指令)实现原子性。

这些硬件指令是 “原子操作” 的基石 —— 软件层面的原子接口(如 C++ 的 std::atomic、Windows 的 _InterlockedXXX)本质都是对这些 CPU 指令的封装。

软件层:原子操作的封装与扩展
硬件指令通常只支持 “单个内存地址的简单操作”(如加 1、比较交换),软件会在此基础上封装更灵活的原子操作:

原子操作

原子的交换操作
_InterlockedExchange(state_lock, 2);
  • 第一个参数 state_lock:指向目标内存地址的指针(通常是一个共享变量,如 LONG* 类型),即要被修改的 “状态标记”。
  • 第二个参数 2:要写入目标内存地址的 “新值”。
  • 返回值:原子地将 state_lock 指向的内存值更新为 2,同时返回该内存地址在更新前的原始值
原子比较交换操作
_InterlockedCompareExchange(&lock_flag, 1, 0);

参数1:类型为 volatile LONG*(指向 32 位有符号整数的指针),要操作的目标内存地址
参数2:类型为 LONG(32 位有符号整数),比较成功后要写入目标地址的值
参数3:类型为 LONG,表示预期的目标地址当前值(即 “旧值”)。
返回值:返回值为 LONG 类型,即目标地址在操作执行前的原始值

// 定义一个共享的“锁标记”,0表示未占用,1表示已占用
LONG lock_flag = 0;// 线程A尝试获取锁
LONG original = _InterlockedCompareExchange(&lock_flag, 1, 0);
if (original == 0) {//获取到权限// 执行临界区操作...// 操作完成后释放锁(如将lock_flag设回0)
} else {// 获取锁失败(锁已被其他线程占用)// 可选择等待、重试或放弃
}
原子地将目标变量的值加
_InterlockedIncrement(dword_14002BFF0);

参数dword_14002BFF0 是一个 LONG 类型(32 位)的共享变量(通常是全局或多线程可见的变量),表示要进行递增操作的目标。

返回值:函数返回递增后的新值LONG 类型)。

原子减 1
_InterlockedDecrement(a1)

参数:类型为 volatile LONG*(指向 32 位有符号整数的指针)
返回值:类型为 LONG(32 位有符号整数),表示减 1 操作完成后的结果值(即 *a1 - 1 的结果)。

线程同步

SRW 锁

SRW 锁支持两种获取模式:

  • 共享模式(Shared Mode):多个线程可同时获取,适用于 “只读操作” 场景(多个读者可并行访问资源)。
  • 独占模式(Exclusive Mode):仅允许一个线程获取,适用于 “修改操作” 场景(写者需独占资源)。
初始化SRW
void InitializeSRWLock(PSRWLOCK SRWLock
);

参数:PSRWLOCKSRWLOCK* 的类型别名,指向 SRWLOCK 结构体(轻量级读写锁的核心数据结构)。

核心作用:初始化 SRW 锁对象的内部状态,初始化后的 SRW 锁可通过以下函数实现读写分离的同步

  • 读操作:AcquireSRWLockShared(获取共享锁)和 ReleaseSRWLockShared(释放共享锁)。
  • 写操作:AcquireSRWLockExclusive(获取独占锁)和 ReleaseSRWLockExclusive(释放独占锁)。
读操作
void AcquireSRWLockShared(PSRWLOCK SRWLock
);

参数 SRWLock 是指向 SRWLOCK 结构体的指针,让线程以 “共享模式”(只读模式)安全地获取轻量级读写锁(SRW Lock)

void ReleaseSRWLockShared(PSRWLOCK SRWLock
);

参数 SRWLock 是指向 SRWLOCK 结构体的指针(PSRWLOCKSRWLOCK*),表示要释放的轻量级读写锁对象。

写操作
void AcquireSRWLockExclusive(PSRWLOCK SRWLock
);

让线程以 “独占模式” 获取 SRW 锁,确保对共享资源的修改操作(写操作)具有原子性

临界区

进出临界区
EnterCriticalSection(&stru_14002C030)

参数:类型LPCRITICAL_SECTION(即 CRITICAL_SECTION*,指向临界区结构体的指针)。
无返回值
作用:让当前线程 “获取临界区的访问权”

LeaveCriticalSection(&stru_14002C030);

作用:释放临界区

初始化临界区
void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection
);

参数 lpCriticalSection 是指向 CRITICAL_SECTION 结构体的指针(LPCRITICAL_SECTIONCRITICAL_SECTION* 的类型别名),表示要初始化的临界区对象。

异常:如果初始化失败(通常是由于系统资源不足),函数会触发一个异常(而非返回错误码)。因此在实际使用中,可能需要配合异常处理(如 __try/__except)捕获潜在错误。

作用:初始化临界区对象的内部状态,初始化后的临界区可通过 EnterCriticalSection(进入临界区)和 LeaveCriticalSection(离开临界区)实现。

使用示例

// 定义临界区对象(全局或栈上)
CRITICAL_SECTION CriticalSection;// 初始化临界区(通常在程序启动或模块初始化时调用)
InitializeCriticalSection(&CriticalSection);// 多线程场景中使用
void ThreadFunc() {// 进入临界区(获取同步权限)EnterCriticalSection(&CriticalSection);// 执行需要同步的操作(如访问共享资源)AccessSharedResource();// 离开临界区(释放同步权限)LeaveCriticalSection(&CriticalSection);
}// 程序退出前销毁临界区(释放资源)
DeleteCriticalSection(&CriticalSection);
临界区同步
TryEnterCriticalSection()

参数:LPCRITICAL_SECTION lpCriticalSection指向 CRITICAL_SECTION 结构体的指针(与 EnterCriticalSection 相同),表示要尝试进入的临界区对象,需提前通过 InitializeCriticalSection 初始化。

返回值: TRUE(非 0 值):表示成功进入临界区,FALSE(0):表示未能进入临界区

作用:非阻塞尝试进入临界区,与 EnterCriticalSection 的 “阻塞等待” 不同,TryEnterCriticalSection 的核心特点是 “尝试进入,失败立即返回”,适用于以下场景:

  • 当线程只需 “短暂尝试” 获取临界区,若失败则执行其他任务(而非等待),避免线程阻塞。
  • 实现 “超时等待” 逻辑(结合循环和 Sleep,多次尝试后放弃)。
临界区状态结构体
00000000 struct _RTL_CRITICAL_SECTION // sizeof=0x28
00000000 {                                       // XREF: .data:CriticalSection/r
00000000                                         // .data:__rtl_critical_section/r
00000000     PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
00000008     LONG LockCount;
0000000C     LONG RecursionCount;
00000010     HANDLE OwningThread;
00000018     HANDLE LockSemaphore;
00000020     ULONG_PTR SpinCount;
00000028 };

线程调度

CPU 时间片

SwitchToThread() 

返回值:

  • 返回 TRUE(非 0 值):表示当前有其他 “就绪状态” 的线程(属于同一优先级或更高优先级)被调度执行;
  • 返回 FALSE(0):表示当前没有其他就绪线程可调度(即系统中只有当前线程可运行)。

作用:

  • 当前线程主动放弃剩余的 CPU 时间片,让操作系统调度器重新选择一个就绪线程(通常是同优先级的其他线程)运行。

自旋等待

while ( 1 )
{v6 = _InterlockedCompareExchange(a1, 1, 0); // 原子比较交换:尝试将a1从0改为1if ( !v6 ) // v6=0表示成功获取权限(a1原本为0,已改为1)break;if ( v6 != 2 ) // 若a1当前为1(被其他线程占用),则主动让出CPUSwitchToThread();if ( *a1 == 2 ) // 若等待期间a1变为2(终止),则返回0return 0LL;
}

文章转载自:

http://wv2O6MuC.jwtwf.cn
http://CJUt4Gx5.jwtwf.cn
http://F2aVToR2.jwtwf.cn
http://FM97fs0q.jwtwf.cn
http://UYVTMj2Y.jwtwf.cn
http://CGraFRDX.jwtwf.cn
http://NWvUewEf.jwtwf.cn
http://uPwtP8PC.jwtwf.cn
http://ZUDD61kQ.jwtwf.cn
http://dGlEpTcP.jwtwf.cn
http://fpnUgNT2.jwtwf.cn
http://Oy9G4AtF.jwtwf.cn
http://2U8gXY5P.jwtwf.cn
http://1qRqlN2f.jwtwf.cn
http://uPKgwU60.jwtwf.cn
http://akWLwFzp.jwtwf.cn
http://s0ZXO1jh.jwtwf.cn
http://F9Sqtzxp.jwtwf.cn
http://wmdtKFOi.jwtwf.cn
http://hNt64R82.jwtwf.cn
http://hGFRNLMA.jwtwf.cn
http://tTvgX8we.jwtwf.cn
http://BzxM2dgV.jwtwf.cn
http://Xd9deRPi.jwtwf.cn
http://742gpKkQ.jwtwf.cn
http://EICpfSQu.jwtwf.cn
http://24MrL93N.jwtwf.cn
http://sDjUtLup.jwtwf.cn
http://iPv3aKXF.jwtwf.cn
http://dLiwThfr.jwtwf.cn
http://www.dtcms.com/a/383880.html

相关文章:

  • 【Java后端】Spring Boot 2.7.x 和 Swagger 3.0.x (springfox 3.x) 的兼容性问题
  • Springboot的自动配置原理?
  • 9 月 13 日科技前沿大揭秘:多领域创新闪耀
  • 基于少样本支持的一类学习的增量式生成对抗诊断:
  • TDengine 特殊选择函数 UNIQUE 用户手册
  • 状态机SMACH相关教程介绍与应用案例分析——机器人操作进阶系列 · 状态机篇
  • Transformer简介
  • 维星AI-AI驱动的精准获客:重塑数字营销新范式
  • 视觉SLAM第11讲:回环检测
  • Linux相关概念和易错知识点(45)(网络层、网段划分)
  • 因果推断 | 从因果树到因果森林:理论解析与代码实践
  • Spring MVC 九大组件源码深度剖析(七):ViewResolver - 视图解析的智慧
  • 【左程云算法09】栈的入门题目-最小栈
  • java设计模式三、创建者模式
  • 出现次数最多的字符 字符串处理
  • 根据IP获取用户信息和天气信息的方法
  • Paxos协议
  • 上网行为二层部署案例
  • XLua教程之热补丁技术
  • Linux 基本命令超详细解释第一期 | cd | pwd | ls | mkdir | rmdir
  • 如何查找 Linux 中 `dm-X` 设备对应的真实磁盘或虚拟机?
  • 线性稳压器LDO原理
  • 大模型浪潮来袭
  • 第6课:安全性与权限控制
  • 如何用 Rust 重写 SQLite 数据库(二):是否有市场空间?
  • pgsql 特有字段记录
  • Leetcode第165场双周赛题目详解+复盘
  • rt1180 rt1180处理器ethercat具体技术介绍
  • Sugov 关于频率变化
  • 多语言编码Agent解决方案(6)-部署和使用指南