win32之临界区

线程安全问题

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

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

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

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

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

int g_dwTickets = 10;

DWORD WINAPI MyFirstThreadProc(LPVOID lpParameter)
{
	while (g_dwTickets > 0)
	{
		printf("还有: %d 张票\n", 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 \n", i);

	}

	return 0;
}

//B线程
DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
	for (int i = 0; i < 30; i++)
	{
		Sleep(50);
		printf("++++++++++++++ %d \n", 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 \n", 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 张票 \n", g_dwTickets);
		g_dwTickets--;
		printf("卖出一张,还有:%d 张\n\n", g_dwTickets);
	}

	return 0;
}

//A线程
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
	for (int i = 0; i < 30; i++)
	{
		Sleep(50);
		printf("++++++++++++++ %d \n", i);

	}

	return 0;
}

//B线程
DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
	for (int i = 0; i < 30; i++)
	{
		Sleep(50);
		printf("++++++++++++++ %d \n", 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 \n", 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了

posted @ 2020-10-19 10:03  0X7e  阅读(207)  评论(0编辑  收藏  举报