吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2438|回复: 7
收起左侧

[系统底层] win32笔记八 临界区

[复制链接]
huchen 发表于 2024-4-22 00:41
之前的索引


临界区

线程安全问题

每个线程都有自己的栈,而局部变量是存储在栈中的,这就意味着每个线程都有一份自己的“局部变量”,如果线程仅仅使用“局部变量”那么就不存在线程安全问题。

那如果多个线程共用一个全局变量呢?

全局变量是存储在全局区的,如果多线程执行的代码都去访问这个全局变量,那么用的却同一个全局变量,这就是线程安全问题

那多线程一定存在线程安全问题吗?

未必,访问的不是全部变量的话就没问题

那多线程访问全局变量就一定存在问题吗?

也未必,如果只对全局变量是只读的操作,就没有

所以线程安全问题的前提

  1. 有全局变量
  2. 对全局变量不是只读的操作,又写的动作

例子体现问题

#include<stdio.h>
#include<windows.h>

int NumOfTickets = 10;

DWORD WINAPI FirstThread(LPVOID lpParameter)
{
    while (NumOfTickets > 0)
    {
        printf("还有:%d张票\n", NumOfTickets);
        NumOfTickets--;
        printf("卖出一张,还剩下:%d张票\n", NumOfTickets);
    }
    return 0;
}

int main()
{
    DWORD res1;
    DWORD res2;
    HANDLE hThread[2];

    //创建两个线程,并指向同一个函数
    hThread[0] = CreateThread(NULL, 0, FirstThread, NULL, 0, NULL);
    hThread[1] = CreateThread(NULL, 0, FirstThread, NULL, 0, NULL);

    //等线程结束
    WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
    //线程执行完
    GetExitCodeThread(hThread[0], &res1);
    GetExitCodeThread(hThread[1], &res2);
    printf("%d  %d\n", res1, res2);

    return 0;
}

这里的代码是粗略的模拟了一个售票系统,初始有10张票,随着两个线程里票的递减,而打印出票的情况,具体看代码,不难,API都是前面写过的

image-20240420224051082.png

可以看到这里的票情况十分的混乱,甚至出现了-1票的情况

(如果没出现-1票情况的,可以多运行几下)

为了方便理解这样情况的发生过程,请看如下

//第一个线程X
DWORD WINAPI FirstThread(LPVOID lpParameter)
{
    while (NumOfTickets > 0)
    {//(1)
        printf("还有:%d张票\n", NumOfTickets);
        NumOfTickets--;
        printf("卖出一张,还剩下:%d张票\n", NumOfTickets);
    }
    return 0;
}

//全局变量
int NumOfTickets;

//第二个线程Y
DWORD WINAPI FirstThread(LPVOID lpParameter)
{
    while (NumOfTickets > 0)
    {
        printf("还有:%d张票\n", NumOfTickets);
        NumOfTickets--;     //(2)
        printf("卖出一张,还剩下:%d张票\n", NumOfTickets);
    }
    return 0;
}

这里有两个线程,一份全局变量,这两个线程是交替执行的,可能会在任何点进行线程切换

假如,当票只剩一张的时候,X运行到(1)的位置,发生线程切换,Y运行到(2)位置时发生线程切换,此时,票已经是0,X就会打印“还有0张票”,然后-1,打印出”还剩-1票“

通过这个例子相信大家能够理解为什么会有线程安全问题

解决办法

这里又要提到两个新的概念

image-20240420230420824.png

如果想让我们的程序变得安全,首先给这个全局变量起个名字,叫临界资源

什么叫临界资源呢?

是指一次只允许一个线程使用的资源

对这个临界资源访问的代码称为临界区

这个临界区可以由自己来写也可以用API来写,目前不涉及自己来写临界区,需要考虑的东西有点多

Windows实现方式:

