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

news2024/9/22 20:14:20

一、前言

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

临界区(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]);          // 返回线程ID
        if (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]);          // 返回线程ID
        if (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.coloradmin.cn/o/2074770.html

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

相关文章

【游戏速递】 小猪冲刺:萌动指尖的极速挑战,小虎鲸Scratch资源站独家献映!

在线玩&#xff1a;Scratch小猪冲刺&#xff1a;全新挑战的几何冒险游戏-小虎鲸Scratch资源站 想象一下&#xff0c;一群憨态可掬的小猪&#xff0c;穿上炫酷的装备&#xff0c;踏上了追逐梦想的赛道。它们或跳跃、或滑行&#xff0c;灵活躲避各种障碍&#xff0c;只为那终点的…

微软亚研院哈佛:同行评议互一致的rStar

本来想将近期另一篇DeepSeek的“DeepSeek-Prover-V1.5: Harnessing Proof Assistant Feedback for Reinforcement Learning and Monte-Carlo Tree Search”与这篇同样基于强化学习思想的小型清爽型推理模型放在一个笔记中相互对比借鉴一下&#xff0c;考虑虽然两者有着一些共通…

论文3解析(复现):六自由度机械臂轨迹规划研究+机器人基础知识-部分1

论文&#xff1a;六自由度机械臂轨迹规划研究&#xff0c;马强 机器人一些关于数学基础的知识&#xff0c;简单的说一下&#xff1a; 向量 在机器人中&#xff0c;向量的含义并不是算算数那样子&#xff0c;而是在空间的本质含义。 向量叉乘&#xff1a;ab |a|*|b|*sin&am…

燃烧控制模型

加热炉燃烧控制 主要功能&#xff1a; 1&#xff0e; 把要轧制的钢坯加热的规定温度&#xff0c;即出炉目标温度&#xff0c;并尽量减少黑印。 2&#xff0e; 协调加热炉及轧机的生产能力&#xff0c;以提高轧机总的生产效率。 3&#xff0e; 节省燃料 在轧钢生产过程中&#x…

s3c2440移植Linux内核之引导

最近想尝试把新的Linux内核移植到tq2440的开发板上&#xff0c;看看还能不能顺利的跑起来。我的基础版本是买板子的时候提供的2.6.30版本&#xff0c;编译器版本是4.3.3.。 下载源码和编译器 下载linux源码&#xff0c;源码的官方网站是The Linux Kernel Archives&#xff0c…

沉积层的厚度为自振周期波长的1/4

要理解为什么是1/4&#xff0c;需要明白如下两点。 &#xff08;1&#xff09;自振周期&#xff08;fundamental model, or first harmonic&#xff09;取决于在某边界条件下可以出现驻波&#xff08;standing wave&#xff09;的最短距离。Standing wave, also known as a st…

AI助力水体保护区无人值守垂钓智能预警,基于YOLOv8全系列【n/s/m/l/x】参数模型开发构建水体保护区场景下无人值守垂钓智能检测预警系统

保护我们赖以生存的自然生态环境&#xff0c;无疑是一项意义深远且需要长期坚持的任务。自然界的生态系统&#xff0c;由水、气、森林、土壤等多要素组成&#xff0c;它们相互依存、相互影响&#xff0c;共同维系着地球的生态平衡。然而&#xff0c;在人类活动的影响下&#xf…

浅谈进程,线程,协程以及服务端高并发的处理

进程、线程、协程 进程&#xff1a;独立的程序实例&#xff0c;资源开销较大&#xff0c;适合隔离性要求高的任务。 独立性&#xff1a;进程具有独立的内存空间和资源&#xff0c;互不干扰。 资源开销大&#xff1a;由于每个进程都需要分配独立的内存和资源&#xff0c;创建和…

5个常用的物理仿真JavaScript插件

还是大剑师兰特&#xff1a;曾是美国某知名大学计算机专业研究生&#xff0c;现为航空航海领域高级前端工程师&#xff1b;CSDN知名博主&#xff0c;GIS领域优质创作者&#xff0c;深耕openlayers、leaflet、mapbox、cesium&#xff0c;canvas&#xff0c;webgl&#xff0c;ech…

【Python学习手册(第四版)】学习笔记21-模块概览

个人总结难免疏漏&#xff0c;请多包涵。更多内容请查看原文。本文以及学习笔记系列仅用于个人学习、研究交流。 import操作和模块是Python之中程序架构的核心。本文主要介绍了模块、属性以及导入的基础知识&#xff0c;并探索了import语句的操作&#xff08;搜索、可选编译、…

不同搜索引擎蜘蛛的功能、‌抓取策略与技术实现差异探究

搜索引擎作为互联网信息检索的重要工具&#xff0c;‌其核心功能依赖于背后的“蜘蛛”程序。‌这些蜘蛛程序负责访问互联网上的各种内容&#xff0c;‌并建立索引数据库&#xff0c;‌以便用户能够快速准确地找到所需信息。‌然而&#xff0c;‌不同搜索引擎的蜘蛛在功能、‌抓…

Python爬取静态网页技术解析

内容导读 实现HTTP请求解析网页存储数据静态网页爬取实例 一、实现HTTP请求 1、爬虫场景简介 &#xff08;1&#xff09;基本功能 爬虫的基本功能是读取URL和爬取网页内容&#xff0c;这就需要爬虫具备能够实现HTTP请求的功能。请求过程主要包括生成HTTP请求、请求头处理、…

《Programming from the Ground Up》阅读笔记:p95-p102

《Programming from the Ground Up》学习第6天&#xff0c;p95-p102总结&#xff0c;总计8页。 一、技术总结 1.directive(伪指令) 很多资料喜欢把directive和instruction都翻译成“指令”&#xff0c;这样在看到指令这个词时就不知道到底指的是什么&#xff1f;这里参考其它…

文件包含漏洞案例

一、PHP://INPUT Example 1&#xff1a;造成任意代码执行 源代码&#xff1a; <meta charset"utf8"> <?php error_reporting(0); $file $_GET["file"]; if(stristr($file,"php://filter") || stristr($file,"zip://") |…

在技术风暴中站稳脚跟:构建软件服务团队的应急韧性与高效响应力

在数字化浪潮汹涌的今天&#xff0c;软件服务已成为连接用户与企业的桥梁&#xff0c;其稳定性直接关系到用户体验、品牌信誉乃至企业的生存与发展。然而&#xff0c;即便是拥有庞大用户基础和先进技术的平台&#xff0c;如网易云音乐&#xff0c;也难以完全避免技术故障的突袭…

MySQL 系统学习系列 - SQL 语句 DQL 语句的使用(3)《MySQL系列篇-05》

SQL 语句 DQL 多表连接查询 连接与多表查询&#xff1a;连接是在多个表之间通过一定的连接条件&#xff0c;使表之间发生关联&#xff0c;进而能从多个表之间获取数据 基本简介与表之间的搭建&#xff08;用于使用多表查询语句-即准备工作&#xff09; 单词普及(名称)单词连…

HT-360A 360度全向强声广播、应急广播、全向声波驱鸟

1、产品简介 HT-360A多层叠装360向广播是北京恒星科通科技发展有限公司自主研发的一款应急广播专用设备&#xff0c;该设备内部采用4组换能器垂直阵列设置&#xff0c;水平采用指数函数碟形堆叠技术&#xff0c;在垂直方向上多层碟扬声器可实现360度环形垂直阵列&#xff0c;实…

MYSQL集群技术

---------------第一部分---------------------- 一.mysql源码部署 环境&#xff1a;rhel7.9 1.1.下载安装包 官网&#xff1a;http://www.mysql.com 1.2.在linux下部署mysql 1.创建登录用户和数据目录&#xff0c;并给数据目录赋权&#xff0c;因为配置文件读取需要权限&…

Delphi5实现秒钟程序

效果图 目的 这个项目非常简单&#xff0c;开发这个是为了方便看秒钟&#xff0c;进行秒杀活动。 虽然目前啥也抢不到&#xff0c;但是有志者事竟成。 完整代码 unit Unit1;interfaceusesSysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,Dialogs, For…

了解prolog规则

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