Linux和windows进程同步与线程同步那些事儿(二): windows线程同步详解示例

《Linux和windows进程同步与线程同步那些事儿(一)》

一、线程同步

1.1 windows下线程同步

在Windows中,线程同步可以通过多种机制来实现,其中最常见的包括互斥量(mutex)、事件(event)、临界区(critical section)、信号量(semaphore)和条件变量(condition variable)等。

1.1.1. 互斥量(Mutex):

互斥量是最常用的线程同步机制,它可以确保在同一时间只有一个线程可以访问共享资源。
在Windows中,可以使用CreateMutex函数来创建互斥量。

代码示例1:
#include <windows.h>
#include <iostream>int main() {HANDLE hMutex;// 创建互斥量hMutex = CreateMutex(NULL,              // 默认安全属性FALSE,             // 初始拥有者为FALSE,表示创建后不立即拥有互斥量L"MyMutex");       // 互斥量名称if (hMutex == NULL) {std::cerr << "CreateMutex error: " << GetLastError() << std::endl;return 1;}// 尝试获取互斥量的所有权DWORD dwWaitResult = WaitForSingleObject(hMutex,    // 互斥量的句柄INFINITE); // 无限等待switch (dwWaitResult) {// 线程获得了互斥量的所有权case WAIT_OBJECT_0:try {// 执行线程的任务,访问共享资源}finally {// 释放互斥量if (!ReleaseMutex(hMutex)) {std::cerr << "ReleaseMutex error: " << GetLastError() << std::endl;}}break;// 无法获取互斥量的所有权case WAIT_ABANDONED:return 1;}// 关闭互斥量句柄CloseHandle(hMutex);return 0;
}

代码讲解

  1. CreateMutex函数用于创建或打开一个互斥量。它的参数包括:
  • 安全属性:NULL表示互斥量使用默认的安全描述符。
  • 初始拥有者:FALSE表示创建互斥量后当前线程不立即拥有它。
  • 互斥量名称:可以是任意字符串,用于标识互斥量。如果为NULL,则创建一个无名互斥量。
  1. WaitForSingleObject函数用于请求互斥量的所有权。如果互斥量已被其他线程拥有,调用线程将等待直到它可以获得互斥量的所有权。参数INFINITE表示无限等待。

  2. WAIT_OBJECT_0表示成功获取了互斥量的所有权,此时线程可以安全地访问共享资源。

  3. ReleaseMutex函数用于释放互斥量的所有权,这样其他等待互斥量的线程可以继续执行。

  4. CloseHandle函数用于关闭互斥量的句柄。当不再需要互斥量时,应该关闭它的句柄。

  5. WAIT_ABANDONED表示试图获取的互斥量是由其他线程在持有时终止的,这通常意味着共享资源可能处于未知状态。

使用互斥量时,务必确保在访问完共享资源后释放互斥量,避免死锁。如果程序在持有互斥量时异常退出,可能会导致互斥量永远不会被释放,从而阻塞等待该互斥量的其他线程。

代码示例2:
#include <windows.h>
#include <iostream>// 全局互斥量句柄
HANDLE hMutex;// 模拟共享资源
int sharedResource = 0;// 线程函数
DWORD WINAPI ThreadFunction(LPVOID lpParam) {// 请求互斥量的所有权WaitForSingleObject(hMutex, INFINITE);// 临界区开始// 修改共享资源sharedResource++;std::cout << "Thread " << GetCurrentThreadId() << " incremented sharedResource to " << sharedResource << std::endl;// 临界区结束// 释放互斥量的所有权ReleaseMutex(hMutex);return 0;
}int main() {// 创建互斥量hMutex = CreateMutex(NULL, FALSE, NULL);if (hMutex == NULL) {std::cerr << "CreateMutex error: " << GetLastError() << std::endl;return 1;}// 创建线程const int numThreads = 5;HANDLE hThreads[numThreads];for (int i = 0; i < numThreads; ++i) {hThreads[i] = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);if (hThreads[i] == NULL) {std::cerr << "CreateThread error: " << GetLastError() << std::endl;return 1;}}// 等待所有线程完成WaitForMultipleObjects(numThreads, hThreads, TRUE, INFINITE);// 关闭线程和互斥量句柄for (int i = 0; i < numThreads; ++i) {CloseHandle(hThreads[i]);}CloseHandle(hMutex);// 输出最终的共享资源值std::cout << "Final value of sharedResource is " << sharedResource << std::endl;return 0;
}