首先在设置一个全局变量,称为令牌,线程一要想访问临界资源,就要先访问令牌,是否还在,在的话下面的代码就可以对临界资源进行操作,那线程二三,访问的时候发现令牌不在了,那代码就访问不了临界资源,所以这样就避免了线程安全问题

细心的大佬可能发现问题了,那线程一在访问令牌的时候还能来的及改,那线程二就来访问了,那两个线程不就同时拥有令牌了吗?

所以,windows就把这里令牌的判断和修改变成了原子操作(感兴趣的可以去搜一搜,我也不是很了解T_T),所以这就是自己实现需要考虑之一的事情

临界区实现之线程锁

这里实现线程安全的方式有很多,用下面的例子来做实验

1.创建全局变量

CRITICAL_SECTION cs;

CRITICAL_SECTION结构体

typedef RTL_CRITICAL_SECTION CRITICAL_SECTION;

typedef struct _RTL_CRITICAL_SECTION {
    PRTL_CRITICAL_SECTION_DEBUG DebugInfo;

    //
    //  The following three fields control entering and exiting the critical
    //  section for the resource
    //

    LONG LockCount;
    LONG RecursionCount;
    HANDLE OwningThread;        // from the thread's ClientId->UniqueThread
    HANDLE LockSemaphore;
    ULONG_PTR SpinCount;        // force size on 64-bit systems when packed
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;

2.初始化全局变量

InitializeCriticalSection(&cs);

3.实现临界区

//进入临界区
EnterCriticalSection(&cs);
//离开临界区
LeaveCriticalSection(&cs);

目前就用知道怎么使用和大概的函数意思就行了

代码真正实现

#include<stdio.h>
#include<windows.h>

int NumOfTickets = 10;
CRITICAL_SECTION cs;        //要注意必须是全局变量

DWORD WINAPI FirstThread(LPVOID lpParameter)
{
    EnterCriticalSection(&cs);      //进入临界区
    while (NumOfTickets > 0)
    {
        printf("还有:%d张票\n", NumOfTickets);
        NumOfTickets--;
        printf("卖出一张,还剩下:%d张票\n", NumOfTickets);
    }
    LeaveCriticalSection(&cs);      //离开临界区
    return 0;
}

int main()
{
    DWORD res1;
    DWORD res2;
    HANDLE hThread[2];
    InitializeCriticalSection(&cs);     //初始化

    //创建两个线程,并指向同一个函数
    hThread[0] = CreateThread(NULL, 0, FirstThread, NULL, 0, NULL);
    hThread[1] = CreateThread(NULL, 0, FirstThread, NULL, 0, NULL);

    //等线程结束
    WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
    //线程执行完
    GetExitCodeThread(hThread[0], &res1);
    GetExitCodeThread(hThread[1], &res2);
    printf("%d  %d\n", res1, res2);

    return 0;
}

运行结果

image-20240420233902825.png

可以看到成功实现,没有混乱的输出和-1票的结果

注意线程锁应该要包含所有与临界资源有关的代码,如判断和改写等

假如把线程锁写到循环里面就会出现-1票的结果

image-20240420234208850.png

总结

  1. 了解了什么是线程安全问题
  2. 两个新的概念:临界资源和临界区
  3. 线程安全问题的其中一个解决方法

免费评分

参与人数 4吾爱币 +10 热心值 +4 收起 理由
willJ + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
jhoneyr + 1 + 1 谢谢@Thanks!
ninja2ren + 1 + 1 用心讨论,共获提升!
allspark + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

iphone7 发表于 2024-4-22 09:32
秦始皇站在阿房宫上使劲顶
Gxd1703 发表于 2024-4-22 10:00
HHJ200318 发表于 2024-4-22 15:01
lduml 发表于 2024-4-22 16:50
谢谢分享,
胡箫儿 发表于 2024-4-23 09:52
赞一个, 感谢分享
xiaotao921 发表于 2024-4-24 14:37
感觉很厉害
yunzhongxing206 发表于 2024-5-30 15:40
瞄一下就走
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-14 10:44

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表