【Windows线程开发】Windows线程同步技术
我们在上一章节中讲解了关于Windows的线程基础,相信大家已经对线程有了基本的概念。这一章节中,我们来讲讲线程同步技术,包括加锁技术(原子锁和互斥体)和事件,信号量。
文章目录
- 一.原子锁
- 二.互斥体
- 三.事件
- 四.信号量
一.原子锁
原子锁主要解决的问题是多线程在操作符方面的问题。
- 相关问题:
多个线程对同一个数据进行原子操作时,会产生结果丢失,比如++运算符
我们来写一段代码看看多线程在操作同一个数据的时候出现的问题:
#include <stdio.h>
#include <windows.h>DWORD WINAPI ThreadProc1(LPVOID lpParameter);
DWORD WINAPI ThreadProc2(LPVOID lpParameter);
int g_value = 0;int main() {DWORD nID = 0;HANDLE hThread1 = CreateThread(NULL, 0, ThreadProc1, NULL, 0, &nID);HANDLE hThread2 = CreateThread(NULL, 0, ThreadProc2, NULL, 0, &nID);WaitForMultipleObjects(2, hThread, TRUE, INFINITE);printf("%d\n", g_value);return 0;
}DWORD WINAPI ThreadProc1(LPVOID lpParameter) {for (int i = 0; i < 100000000; i++) {g_value++;}return 0;
}DWORD WINAPI ThreadProc2(LPVOID lpParameter) {for (int i = 0; i < 100000000; i++) {g_value++;}return 0;
}
- 代码解释
我们创建两个线程,同时对全局变量g_value进行自增操作,两个线程分别自增100000000次,那么最后结果就应该是200000000,我们来看看执行结果:
我们发现,最后结果并不是200000000,那么是为什么呢?
我们来分析一下错误:
当线程A执行g_value++时,如果线程切换正好是在线程A将结果保存到g_value之前,线程B继续执行g_value++,那么当线程A再次被切换回来之后,会继续上一步的操作,继续将值保存到g_value中,线程B的计算结果被覆盖
通俗点来说,就是线程A计算好了g_value的结果,但是还没有保存到g_value,这时候线程切换到了B线程,线程B完成了计算,并且成功保存,当返回到A线程的时候,A线程会继续上一步的保存操作,那么B线程的计算结果就被覆盖掉了。
那么如何来解决这样的问题呢?那就要用到我们的线程同步技术—原子锁了:
- 原子锁函数:
InterlockedIncrement()
InterlockedDecrement()
InterlockedCompareExcahnge()
InterlockedExchange()
我们在上文中提到,原子锁主要针对的是运算符的问题,每一种运算符都有原子锁函数
我们来看看使用效果:这里以++运算符为例:
#include <stdio.h>
#include <windows.h>DWORD WINAPI ThreadProc1(LPVOID lpParameter);
DWORD WINAPI ThreadProc2(LPVOID lpParameter);
DWORD g_value = 0;int main() {DWORD nID = 0;HANDLE hThread[2] = { 0 };hThread[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, &nID);hThread[1] = CreateThread(NULL, 0, ThreadProc2, NULL, 0, &nID);WaitForMultipleObjects(2, hThread, TRUE, INFINITE);printf("%d\n", g_value);return 0;
}DWORD WINAPI ThreadProc1(LPVOID lpParameter) {for (int i = 0; i < 100000000; i++) {InterlockedIncrement(&g_value);}return 0;
}DWORD WINAPI ThreadProc2(LPVOID lpParameter) {for (int i = 0; i < 100000000; i++) {InterlockedIncrement(&g_value);}return 0;
}
我们来看看执行效果:
我们可以发现,当我们使用原子锁的时候,两个线程操作同一个数据,就不会出现结果丢失的问题了。但是我们也不难发现,执行结果慢了很多,这是因为执行过程中多了很多等待事件,这个等待我们在互斥中会讲到。
原子锁的实现:直接对数据所在的内存操作,并且在任何一个瞬间,只能有一个线程访问
二.互斥体
- 相关问题
跟原子锁一样,都是解决多线程下资源的共享使用,但是与原子锁不同的是,互斥体解决的是代码资源的共享使用。
- 互斥体的使用:
-
- 创建互斥体:
使用CreateMutex
函数:
MSDN官方文档解释
- 创建互斥体:
-
HMODLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes, //安全性BOOL bInitialOwner, //初始拥有者LPCSTR lpName //为互斥命名
);
参数bInitialOwner介绍:
如果此值为 TRUE ,并且调用方创建了互斥体,则调用线程获取互斥体对象的初始所有权。 否则,调用线程不会获取互斥体的所有权。
互斥体特性介绍:
- 在任何一个时间点上,只能由一个线程拥有互斥体
- 当前任何一个线程不拥有互斥体是,互斥体句柄有信号
- 谁先等候互斥体,谁先获取
-
- 等候互斥体:
上一篇介绍过了,使用等候句柄函数。WaitFor...
- 等候互斥体:
-
- 释放互斥体
BOOL ReleaseMutex(HANDLE hMutex //handle of Mutex
);
-
- 关闭互斥体
使用CloseHandle
函数
我们来看看使用互斥体来解决我们在多线程中遇到的问题:
- 关闭互斥体
#include <stdio.h>
#include <windows.h>DWORD WINAPI ThreadProc1(LPVOID lpParameter);
DWORD WINAPI ThreadProc2(LPVOID lpParameter);
DWORD g_value = 0;
HANDLE hMutex = NULL; //用于接收互斥体句柄int main() {DWORD nID = 0;HANDLE hThread[2] = { 0 };//hMutex = CreateMutex(NULL, FALSE, NULL);hThread[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, &nID);hThread[1] = CreateThread(NULL, 0, ThreadProc2, NULL, 0, &nID);WaitForMultipleObjects(2, hThread, TRUE, INFINITE);printf("%d\n", g_value);CloseHandle(hMutex);return 0;
}DWORD WINAPI ThreadProc1(LPVOID lpParameter) {char a[] = "********";while (1) {//WaitForSingleObject(hMutex, INFINITE);for (int i = 0; i < strlen(a); i++) {printf("%c", a[i]);Sleep(125);}printf("\n");//ReleaseMutex(hMutex);}return 0;
}DWORD WINAPI ThreadProc2(LPVOID lpParameter) {char b[] = "--------";while (1) {//WaitForSingleObject(hMutex, INFINITE);for (int i = 0; i < strlen(b); i++) {printf("%c", b[i]);Sleep(125);}//ReleaseMutex(hMutex);printf("\n");}return 0;
}
我们来看看不适用互斥体技术的时候的输出:
我们再来看看使用了互斥体之后:
#include <stdio.h>
#include <windows.h>DWORD WINAPI ThreadProc1(LPVOID lpParameter);
DWORD WINAPI ThreadProc2(LPVOID lpParameter);
DWORD g_value = 0;
HANDLE hMutex = NULL; //用于接收互斥体句柄int main() {DWORD nID = 0;HANDLE hThread[2] = { 0 };hMutex = CreateMutex(NULL, FALSE, NULL);hThread[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, &nID);hThread[1] = CreateThread(NULL, 0, ThreadProc2, NULL, 0, &nID);WaitForMultipleObjects(2, hThread, TRUE, INFINITE);printf("%d\n", g_value);CloseHandle(hMutex);return 0;
}DWORD WINAPI ThreadProc1(LPVOID lpParameter) {char a[] = "********";while (1) {WaitForSingleObject(hMutex, INFINITE);for (int i = 0; i < strlen(a); i++) {printf("%c", a[i]);Sleep(125);}printf("\n");ReleaseMutex(hMutex);}return 0;
}DWORD WINAPI ThreadProc2(LPVOID lpParameter) {char b[] = "--------";while (1) {WaitForSingleObject(hMutex, INFINITE);for (int i = 0; i < strlen(b); i++) {printf("%c", b[i]);Sleep(125);}ReleaseMutex(hMutex);printf("\n");}return 0;
}
我们可以发现使用互斥体之后,对代码段进行了枷锁。
我们来大致讲解一下互斥体的实现吧:
我们在主进程中创建了互斥体,并且互斥体不归之进程所有,两个线程谁先等待互斥体句柄,谁就拥有了互斥体,那么当线程跳转到另一个线程之后,发现被锁定在了另一个线程,那么线程就会被阻塞,直到线程再次跳转到另一个线程,执行完之后,互斥体被释放,这时候跳转到这个线程,在这个线程中再进行加锁,这个线程执行完之后,再锁定到另一个线程,这样就实现了加锁技术。
三.事件
前两个技术都属于加锁技术,即两个线程互斥的时候使用,那么线程也会有协调工作的时候,这时候就需要用到我们的事件和信号量了。
-
相关问题:
多线程协调工作的时候的通知问题 -
事件的使用
- 创建事件:
使用CreatEvent
函数,MSDN官方解释
- 创建事件:
HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes, //安全属性BOOL bManualReset, //事件重置/复位方式BOOL bInitialState, //事件初始状态LPCSTR lpName //为事件命名
);
- 参数解释:
- bManualReset为事件重置/复位方式,如果该参数被设置为TRUE那么就需要我们来手动重置事件对象,如果该参数被设置为FALSE,那么操纵系统会帮我们完成事件的重置和复位。
- bInitialState:该参数指定了当创建事件后,该事件句柄是否处于有消息状态
- 等候事件:
WaitFor......
函数 - 触发事件(使事件句柄处于有消息状态)
BOOLSetEvent(HANDLE hEvent
);
- 复位事件(将事件句柄设置为无消息状态)
BOOL ResetEvent(HANDLE hEvent
);
- 关闭事件
CloseHandle
函数
我们来看看事件的使用:
#include <stdio.h>
#include <windows.h>DWORD WINAPI ThreadProc1(LPVOID lpParameter);
DWORD WINAPI ThreadProc2(LPVOID lpParameter);
DWORD g_value = 0;
HANDLE hEvent = NULL; //用于接收事件句柄int main() {DWORD nID = 0;HANDLE hThread[2] = { 0 };hEvent = CreateEvent(NULL,FALSE,0,NULL);hThread[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, &nID);hThread[1] = CreateThread(NULL, 0, ThreadProc2, NULL, 0, &nID);WaitForMultipleObjects(2, hThread, TRUE, INFINITE);printf("%d\n", g_value);CloseHandle(hEvent);return 0;
}DWORD WINAPI ThreadProc1(LPVOID lpParameter) {char a[] = "********";while (1) {WaitForSingleObject(hEvent, INFINITE);for (int i = 0; i < strlen(a); i++) {printf("%c", a[i]);}printf("\n");ResetEvent(hEvent);}return 0;
}DWORD WINAPI ThreadProc2(LPVOID lpParameter) {while (1) {Sleep(1000);SetEvent(hEvent);}return 0;
}
这是一个很典型的相互协调工作的双线程,我们在A线程中没有设定时间间隔,但是在B线程中设定了事件间隔,我们能够很明显地感受到输出是有时间间隔的:
四.信号量
- 相关问题:
类似于事件,解决线程之间通知的相关问题,但提供一个计数器,可以设置次数。 - 创建信号量:
使用CreateSemaphore
函数:
MSDN官方解释
HANDLE CreateSemaphore(LPSECURITY_ATTRIBUIES lpSemaphoreAttributes, //安全属性LONG lInitialCount, //初始化信号量数量LONG lMaximumCount, //信号量的最大值LPSTSTR lpName //为信号量命名
);创建成功返回信号量句柄
- 等候信号量
使用WaitFor...
函数
注意: 等候每通过一次,信号量的信号减一,知道为0阻塞 - 给信号量指定定计数值
使用ReleaseSemaphore()
函数
MSDN官方解释
BOOL ReleaseSemaphore(HANDLE hSeamephore, //信号量句柄LONG lReleaseSemaphore, //信号量将增加的量LPONG lpPreviousCount //指向一个变量的指针,用于记录信号量的上一个计数
);
- 关闭信号量:
使用CloseHandle()
函数
我们来看看信号量的使用实例:
#include <stdio.h>
#include <windows.h>DWORD WINAPI ThreadProc1(LPVOID lpParameter);
DWORD WINAPI ThreadProc2(LPVOID lpParameter);
HANDLE hSemaphore = NULL; //用于接收事件句柄int main() {DWORD nID = 0;HANDLE hThread[2] = { 0 };hSemaphore = CreateSemaphore(NULL, 3, 10, NULL);hThread[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, &nID);while (getchar()=='\n') {ReleaseSemaphore(hSemaphore, 5, NULL);}CloseHandle(hSemaphore);return 0;
}DWORD WINAPI ThreadProc1(LPVOID lpParameter) {char a[] = "********";while (1) {WaitForSingleObject(hSemaphore, INFINITE);for (int i = 0; i < strlen(a); i++) {printf("%c", a[i]);}printf("\n");}return 0;}
我们设置了计数器为3的信号量,我们发现程序最开始只会输出三行,每当我们按一次回车键,就将信号量计数值值为5:
本篇文章的分享就到这里,如果大家发现有错误之处,还请大家指出来,我会非常虚心地学习。希望我们共同进步!!!
相关文章:

【Windows线程开发】Windows线程同步技术
我们在上一章节中讲解了关于Windows的线程基础,相信大家已经对线程有了基本的概念。这一章节中,我们来讲讲线程同步技术,包括加锁技术(原子锁和互斥体)和事件,信号量。 文章目录 一.原子锁二.互斥体三.事件…...

【数据结构与算法】- 期末考试
课程链接: 清华大学驭风计划 代码仓库:Victor94-king/MachineLearning: MachineLearning basic introduction (github.com) 驭风计划是由清华大学老师教授的,其分为四门课,包括: 机器学习(张敏教授) , 深度学习(胡晓林教授), 计算…...

股票?看我用python采集数据制作成交量图表
前言 嗨喽,大家好呀~这里是爱看美女的茜茜呐 开发环境 & 第三方模块: 解释器版本: python 3.8 代码编辑器: pycharm 2021.2 requests: pip install requests 爬虫 pyecharts: pip install pyecharts 数据分析 pandas: pip install pandas 数据分析 基本流…...

C++中的多态,以及多态的实现、以及实现多态的两个特例。
一、 多态是什么? 通俗点说,就是多种形态。具体点就是不同对象完成某种事情,会产生不一样的状态。 举个例子:就好比:买票的时候,普通人、学生、军人等等,他们买票有不同的结果,普通人…...