代码解释

  1. 定义了一个全局互斥量句柄hMutex和一个模拟的共享资源sharedResource。
  2. ThreadFunction是线程将要执行的函数。它首先尝试获取互斥量,然后进入临界区修改共享资源,并在完成后释放互斥量。
  3. 在main函数中,创建了一个互斥量和多个线程。每个线程都会执行ThreadFunction。
  4. 使用WaitForMultipleObjects等待所有线程完成执行。
  5. 最后,关闭所有线程和互斥量的句柄,并输出共享资源的最终值。

这个示例确保了即使多个线程尝试同时访问和修改sharedResource,互斥量也会保证每次只有一个线程可以进行修改。这样就避免了竞态条件和数据不一致的问题。

1.1.2. 事件(Event):

事件用于线程间的通信和同步,允许线程等待某个特定事件的发生。
在Windows中,可以使用CreateEvent函数来创建事件对象。

#include <windows.h>
#include <iostream>// 全局事件对象句柄
HANDLE hEvent;// 全局共享资源
int sharedValue = 0;// 线程函数
DWORD WINAPI ThreadFunction(LPVOID lpParam) {// 等待事件触发WaitForSingleObject(hEvent, INFINITE);// 临界区开始// 修改共享资源sharedValue++;std::cout << "Thread " << GetCurrentThreadId() << " incremented sharedValue to " << sharedValue << std::endl;// 临界区结束return 0;
}int main() {// 创建手动复位事件对象hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);if (hEvent == NULL) {std::cerr << "CreateEvent error: " << GetLastError() << std::endl;return 1;}// 创建线程const int numThreads = 3;HANDLE hThreads[numThreads];for (int i = 0; i < numThreads; ++i) {hThreads[i] = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);if (hThreads[i] == NULL) {std::cerr << "CreateThread error: " << GetLastError() << std::endl;return 1;}}// 模拟触发事件std::cout << "Event is being signaled." << std::endl;SetEvent(hEvent);// 等待所有线程完成WaitForMultipleObjects(numThreads, hThreads, TRUE, INFINITE);// 关闭线程和事件对象句柄for (int i = 0; i < numThreads; ++i) {CloseHandle(hThreads[i]);}CloseHandle(hEvent);// 输出最终的共享资源值std::cout << "Final value of sharedValue is " << sharedValue << std::endl;return 0;
}

代码解释

  1. 定义了一个全局事件对象句柄hEvent和一个全局共享资源sharedValue。
  2. ThreadFunction是线程将要执行的函数。它首先等待事件对象被触发,然后进入临界区修改共享资源,并在完成后释放事件对象。
  3. 在main函数中,创建了一个手动复位事件对象和多个线程。每个线程都会执行ThreadFunction。
  4. 使用SetEvent模拟触发事件,使得所有等待的线程可以执行。
  5. 使用WaitForMultipleObjects等待所有线程完成执行。
  6. 最后,关闭所有线程和事件对象的句柄,并输出共享资源的最终值。

在这个示例中,事件对象hEvent充当了一个信号,当事件被触发时,所有等待的线程可以修改共享资源sharedValue。通过控制事件的触发时机,可以实现对共享资源的安全访问和修改。

1.1.3. 临界区(Critical Section):

临界区用于保护共享资源,确保在同一时间只有一个线程可以访问。
在Windows中,可以使用InitializeCriticalSection函数来初始化临界区。

#include <windows.h>
#include <iostream>// 全局临界区对象
CRITICAL_SECTION criticalSection;// 全局共享资源
int sharedValue = 0;// 线程函数
DWORD WINAPI ThreadFunction(LPVOID lpParam) {// 进入临界区EnterCriticalSection(&criticalSection);// 修改共享资源sharedValue++;std::cout << "Thread " << GetCurrentThreadId() << " incremented sharedValue to " << sharedValue << std::endl;// 离开临界区LeaveCriticalSection(&criticalSection);return 0;
}int main() {// 初始化临界区InitializeCriticalSection(&criticalSection);// 创建线程const int numThreads = 3;HANDLE hThreads[numThreads];for (int i = 0; i < numThreads; ++i) {hThreads[i] = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);if (hThreads[i] == NULL) {std::cerr << "CreateThread error: " << GetLastError() << std::endl;return 1;}}// 等待所有线程完成WaitForMultipleObjects(numThreads, hThreads, TRUE, INFINITE);// 关闭线程句柄for (int i = 0; i < numThreads; ++i) {CloseHandle(hThreads[i]);}// 删除临界区DeleteCriticalSection(&criticalSection);// 输出最终的共享资源值std::cout << "Final value of sharedValue is " << sharedValue << std::endl;return 0;
}

