• win32之临界区


    线程安全问题

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

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

    多线程的线程安全问题前提:
    1、有全局变量
    2、对全局变量有写的权限

    我们写一段代码,模拟一下两个进程访问一个全局变量,代码如下:

    #include <stdio.h>
    #include <windows.h>
    
    int g_dwTickets = 10;
    
    DWORD WINAPI MyFirstThreadProc(LPVOID lpParameter)
    {
    	while (g_dwTickets > 0)
    	{
    		printf("还有: %d 张票
    ", g_dwTickets);
    		g_dwTickets--;
    		printf("卖出一张,还有:%d 张", g_dwTickets);
    	}
    
    	return 0;
    }
    
    //A线程
    DWORD WINAPI ThreadProc(LPVOID lpParameter)
    {
    	for (int i = 0; i < 30; i++)
    	{
    		Sleep(50);
    		printf("++++++++++++++ %d 
    ", i);
    
    	}
    
    	return 0;
    }
    
    //B线程
    DWORD WINAPI ThreadProc2(LPVOID lpParameter)
    {
    	for (int i = 0; i < 30; i++)
    	{
    		Sleep(50);
    		printf("++++++++++++++ %d 
    ", i);
    
    	}
    
    	return 1;
    }
    
    int main()
    {
    	HANDLE aThreadHandles[2];
    	DWORD dwResult1;
    	DWORD dwResult2;
    
    	//线程A
    	aThreadHandles[0] = CreateThread(NULL, 0, MyFirstThreadProc, NULL, 0, NULL);
    	//线程B
    	aThreadHandles[1] = CreateThread(NULL, 0, MyFirstThreadProc, NULL, 0, NULL);
    
    	//等待线程结束了
    	WaitForMultipleObjects(2, aThreadHandles, True, INFINITE);
    
    	//当线程执行完了
    	GetExitCodeThread(aThreadHandles[0], &dwResult1);
    	GetExitCodeThread(aThreadHandles[1], &dwResult2);
    	printf("%d %d 
    ", dwResult1, dwResult2);
    
    	getchar();
    	return 0;
    }
    
    

    运行一下,发现出现了写问题

    多个线程对同一个全局变量访问的时候,就会存在线程安全问题

    因为创建了两个线程,线程都是有独立的堆栈,各自都是不影响各自的

    也就是说这段代码有两份的,各自跑各自的,但是全局变量只有一份,我写在了下图中:

    因为AB两线程同时访问一个全局变量,交替的在访问一个全局变量,那么再任何一行代码执行完都有可能出现线程切换

    当只剩1张票了,那么A线程判断票大于0,成立吗,很明显,这是成立的
    然后再黄色区域A那边停了,就给切换到了B线程,B线程判断是否大于0

    因为A线程还没有对最后一张票进行修改,所以还是1张, 1 > 0
    那么B线程就执行代码,最后还有0张票,然后又切换给了 A 线程

    A线程不会重头开始跑了,只会从切换前的那个地方开始跑,所以从黄色区域 A开始往下走,最后变成了 -1张票;

    这样的话就会对 多线程访问同时访问全局变量的安全问题 概念更深了;

    解决方法

    #include <stdio.h>
    #include <windows.h>
    
    int g_dwTickets = 10;
    
    DWORD WINAPI MyFirstThreadProc(LPVOID lpParameter)
    {
    	while (g_dwTickets > 0)
    	{
    		printf("还有: %d 张票 
    ", g_dwTickets);
    		g_dwTickets--;
    		printf("卖出一张,还有:%d 张
    
    ", g_dwTickets);
    	}
    
    	return 0;
    }
    
    //A线程
    DWORD WINAPI ThreadProc(LPVOID lpParameter)
    {
    	for (int i = 0; i < 30; i++)
    	{
    		Sleep(50);
    		printf("++++++++++++++ %d 
    ", i);
    
    	}
    
    	return 0;
    }
    
    //B线程
    DWORD WINAPI ThreadProc2(LPVOID lpParameter)
    {
    	for (int i = 0; i < 30; i++)
    	{
    		Sleep(50);
    		printf("++++++++++++++ %d 
    ", i);
    
    	}
    
    	return 1;
    }
    
    int main()
    {
    	HANDLE aThreadHandles[2];
    	DWORD dwResult1;
    	DWORD dwResult2;
    
    	//线程A
    	aThreadHandles[0] = CreateThread(NULL, 0, MyFirstThreadProc, NULL, 0, NULL);
    	//线程B
    	aThreadHandles[1] = CreateThread(NULL, 0, MyFirstThreadProc, NULL, 0, NULL);
    
    	//等待线程结束了
    	WaitForMultipleObjects(2, aThreadHandles, TRUE, INFINITE);
    
    	//当线程执行完了
    	GetExitCodeThread(aThreadHandles[0], &dwResult1);
    	GetExitCodeThread(aThreadHandles[1], &dwResult2);
    	printf("%d %d 
    ", dwResult1, dwResult2);
    
    	getchar();
    	return 0;
    }
    

    像上面这段代码,就是没有把全局变量变为 “临界资源”,而是两个线程同时访问

    访问临界资源的那段代码,叫“临界区”; 我们就需要自己构建一段临界区
    我们可以自己写代码实现,也可以使用windows提供的API构建

    Windows的实现方式:
    再设置个全局变量,就是令牌;临界区的代码实现之前,要获取令牌,看看令牌有没有人拿,这个令牌
    就是全局变量,是 1 或者 0。拿到之后设置为0,代表某人拿到了

    那段代码就是对全局变量进行访问,当这个过程中其他线程会试着访问,但是会先获取令牌,令牌为0,那么就无法去访问了

    临界区实现之线程锁

    1、创建全局变量
    CRITICAL_SECTION cs
    2、初始化全局变量
    InitializeCriticalSection(&cs)
    3、实现临界区
    EnterCriticalSection(&cs) //进入临界区
    LeaveCriticalSection(&cs) //使用临界资源

    开始操作,如下:

    我们先创建的话这个结构体是全局的CRITICAL_SECTION,然后再初始化
    然后我们就在main函数内初始化

    然后我们就真正的对这个全局变量访问的时候,我们就可以构建临界区了
    在读取之前,我们要构建一下临界区

    两个线程用的都是同一份代码,所以可以理解成如上图那样,但是代码这样写是这样的,但是逻辑上有问题

    比如在while循环判断的时候还是有问题的,这时候我们可以尝试把进入临界区的入口放到while循环前面,应该就OK了

  • 相关阅读:
    SQLServer 高可用、高性能和高保护延伸
    SQLServer 通过DMV实现低影响的自动监控和历史场景追溯
    查找表包含的页和页所在的表
    出身在二三线城市软件工作者的悲哀
    SQL语句实现取消自增列属性
    基于Apache(without ssl)的svn环境搭建
    sqlite3 命令行操作
    HTML常用特殊符号集
    IOS项目目录结构和开发流程
    Mac OSX 快捷键&命令行
  • 原文地址:https://www.cnblogs.com/0x7e/p/13833348.html
Copyright © 2020-2023  润新知