线程安全问题
每个线程都有自己的栈,而局部变量是存储在栈中的,这就意味着每个线程都有一份自己的“局部变量”,如果线程
仅仅使用 “局部变量” 那么就不存在线程安全问题
那如果多个线共用一个全局变量呢?
多线程的线程安全问题前提:
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了