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;
}

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

win32之临界区第1张

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

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

win32之临界区第2张

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

win32之临界区第3张

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

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

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

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

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

解决方法

win32之临界区第4张

#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,代表某人拿到了

win32之临界区第5张

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

临界区实现之线程锁

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

开始操作,如下:

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

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

win32之临界区第6张

win32之临界区第7张

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

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

免责声明:文章转载自《win32之临界区》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇PHPCMS V9教程之快速入门C# 使用配置文件配置应用下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

delphi 多线程 数据库

// 线程类unit Unit2; interface uses Classes; type TMyThread = class(TThread) private FUserName: string; FPassWord: string; FFlag: Boolean; procedure GetUserName(const Value: string);...

【面试】iOS 开发面试题(二)

1. 我们说的oc是动态执行时语言是什么意思?  答案:多态。主要是将数据类型的确定由编译时,推迟到了执行时。  这个问题事实上浅涉及到两个概念。执行时和多态。  简单来说。执行时机制使我们直到执行时才去决定一个对象的类别,以及调用该类别对象指定方法。  多态:不同对象以自己的方式响应同样的消息的能力叫做多态。意思就是如果生物类(life)都用有一个同样...

第四章:文件stat获取函数

所在的头文件 <sys/stat.h> struct  stat{ mode_t  st_mode; /*文件的访问模式*/ ino_t   st_ino;  /*i节点的信息*/ dev_t   st_dev;  /*设备号*/ dev_t   st_rdev;  /*特殊文件的设备号*/ nlink_t  st_nlink;  /*硬链接接...

多线程实现Thread.Start()与ThreadPool.QueueUserWorkItem两种方式对比

Thread.Start(),ThreadPool.QueueUserWorkItem都是在实现多线程并行编程时常用的方法。两种方式有何异同点,而又该如何取舍? 写一个Demo,分别用两种方式实现。观察各自的现象。  一个WorkMan class,其内的method doSomething()是每次异步线程调用的方法。该方法只是随机的让线程休眠一段时间。...

【Android】Android中线程的应用

1. Android进程    在了解Android线程之前得先了解一下Android的进程。当一个程序第一次启动的时候,Android会启动一个LINUX进程和一个主线程。默认的情况下,所有该程序的组件都将在该进程和线程中运行。同时,Android会为每个应用程序分配一个单独的LINUX用户。Android会尽量保留一个正在运行进程,只在内存资源出现不足...

Go并发

  进程、线程、协程     进程:进程是操作系统资源分配的最小单位       进程有自己的虚拟地址空间,这个空间包括了各种资源,例如堆、栈,各种段,它们其实都是虚拟地址空间的一块区域。所以说进程是资源分配的最小单位。     线程:线程是操作系统任务调度和执行的最小单位。       线程包含在进程之中,是进程中实际运作单位     协程:线程中协作式...