ESP32-C2系列开发板简介
C2是一个芯片采用4毫米x 4毫米封装,与272 kB内存。它运行框架,例如ESP-Jumpstart和ESP造雨者,同时它也运行ESP-IDF。ESP-IDF是Espressif面向嵌入式物联网设备的开源实时操作系统,受到了全球用户的信赖。它由支持Espressif以及所有…...

AI在狂飙,ChatGPT-4可直接在iPhone上使用啦
今天凌晨,OpenAI 正式在 App Store 推出了 ChatGPT 的 iOS app,瞬间冲上苹果商店免费榜第二名,效率榜第一名。 于是兴致勃勃的去下载体验了一番。整体不错,以后手机使用官方的 ChatGPT 更方便啦!而且使用 GPT4 不再麻…...
【计算机操作系统学习资源汇总】
引言 做优秀资源的搬运工!阅读经典,传承经典! 每个人的水平和理解不同,因此总结的水平也不同,推荐阅读一手资源,有时间尽量啃书。 发现好的资源会持续更新… 看书的一点小建议 1. 《深入理解计算机系统》…...

吴恩达 x OpenAI Prompt Engineering教程中文笔记
Datawhale干货 作者:刘俊君,Datawhale成员 完整课程:《吴恩达ChatGPT最新课程》 🐳Reasons & Importance Important for research, discoveries, and advancement 对研究、发现和进步很重要 Accelerate the scientific resea…...