代码解释

  1. 定义了一个全局临界区对象criticalSection和一个全局共享资源sharedValue。
  2. ThreadFunction是线程将要执行的函数。它首先进入临界区,然后修改共享资源,并在完成后离开临界区。
  3. 在main函数中,初始化了临界区,并创建了多个线程。每个线程都会执行ThreadFunction。
  4. 使用WaitForMultipleObjects等待所有线程完成执行。
  5. 最后,关闭所有线程的句柄,删除临界区,并输出共享资源的最终值。

在这个示例中,临界区criticalSection充当了一个保护共享资源的锁,确保每次只有一个线程可以进入临界区修改共享资源sharedValue。这样就避免了多个线程同时修改共享资源导致的数据不一致问题。

1.1.4. 信号量(Semaphore):

信号量是一种经典的线程同步机制,它可以用于控制对共享资源的访问。
在Windows中,可以使用CreateSemaphore函数来创建信号量。

#include <windows.h>
#include <iostream>// 全局信号量对象
HANDLE hSemaphore;// 全局共享资源
int sharedValue = 0;// 线程函数
DWORD WINAPI ThreadFunction(LPVOID lpParam) {// 等待信号量WaitForSingleObject(hSemaphore, INFINITE);// 修改共享资源sharedValue++;std::cout << "Thread " << GetCurrentThreadId() << " incremented sharedValue to " << sharedValue << std::endl;// 释放信号量ReleaseSemaphore(hSemaphore, 1, NULL);return 0;
}int main() {// 创建信号量hSemaphore = CreateSemaphore(NULL, 1, 1, NULL);if (hSemaphore == NULL) {std::cerr << "CreateSemaphore error: " << GetLastError() << std::endl;return 1;}// 创建线程const int numThreads = 3;HANDLE hThreads[numThreads];for (int i = 0; i < numThreads; ++i) {hThreads[i] = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);if (hThreads[i] == NULL) {std::cerr << "CreateThread error: " << GetLastError() << std::endl;return 1;}}// 等待所有线程完成WaitForMultipleObjects(numThreads, hThreads, TRUE, INFINITE);// 关闭线程句柄for (int i = 0; i < numThreads; ++i) {CloseHandle(hThreads[i]);}// 关闭信号量句柄CloseHandle(hSemaphore);// 输出最终的共享资源值std::cout << "Final value of sharedValue is " << sharedValue << std::endl;return 0;
}

代码解释

  1. 定义了一个全局信号量对象hSemaphore和一个全局共享资源sharedValue。
  2. ThreadFunction是线程将要执行的函数。它首先等待信号量,然后修改共享资源,并在完成后释放信号量。
  3. 在main函数中,创建了一个初始计数为1的信号量,并多个线程。每个线程都会执行ThreadFunction。
  4. 使用WaitForMultipleObjects等待所有线程完成执行。
  5. 最后,关闭所有线程和信号量的句柄,并输出共享资源的最终值。

在这个示例中,信号量hSemaphore充当了一个控制访问共享资源的信号,确保每次只有一个线程可以修改共享资源sharedValue。通过控制信号量的释放和等待,可以实现对共享资源的安全访问和修改。

1.1.5. 条件变量(Condition Variable):

条件变量用于线程间的通信和同步,允许线程等待某个特定条件的发生。
在Windows中,可以使用条件变量的概念结合事件对象或互斥量来实现条件变量的功能。

#include <windows.h>
#include <iostream>// 全局事件对象句柄
HANDLE hEvent;// 全局临界区对象
CRITICAL_SECTION criticalSection;// 全局共享资源
int sharedValue = 0;// 线程函数
DWORD WINAPI ThreadFunction(LPVOID lpParam) {// 进入临界区EnterCriticalSection(&criticalSection);// 等待事件触发LeaveCriticalSection(&criticalSection);WaitForSingleObject(hEvent, INFINITE);EnterCriticalSection(&criticalSection);// 修改共享资源sharedValue++;std::cout << "Thread " << GetCurrentThreadId() << " incremented sharedValue to " << sharedValue << std::endl;// 重置事件,以便其他线程等待ResetEvent(hEvent);// 离开临界区LeaveCriticalSection(&criticalSection);return 0;
}int main() {// 初始化事件对象和临界区hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);InitializeCriticalSection(&criticalSection);// 创建线程const int numThreads = 3;HANDLE hThreads[numThreads];for (int i = 0; i < numThreads; ++i) {hThreads[i] = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);if (hThreads[i] == NULL) {std::cerr << "CreateThread error: " << GetLastError() << std::endl;return 1;}}// 触发事件,使得所有等待的线程可以执行SetEvent(hEvent);// 等待所有线程完成WaitForMultipleObjects(numThreads, hThreads, TRUE, INFINITE);// 关闭线程句柄for (int i = 0; i < numThreads; ++i) {CloseHandle(hThreads[i]);}// 关闭事件对象和删除临界区CloseHandle(hEvent);DeleteCriticalSection(&criticalSection);// 输出最终的共享资源值std::cout << "Final value of sharedValue is " << sharedValue << std::endl;return 0;
}

