1.线程安全问题
现在我们从第一堂课开始思考关于线程的问题
1.单线程执行对线程任务
前面我们在编写一个计时器程序时,一个函数时实现数字的自减并显示,另一个是消息处理函数
此时如果我们不新建一个线程在实现数字自减的功能的话,就会出现消息处理函数线程被占用的问题,因此导致程序崩溃
2.开始、挂起、恢复、终止
这里的挂起和恢复功能没有什么好讲的,调用不同的韩式来实现就行了
如果此时我们想要实现两个线程对一个变量的操作,就会存在问题
前面我们知道了,如果两个线程同时启动,那么内核就会对他们就行不断地切换
并且在切换之前,会将当前线程中各个寄存器的数据存进ConText结构体中
在20ms后,再次切换到该线程时可以实现从前面的进程开始
现在我们创建了两个线程来实现对一个变量的操作
那么就要考虑到以下情况:
1.点击开始1,知道线程1执行完毕,我们再点击线程2,此时结果是正确的
2.点击开始1后,线程1还没执行完毕,我们就点击线程2,此时就相当于两个线程在切换执行,线程2读取变量进行操作后,时间片结束后又轮到线程1执行,但是线程1会POP出ConText结构体中的数据继续前面的进程进行操作,因此就会出现进程1少操作一次的问题
为了避免出现这种情况,我们就要使用临界区来进行限制
2.临界区的使用
3.怎样使用是最合理的
该场景获取令牌函数和归还令牌函数在循环当中,实现的功能是每一次循环获取一次令牌
然后实现对变量的操作,操作完毕后归还令牌
此时另一线程就可以获取令牌,实现操作。。。。。
因此场景一是正确的,可以实现对于多线程的运行
场景二是先获取令牌,而后完成循环,只有将循环完成后才归还令牌
然后另一线程还有获取令牌的机会
因此改场景是将多线程实现了逐个进行的目的,因此不正确
该场景只有线程1当中使用了临界区,但是在线程2中没有临界区的限制
因此就相当于另一线程无视令牌的存在,直接占用内核,因此不正确
多线程使用多全局变量的情况:
下面的代码哪里有问题:
全局变量X
全局变量Y
全局变量Z
线程1
DWORD WINAPI ThreadFunc(PVOID pvParam)
{
EnterCriticalSection(&g_cs);
使用X
使用Y
LeaveCriticalSection(&g_cs);
return(0);
}
线程2
DWORD WINAPI ThreadFunc(PVOID pvParam)
{
EnterCriticalSection(&g_cs);
使用X
使用Z
LeaveCriticalSection(&g_cs);
return(0);
}
线程3
DWORD WINAPI ThreadFunc(PVOID pvParam)
{
EnterCriticalSection(&g_cs);
使用Y
使用X
LeaveCriticalSection(&g_cs);
return(0);
}
解决方案:
CRITICAL_SECTION g_csX;
CRITICAL_SECTION g_csY;
CRITICAL_SECTION g_csZ;
线程1
DWORD WINAPI ThreadFunc(PVOID pvParam)
{
EnterCriticalSection(&g_csX);
使用X
LeaveCriticalSection(&g_csX);
EnterCriticalSection(&g_csY);
使用Y
LeaveCriticalSection(&g_csY);
return(0);
}
线程2
DWORD WINAPI ThreadFunc(PVOID pvParam)
{
EnterCriticalSection(&g_csX);
使用X
LeaveCriticalSection(&g_csX);
EnterCriticalSection(&g_csZ);
使用Z
LeaveCriticalSection(&g_csZ);
return(0);
}
线程3
DWORD WINAPI ThreadFunc(PVOID pvParam)
{
EnterCriticalSection(&g_csX);
使用X
LeaveCriticalSection(&g_csX);
return(0);
}
多线程想要使用不同的资源的时候,我们可以定义不同的令牌
这样就可以实现,某线程需要使用某资源时,获取该资源的指定令牌
待该线程归还令牌后,其他线程才能获取令牌,再使用资源
4.死锁
线程X拿B后在还没拿到A之前,时间片结束,来到线程Y,在时间片没结束之间拿到B,那么就出现了两个线程之间都不归还的情况,就造成了两个线程都无法继续的情况
解决办法就是将两个线程之间获取令牌归还令牌的顺序写成一样
这样一旦线程X拿到B,那么线程2就无法继续进行下去
#include<windows.h>
#include<stdio.h>
CRITICAL_SECTION cs_A;
CRITICAL_SECTION cs_B;
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
EnterCriticalSection(&cs_A);
Sleep(21);
for(int i=0;i<1000;i++)
{
Sleep(1000);
printf("1111:%d %d %d\n",cs_A.LockCount,cs_A.RecursionCount,cs_A.OwningThread);
EnterCriticalSection(&cs_B);
LeaveCriticalSection(&cs_B);
}
LeaveCriticalSection(&cs_A);
return 0;
}
DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
EnterCriticalSection(&cs_B);
for(int i=0;i<1000;i++)
{
//Sleep(1000);
printf("2222:%d %d %d\n",cs_B.LockCount,cs_B.RecursionCount,cs_B.OwningThread);
EnterCriticalSection(&cs_A);
LeaveCriticalSection(&cs_A);
}
LeaveCriticalSection(&cs_B);
return 0;
}
int main()
{
InitializeCriticalSection(&cs_A);
InitializeCriticalSection(&cs_B);
HANDLE hThread1=CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);
HANDLE hThread2=CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
Sleep(1000*10000);
return 0;
}