Everypixel: AI图片搜索引擎
【产品介绍】 Everypixel是一个基于人工智能的图片搜索引擎。可以搜索超过 50 个图片来源的优质的授权图库版权素材图片,还可以使用免费图案功能,找到适合自己需求的可定制无缝图案。 Everypixel利用深度学习和计算机视觉技术,为客户提供先进…...

IOS新建应用
一:Application App。普通app。Document App。打开是记事本类似App。Game。新建游戏相关app。RealityKit为新建一个打开摄像机,一个Ar立方体的应用。 SenceKit为有一架飞机旋转的游戏App。 SpirteKit为一个手指头按上会出一个手指特效的应用。 Metal为一…...
CMake学习笔记:搜索第三方库及头文件路径 find_package()命令
1、find_package命令基本介绍 在实际开发过程中,经常不可避免的会使用到第三方开源库,这些开源库可能是通过apt-get install 命令自动安装到系统目录,也可能是由我们自己下载库的源码然后通过编译安装到指定目录的。 不管哪种方式安装的库文…...

Vue3-黑马(一)
目录: (1)vue3-基础-环境准备 (2)vue3-基础-入门案例 (3)vue3-基础-main.ts (1)vue3-基础-环境准备 vue3的技术选型,它提供了两套API,一个是选…...