在这个示例中,我们使用了一个事件对象hEvent和一个临界区criticalSection来模拟条件变量的行为。当事件被触发时,所有等待的线程可以进入临界区修改共享资源sharedValue。通过控制事件的触发时机和临界区的进入和离开,可以实现对共享资源的安全访问和修改。

这些线程同步机制都可以通过Windows提供的API函数来使用。在实际编程中,选择合适的线程同步机制取决于具体的应用场景和需求,以确保线程间的安全访问和协调。

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

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

相关文章

【python可视化大屏】使用python实现可拖拽数据可视化大屏

介绍&#xff1a; 我在前几期分享了关于爬取weibo评论的爬虫&#xff0c;同时也分享了如何去进行数据可视化的操作。但是之前的可视化都是单独的&#xff0c;没有办法在一个界面上展示的。这样一来呢&#xff0c;大家在看的时候其实是很不方便的&#xff0c;就是没有办法一目了…

vue项目完整搭建与启动

vue项目完整搭建与启动 一&#xff0c;安装node环境二&#xff0c;安装vue脚手架&#xff08;vue-cli&#xff09;1.cnpm(淘宝镜像安装&#xff09;2.npm安装3.yarn安装 三&#xff0c;创建vue项目四&#xff0c;cmd切换目录方式1方式2 一&#xff0c;安装node环境 1.下载地址…

uniapp微信小程序投票系统实战 (SpringBoot2+vue3.2+element plus ) -我创建的投票列表实现

锋哥原创的uniapp微信小程序投票系统实战&#xff1a; uniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )_哔哩哔哩_bilibiliuniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )共计21条视频…

GitLab clone 地址 不对

1丶问题描述 2丶解决方案 解决方案&#xff1a; 找到挂载到宿主机配置文件&#xff1a;gitlab.rb vi gitlab.rb 改成自己的ip 重启容器 docker restart gitlab 如果发现容器一直重启&#xff0c;可采用粗暴的方法&#xff0c;直接干掉当前容器&#xff0c;重新运行一个 …

SpringMVC-异常处理及常用组件

异常处理器 1.基于配置的异常处理 springmvc提供了一个处理控制器方法执行过程中所出现的异常的接口: HandlerExceptionResolver HandlerExceptionResolver接口的实现类有: DefaultHandlerExceptionResolver和SimpleMappingExceptionResolver springmvc提供了自定义的异常处…

Apache Doris (六十二): Spark Doris Connector - (2)-使用

🏡 个人主页:IT贫道-CSDN博客 🚩 私聊博主:私聊博主加WX好友,获取更多资料哦~ 🔔 博主个人B栈地址:豹哥教你学编程的个人空间-豹哥教你学编程个人主页-哔哩哔哩视频 目录 1. 将编译jar包加入本地Maven仓库

全版本Windows RCE漏洞复现CVE-2023-36025

漏洞简介 CVE-2023-36025是微软于11月补丁日发布的安全更新中修复Windows SmartScreen安全功能绕过漏洞。攻击者可以通过诱导用户单击特制的URL来利用该漏洞&#xff0c;对目标系统进行攻击。成功利用该漏洞的攻击者能够绕过Windows Defender SmartScreen检查及其相关提示。该漏…

2024年【R2移动式压力容器充装】考试资料及R2移动式压力容器充装理论考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 R2移动式压力容器充装考试资料根据新R2移动式压力容器充装考试大纲要求&#xff0c;安全生产模拟考试一点通将R2移动式压力容器充装模拟考试试题进行汇编&#xff0c;组成一套R2移动式压力容器充装全真模拟考试试题&a…

