Windows下线程的竞争与资源保护(win32-API)

一、前言

在线程编程中,资源共享与保护是一个核心议题,尤其当多个线程试图同时访问同一份资源时,如果不采取适当的措施,就会引发一系列的问题,如数据不一致、竞态条件、死锁等。为了确保数据的一致性和线程安全,多种资源保护机制被设计出来,这些机制主要围绕着资源的互斥访问展开,以防止多个线程同时修改同一份数据而导致的错误。

临界区(Critical Section)

临界区是最基本的资源保护方式之一,它允许同一时间内只有一个线程进入临界区并访问受保护的资源。临界区通过操作系统提供的原语实现,如Windows下的EnterCriticalSectionLeaveCriticalSection函数。当一个线程进入临界区时,其他试图进入同一临界区的线程将被阻塞,直到当前线程离开临界区。临界区适用于同一进程内的线程,因为它们共享相同的地址空间,可以快速且有效地进行同步。

互斥量(Mutex)

互斥量是一种更通用的同步机制,它不仅限于同一进程内的线程,还可以跨越进程边界。互斥量提供了比临界区更强大的功能,如命名互斥量,这允许不同进程中的线程可以共享同一个互斥量对象。互斥量通过CreateMutex函数创建,并使用WaitForSingleObjectReleaseMutex函数进行锁定和解锁。互斥量支持优先级继承,这有助于防止优先级反转问题,即高优先级线程等待低优先级线程释放资源的情况。

信号量(Semaphore)

信号量用于控制对有限数量资源的访问,例如控制并发访问数据库连接的数量。信号量维护一个计数器,当计数器大于零时,线程可以获取信号量并减少计数器的值,从而获得访问资源的许可。当线程释放信号量时,计数器增加,允许其他等待的线程获取信号量。信号量分为二进制信号量和计数信号量,前者只能在0和1之间切换,常用于实现互斥访问;后者可以有任意非负值,用于控制资源的数量。

自旋锁(Spin Lock)

自旋锁是一种非阻塞的同步机制,主要用于短时间的锁定,尤其是在高负载、高频率的访问场景中。当一个线程尝试获取一个已被占用的自旋锁时,它不会被阻塞,而是循环检查锁的状态,直到锁被释放。自旋锁避免了线程上下文切换带来的开销,但在锁长时间被占用的情况下,可能会消耗大量的CPU资源。

读写锁(Reader-Writer Lock)

读写锁允许多个读线程同时访问资源,但只允许一个写线程访问资源,这在读操作远多于写操作的场景中非常有效。读写锁优化了读取性能,因为多个读线程可以同时持有读锁,而写操作则需要独占锁才能进行,以防止数据的不一致性。

条件变量(Condition Variable)

条件变量通常与互斥量结合使用,用于实现线程间的高级同步。当线程需要等待某个条件变为真时,它可以释放互斥量并调用条件变量的Wait函数。当条件满足时,线程可以被唤醒并重新获取互斥量,继续执行。条件变量是实现生产者-消费者模式、读者-写者模式等复杂同步策略的基础。

在多线程编程中,正确选择和使用这些同步机制对于保证程序的正确性和性能至关重要。开发人员必须仔细分析线程间的交互,识别出可能引起竞态条件的资源,并采取适当的保护措施,以确保数据的一致性和线程的安全运行。同时,过度的同步也可能导致性能瓶颈,因此在设计时还需平衡同步的必要性和程序的效率。

二、实操代码

2.1 互斥量案例-消费者与生产者模型

开发环境:在Windows下安装一个VS即可。我当前采用的版本是VS2020。

创建一个基于互斥量(mutex)的火车票售卖模型,可以很好地展示消费者与生产者关系中资源保护的重要性。在这个模型中,“生产者”可以视为负责初始化火车票数量的角色,而“消费者”则是购买火车票的线程。为了确保在多线程环境中票数的正确性和一致性,需要使用互斥量来保护对票数的访问和修改。

下面是一个使用C语言和Windows API实现的火车票售卖模型的示例代码:

#include <windows.h>
#include <stdio.h>#define TICKET_COUNT 10// 定义互斥量
CRITICAL_SECTION ticketMutex;
int ticketsAvailable = TICKET_COUNT;// 消费者线程函数
DWORD WINAPI ConsumerThread(LPVOID lpParameter)
{int id = (int)lpParameter;while (ticketsAvailable > 0){// 进入临界区EnterCriticalSection(&ticketMutex);if (ticketsAvailable > 0){ticketsAvailable--;printf("Consumer %d bought a ticket. Tickets left: %d\n", id, ticketsAvailable);}// 离开临界区LeaveCriticalSection(&ticketMutex);}return 0;
}int main()
{HANDLE consumerThreads[TICKET_COUNT * 2]; // 假设有两倍于票数的消费者DWORD threadIDs[TICKET_COUNT * 2];// 初始化临界区InitializeCriticalSection(&ticketMutex);// 创建消费者线程for (int i = 0; i < TICKET_COUNT * 2; i++){consumerThreads[i] = CreateThread(NULL,                    // 默认安全属性0,                       // 使用默认堆栈大小ConsumerThread,          // 线程函数(LPVOID)(i + 1),         // 传递给线程函数的参数0,                       // 创建标志,0表示立即启动&threadIDs[i]);          // 返回线程IDif (consumerThreads[i] == NULL){printf("Failed to create thread %d.\n", i);return 1;}}// 等待所有线程结束for (int i = 0; i < TICKET_COUNT * 2; i++){WaitForSingleObject(consumerThreads[i], INFINITE);}// 删除临界区DeleteCriticalSection(&ticketMutex);// 关闭所有线程句柄for (int i = 0; i < TICKET_COUNT * 2; i++){CloseHandle(consumerThreads[i]);}return 0;
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这个示例中,定义了一个CRITICAL_SECTION类型的ticketMutex互斥量来保护对ticketsAvailable变量的访问。在ConsumerThread函数中,每个线程在尝试购买一张票之前,都需要先通过EnterCriticalSection函数进入临界区,以确保在任何时刻只有一个线程可以修改票数。购买完成后,通过LeaveCriticalSection函数离开临界区,允许其他线程有机会进入临界区并尝试购票。

虽然创建了两倍于票数的消费者线程,但由于互斥量的存在,最多只会有一张票在同一时刻被售出,从而避免了资源竞争和数据不一致的问题。

此代码演示了如何在多线程环境中使用互斥量来保护共享资源,确保数据的一致性和线程安全。在实际应用中,互斥量是处理多线程并发访问问题的重要工具,尤其是在涉及到资源有限且需要严格控制访问顺序的场景下。

2.2 使用临界区保护共享资源

开发环境:在Windows下安装一个VS即可。我当前采用的版本是VS2020。

使用临界区(Critical Section)来保护共享资源,如火车票数量,在多线程环境中确保数据一致性。

下面是一个使用C语言和Windows API实现的火车票售卖模型,其中包含了生产者初始化票数和多个消费者线程购买票的过程。这个模型将展示如何使用临界区来避免竞态条件,确保所有线程安全地访问和修改票数。

#include <windows.h>
#include <stdio.h>#define TICKET_COUNT 10// 定义临界区
CRITICAL_SECTION ticketMutex;
int ticketsAvailable = TICKET_COUNT;// 消费者线程函数
DWORD WINAPI ConsumerThread(LPVOID lpParameter)
{int id = (int)lpParameter;while (1){// 进入临界区EnterCriticalSection(&ticketMutex);// 检查是否有票if (ticketsAvailable > 0){ticketsAvailable--;printf("Consumer %d bought a ticket. Tickets left: %d\n", id, ticketsAvailable);}else{// 如果没有票了,退出循环LeaveCriticalSection(&ticketMutex);break;}// 离开临界区LeaveCriticalSection(&ticketMutex);}return 0;
}int main()
{HANDLE consumerThreads[TICKET_COUNT * 2]; // 假设有两倍于票数的消费者DWORD threadIDs[TICKET_COUNT * 2];// 初始化临界区InitializeCriticalSection(&ticketMutex);// 创建消费者线程for (int i = 0; i < TICKET_COUNT * 2; i++){consumerThreads[i] = CreateThread(NULL,                    // 默认安全属性0,                       // 使用默认堆栈大小ConsumerThread,          // 线程函数(LPVOID)(i + 1),         // 传递给线程函数的参数0,                       // 创建标志,0表示立即启动&threadIDs[i]);          // 返回线程IDif (consumerThreads[i] == NULL){printf("Failed to create thread %d.\n", i);return 1;}}// 等待所有线程结束for (int i = 0; i < TICKET_COUNT * 2; i++){WaitForSingleObject(consumerThreads[i], INFINITE);}// 删除临界区DeleteCriticalSection(&ticketMutex);// 关闭所有线程句柄for (int i = 0; i < TICKET_COUNT * 2; i++){CloseHandle(consumerThreads[i]);}return 0;
}

在这个代码示例中,使用InitializeCriticalSection函数初始化临界区ticketMutex,并在每个线程的ConsumerThread函数中使用EnterCriticalSectionLeaveCriticalSection函数来保护对ticketsAvailable变量的访问。这意味着在任何时候,只有一个线程能够修改ticketsAvailable的值,从而避免了多线程并发访问时可能出现的数据不一致问题。

每个线程在进入临界区检查是否有剩余票之前,都要调用EnterCriticalSection,而在完成票的购买之后,调用LeaveCriticalSection来释放临界区,允许其他线程有机会进入并购买票。当票卖完后,线程会退出循环并结束。

通过这种方式,临界区确保了即使在高并发的环境中,火车票的销售过程也能有序进行,每张票只被出售一次,且所有消费者线程都能正确地跟踪剩余票数。

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

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

相关文章

数据结构(树、平衡树、红黑树)

目录 树 树的遍历方式 平衡二叉树 旋转机制 左旋 右旋 旋转实例 左左 左右 右右 右左 总结 红黑树 树 相关概念 节点的内部结构如下 二叉树与二叉查找树的定义如下 树的遍历方式 前序遍历&#xff1a;当前节点&#xff0c;左子节点&#xff0c;右子结点 中序遍…

Excel的使用总结1

目录 1、汇总公式&#xff1a;TEXTJOIN 2、excel中选择某个区域的方法 3、excel中如何在复制的时候&#xff0c;不将公式一起复制过去 4、想要自动填充某个区域的值的方法 1、汇总公式&#xff1a;TEXTJOIN TEXTJOIN 函数 - Microsoft 支持 例&#xff1a;TEXTJOIN("…

25 配置交换机网关

配置交换机网关 一、配置交换机默认网关 配置管理网关&#xff1a; Switch(config)#ip default-gateway 192.168.1.254二、配置交换机管理IP及默认网关练习 Route0&#xff1a; # 进入特权模式 Router>enable# 进入全局配置模式 Router#configure terminal # 进入f0/0口…

了解prolog规则

要推理先要有规则&#xff1b; 假设有一条规则&#xff0c; 如果X和Y是朋友&#xff0c;那么Y和X也是朋友&#xff1b; 这条规则写成这样&#xff0c; friend(X,Y) :- friend(Y, X). X和Y都是大写&#xff0c;表示这是两个变量&#xff1b;符号 :- 表示推理关系&…

【计算机网络】mini HTTP服务器框架与代码

注注注&#xff1a;本篇博文都是代码实现细节&#xff0c;但不会进行演示&#xff0c;演示看孪生篇 另外&#xff0c;由于tcp套接字部分本质都是套路&#xff0c;所以就不再进行赘述。 目录 1 请求反序列化2 读取url文件内容3 构建响应 1 请求反序列化 我们肯定会先收到请求&…

搜狐新闻HarmonyOS Push开发实践

本文字数&#xff1a;1795字 预计阅读时间&#xff1a;15分钟 01 背景 搜狐新闻作为HarmonyOS的合作伙伴&#xff0c;于2023年12月成功上架鸿蒙单框架应用市场&#xff0c;成为首批鸿蒙应用矩阵的一员。 推送作为新闻类应用的重要组成部分&#xff0c;我们将其纳入到二期功能开…

资本相信人形机器人

文&#xff5c;刘俊宏 编&#xff5c;王一粟 闷热的场馆里&#xff0c;兴奋的议论声&#xff0c;所有人生怕错过这场AI让机器人进化的盛宴。 人山人海的会展现场 光锥智能拍摄 8月21日&#xff0c;2024世界机器人大会&#xff08;WRC&#xff09;在北京开幕。在这场由169家…

vue3 element-plus el-table 多层级表头动态渲染。

效果图: html: <el-table :data"arrlist" border style"width: 100%"><template v-for"(i, index) in currentFieldData" :key"index"><el-table-column :label"i.label" :header-D"i.headerAlign&q…

TCP系列相关内容

一、TCP上传文件 loop——本地回环测试地址。 void *memset&#xff08;void *s,int c,size_t n&#xff09;——给一个变量设定一个值。 1、“粘包”问题 两次分别发送的数据&#xff0c;被一起接收形成该现象。 原因&#xff1a;TCP流式套接字&#xff0c;数据与数据间没…

分布式锁 redis与zookeeper

redis实现分布式锁 原理 基于redis命令setnx key value来实现分布式锁的功能&#xff0c;只有当key不存在时&#xff0c;setnx才可以设置成功并返回1&#xff0c;否则设置失败返回0。 方案1&#xff1a; 方案1存在的问题 假如在加锁成功&#xff0c;释放锁之前&#xff0c;…

飞书怎么关联任意两段话

最近开始用飞书记文档&#xff0c;体验实在是非常的丝滑&#xff0c;对我来说感觉没有找到更好的竞品了。废话不多说&#xff0c;接下来简单介绍一下怎么关联任意两段话吧。 首先说明&#xff0c;关联可以单向&#xff0c;也可以双向。 直接举例。 我想要将蓝字关联到最下面的…

自适应学习率(Datawhale X 李宏毅苹果书 AI夏令营)

传统的梯度下降方法在优化过程中常常面临学习率设置不当的问题。固定的学习率在训练初期可能过大&#xff0c;导致模型训练不稳定&#xff0c;而在后期可能过小&#xff0c;导致训练速度缓慢。为了克服这些问题&#xff0c;自适应学习率方法应运而生。这些方法通过动态调整学习…

微服务通信

目录 一、Feign远程调用 1、Feign简介 2、基本使用 二、Dubbo 1、基本简介 2、基础实现 一、Feign远程调用 先来看我们以前利用RestTemplate发起远程调用的代码&#xff1a; //通过restTemplate调用商品微服务String url "service-product";Product product …

proc文件的写操作机制

“一切皆是文件”。 Linux的基本哲学之一。它是指linux系统中的所有一切都可以通过文件的方式访问、管理&#xff0c;即便不是文件&#xff0c;也以文件的形式来管理。例如硬件设备、进程、套接字等都抽象成文件&#xff0c;使用统一的用户接口&#xff0c;虽然文件类型各不相同…

qt-内置图片遍历-Lambda按钮

内置图片遍历-Lambda按钮 知识点widget.hwidget.cppmain.cpp运行图 知识点 使用新的connect语法连接信号和槽 --Lambda 使用 connect(btn, &QToolButton::clicked, this, [this, btn,index]() { onToolButtonClicked(btn)}); // Lambda表达式中调用成员函数&#xff0c;并…

Springboot打包、部署

一、导入maven打包插件 <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins> </build> 二、执行打包操作&#xff08;…

基于imx6ull平台opencv的图像采集和显示屏LCD显示功能(不带Qt界面)

目录 一、概述二、环境要求2.1 硬件环境2.2 软件环境 三、开发流程3.1 编写测试3.2 验证功能 一、概述 本文档是针对imx6ull平台opencv的图像采集和显示屏LCD显示功能&#xff0c;opencv通过摄像头采集视频图像&#xff0c;将采集的视频图像送给显示屏LCD进行显示。 测试结果…

【数据结构3】哈希表、哈希表的应用(集合与字典、md5算法和文件的哈希值)

1 哈希表 哈希表一个通过哈希函数来计算数据存 储位置的数据结构&#xff0c;通常支持如下操作: 插入(键&#xff0c;值):插入键值对(键&#xff0c;值) Get(key):如果存在键为键的键值对则返回其值&#xff0c;否则返回空值 删除(键):删除键为键的键值对哈希表(Hash Table&am…

使用kafka改造分布式事务

文章目录 1、kafka确保消息不丢失&#xff1f;1.1、生产者端确保消息不丢失1.2、kafka服务端确保消息不丢失1.3、消费者确保正确无误的消费 2、生产者发送消息 KafkaService3、UserInfoServiceImpl -> login()4、service-account - > AccountListener.java 1、kafka确保消…

Renesa Version Board开发RT-Thread 之UART驱动应用

目录 概述 1 硬件介绍 2 软件配置 2.1 RT-Thread Studio配置参数 2.2 FSP配置MCU 3 RT-Thread中UART的接口介绍 3.1 RT-Thread UART简介 3.2 RT-Thread 下的UART接口 4 UART的应用 4.1 应用功能实现 4.2 源代码文件 5 测试 程序下载地址&#xff1a; RenesaVersio…