[组合数学]母函数与递推关系
文章目录 母函数---解决计数组合 球相同 盒子不同 不能是空 C n − 1 m − 1 \quad C_{n-1}^{m-1} Cn−1m−1数的拆分 递推关系常系数线性齐次递推关系常系数线性非齐次递推关系汉诺塔递推关系 母函数—解决计数 普母函数—组合问题 指母函数—排列问题 f(x) ∑ i 1 n a i…...

opencv膨胀腐蚀
OpenCV 是一个开源的计算机视觉库,它包含了许多图像处理的功能,其中膨胀和腐蚀是两种常用的形态学操作。 膨胀(Dilation):膨胀操作是将图像中的高亮区域(白色像素)扩张,从而填充低亮…...

ARM的读写内存指令与栈的应用
1.基础读写指令 写内存指令:STR MOV R1, #0xFF000000 MOV R2, #0x40000000 STR R1, [R2] 将R1寄存器中的数据写入到R2指向的内存空间 需注意,此命令是将R1中的数据写给R2所指向的内存空间,而不是直接把R1的数据赋给R2,R2寄存器…...

2022年平均工资出炉,IT行业又是第一
根据5月9日国家统计局最新资料显示,2022年,全国城镇非私营单位就业人员年平均工资为114029元,比上年增长6.7%,扣除通胀后实际增长4.6%。其中,行业间的差距相当明显。根据资料显示,2022年无论是在私营单位还…...

ov2640子设备核心操作详细分析
ov2640子设备核心操作详细分析 文章目录 ov2640子设备核心操作详细分析ov2640_subdev_core_ops核心操作获取寄存器值ov2640_g_register设置寄存器值ov2640_s_registeri2c_smbus_xferi2c_imx_xferi2c_smbus_xfer_emulatedi2c_transfer__i2c_transfer 设置ov2640的电源ov2640_s_p…...
MATLAB语句实现方阵性质的验证
系列文章目录 MATLAB绘图函数的相关介绍——海底测量、二维与三维图形绘制 MATLAB求函数极限的简单介绍 matlab系统环境思维导图 文章目录 系列文章目录 1. MATLAB语句验证方阵的六个性质如下 2. 六个性质的解释如下 3. 使用随机矩阵进行验证的代码示例如下 总结 前言 …...
使用Springboot AOP进行请求接口异常监控
常用注解 Aspect 切面类 Before 前置 AfterReturning 后置 Around 环绕 AfterThrowing 异常 切入点设置 execution(public * *(..)) 定义任意公共方法的执行 execution(* set*(..)) 定义任何一个以"set"开始的方法的执行 execution(* com.sys.service.UserService…...

【云原生|Kubernetes】05-Pod的存储卷(Volume)
【云原生Kubernetes】05-Pod的存储卷(Volume) 文章目录 【云原生Kubernetes】05-Pod的存储卷(Volume)简介Volume类型解析emptyDirHostPathgcePersistentDiskNFSiscsiglusterfsceph其他volume 简介 Volume 是Pod 中能够被多个容器访问的共享目录。 Kubern…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...

vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...

html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...

什么是VR全景技术
VR全景技术,全称为虚拟现实全景技术,是通过计算机图像模拟生成三维空间中的虚拟世界,使用户能够在该虚拟世界中进行全方位、无死角的观察和交互的技术。VR全景技术模拟人在真实空间中的视觉体验,结合图文、3D、音视频等多媒体元素…...

抽象类和接口(全)
一、抽象类 1.概念:如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象,这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法,包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中,⼀个类如果被 abs…...
Python竞赛环境搭建全攻略
Python环境搭建竞赛技术文章大纲 竞赛背景与意义 竞赛的目的与价值Python在竞赛中的应用场景环境搭建对竞赛效率的影响 竞赛环境需求分析 常见竞赛类型(算法、数据分析、机器学习等)不同竞赛对Python版本及库的要求硬件与操作系统的兼容性问题 Pyth…...