Node.js和npm

目录 01_Node.js01.什么是 Node.js目标讲解小结 02.fs模块-读写文件目标讲解小结 03.path模块-路径处理目标讲解小结 04.案例-压缩前端html目标讲解小结 05.认识URL中的端口号目标讲解小结 06.http模块-创建Web服务目标讲解小结 07.案例-浏览时钟目标讲解小结 02_Node.js模块化…

02-python的基础语法-01python字面量/注释/数据类型/数据类型转换

字面量 在代码中&#xff0c;被写下来的固定的值&#xff0c;被称为字面量。 python中哪些值是可以被写出来的呢?又该如何写呢&#xff1f; 字符串&#xff1a;又称文本&#xff0c;是由任意数量的字符如中文&#xff0c;英文&#xff0c;各类符号&#xff0c;数字组成。 这…

ES-极客学习第二部分ES 入门

基本概念 索引、文档、节点、分片和API json 文档 文档的元数据 需要通过Kibana导入Sample Data的电商数据。具体参考“2.2节-Kibana的安装与界面快速浏览” 索引 kibana 管理ES索引 在系统中找到kibana配置文件&#xff08;我这里是etc/kibana/kibana.yml&#xff09; vim /…

redis(1)

redis 缓存穿透 1.什么是缓存穿透呢&#xff1f; 当客户端访问数据库一个不存在的数据时&#xff0c;数据库查询不到也不会添加在缓存中&#xff0c;所以客户端的每次访问都会去查数据库&#xff0c;这有可能会导致数据库垮掉。 2.解决方案 1.缓存空数据&#xff0c;即便查…

Oracle与Java JDBC数据类型对照

Oracle Database JDBC开发人员指南和参考 SQL Data TypesJDBC Type CodesStandard Java TypesOracle Extension Java Types CHAR java.sql.Types.CHAR java.lang.String oracle.sql.CHAR VARCHAR2 java.sql.Types.VARCHAR java.lang.String oracle.sql.CHAR LONG jav…

vue使用sm2对手机号邮箱等加签名,数据完整性

vue使用sm2对手机号邮箱等加签名&#xff0c;数据完整性 1、安装依赖 npm install --save sm-crypto2、在utils文件下新建sm2Util.js // sm2Util.js const SM2 require(sm-crypto).sm2; const publicKey woshigongyao // 公钥 const privateKey woshisiyao // 私钥/* * 加…

esp32UART串口外设(Arduino)

通用异步接收器/发送器 &#xff08;UART&#xff09; 介绍 通用异步接收器/发送器 &#xff08;UART&#xff09; 是一种硬件功能&#xff0c;它使用广泛采用的异步串行通信接口&#xff08;如 RS232、RS422 和 RS485&#xff09;处理通信&#xff08;即时序要求和数据成帧&…

个人笔记:分布式大数据技术原理(二)构建在 Hadoop 框架之上的 Hive 与 Impala

大家想了解更多大数据相关内容请移驾我的课堂: 大数据相关课程 剖析及实践企业级大数据 数据架构规划设计 大厂架构师知识梳理:剖析及实践数据建模 有了 MapReduce,Tez 和 Spark 之后,程序员发现,MapReduce 的程序写起来真麻烦。他们希望简化这个过程。这就好比你有了汇编…

Vue-9、Vue事件修饰符

1、prevent 阻止默认事件 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>事件修饰符</title><!--引入vue--><script type"text/javascript" src"https://cdn.jsdeliv…

用html和css实现一个加载页面【究极简单】

要创建一个简单的加载页面&#xff0c;你可以使用 HTML 和 CSS 来设计。以下是一个基本的加载页面示例&#xff1a; HTML 文件 (index.html): <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"…

Hive基础知识(五):Hive 配置 JDBC方式访问

1&#xff09;在 hive-site.xml 文件中添加如下配置信息 <!--指定 hiveserver2 连接的 host --> <property><name>hive.server2.thrift.bind.host</name><value>hadoop100</value> </property> <!--指定 hiveserver2 连接的端口号…

JavaScript中解锁Map和Set的力量

&#x1f9d1;‍&#x1f393; 个人主页&#xff1a;《爱蹦跶的大A阿》 &#x1f525;当前正在更新专栏&#xff1a;《VUE》 、《JavaScript保姆级教程》、《krpano》 ​ ​ ✨ 前言 ES6带来了Map和Set两个新的数据结构 - 它们分别用于存放键值对和唯一值。Map和Set提供了更…