1 引入
为什么会存在临界区这中机制呢?是为多线程同时访问全局变量而引入的。也就是上一篇帖子的末尾流出的问题程序的解决办法。
看懂了上面的,那么我们再罗嗦总结一下:
1.多线程访问全局变量时,存在线程安全问题。
2.局部变量不存在线程安全问题。
2 临界区的使用
2.1 创建CRITICAL_SECTION:
CRITICAL_SECTION cs;
2.2 在使用前进行初始化
InitializeCriticalSection(&cs);
2.3 在函数中使用
DWORD WINAPI 线程A(PVOID pvParam)
{
EnterCriticalSection(&cs);
//对全局遍历X的操作
LeaveCriticalSection(&cs);
return(0);
}
DWORD WINAPI 线程B(PVOID pvParam)
{
EnterCriticalSection(&g_cs);
//对全局遍历X的操作
LeaveCriticalSection(&g_cs);
return(0);
}
2.4 删除CRITICAL_SECTION
VOID DeleteCriticalSection(PCRITICAL_SECTION pcs);//当线程不再试图访问共享资源时
3 CRITICAL_SECTION
3.1 结构
typedef struct _RTL_CRITICAL_SECTION {
PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
LONG LockCount;
LONG RecursionCount;
HANDLE OwningThread;
HANDLE LockSemaphore;
DWORD SpinCount;
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
LockCount:
它被初始化为数值 -1
此数值等于或大于 0 时,表示此临界区被占用
等待获得临界区的线程数:LockCount - (RecursionCount -1)
RecursionCount:
此字段包含所有者线程已经获得该临界区的次数
OwningThread:
此字段包含当前占用此临界区的线程的线程标识符
此线程 ID 与GetCurrentThreadId 所返回的 ID 相同
3.2 测试代码
#include "stdafx.h"
#include <windows.h>
CRITICAL_SECTION cs;
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
for(int x=0;x<1000;x++)
{
EnterCriticalSection(&cs);
Sleep(1000);
printf("11111:%x %x %x\n",cs.LockCount,cs.RecursionCount,cs.OwningThread);
LeaveCriticalSection(&cs);
}
return 0;
}
DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
for(int x=0;x<1000;x++)
{
EnterCriticalSection(&cs);
Sleep(1000);
printf("22222:%x %x %x\n",cs.LockCount,cs.RecursionCount,cs.OwningThread);
LeaveCriticalSection(&cs);
}
return 0;
}
DWORD WINAPI ThreadProc3(LPVOID lpParameter)
{
for(int x=0;x<1000;x++)
{
EnterCriticalSection(&cs);
Sleep(1000);
printf("33333:%x %x %x\n",cs.LockCount,cs.RecursionCount,cs.OwningThread);
LeaveCriticalSection(&cs);
}
return 0;
}
DWORD WINAPI ThreadProc4(LPVOID lpParameter)
{
for(int x=0;x<1000;x++)
{
EnterCriticalSection(&cs);
Sleep(1000);
printf("44444:%x %x %x\n",cs.LockCount,cs.RecursionCount,cs.OwningThread);
LeaveCriticalSection(&cs);
}
return 0;
}
int main(int argc, char* argv[])
{
InitializeCriticalSection(&cs);
//printf("主线程:%x %x %x\n",cs.LockCount,cs.RecursionCount,cs.OwningThread);
//创建一个新的线程
HANDLE hThread1 = ::CreateThread(NULL, 0, ThreadProc1,
NULL, 0, NULL);
//创建一个新的线程
HANDLE hThread2 = ::CreateThread(NULL, 0, ThreadProc2,
NULL, 0, NULL);
//创建一个新的线程
HANDLE hThread3 = ::CreateThread(NULL, 0, ThreadProc3,
NULL, 0, NULL);
//创建一个新的线程
HANDLE hThread4 = ::CreateThread(NULL, 0, ThreadProc4,
NULL, 0, NULL);
//如果不在其他的地方引用它 关闭句柄
::CloseHandle(hThread1);
::CloseHandle(hThread2);
::CloseHandle(hThread3);
::CloseHandle(hThread4);
Sleep(1000*60*60);
return 0;
}
4 使用时容易出错和造成程序不稳定的地方
4.1 怎样使用是合理的
场景1:
//1. 错误
DWORD WINAPI 线程A(PVOID pvParam)
{
EnterCriticalSection(&cs);
while(g_nIndex < MAX_TIMES)
{
//对全局遍历X的操作
}
LeaveCriticalSection(&cs);
return(0);
}
DWORD WINAPI 线程B(PVOID pvParam)
{
EnterCriticalSection(&cs);
while(g_nIndex < MAX_TIMES)
{
//对全局遍历X的操作
}
LeaveCriticalSection(&cs);
return(0);
}
//2. 正确
DWORD WINAPI 线程A(PVOID pvParam)
{
while(g_nIndex < MAX_TIMES)
{
EnterCriticalSection(&cs);
//对全局遍历X的操作
LeaveCriticalSection(&cs);
}
return(0);
DWORD WINAPI 线程B(PVOID pvParam)
{
while(g_nIndex < MAX_TIMES)
{
EnterCriticalSection(&cs);
//对全局遍历X的操作
LeaveCriticalSection(&cs);
}
return(0);
}
场景2:
//1. 错误
DWORD WINAPI 线程A(PVOID pvParam)
{
EnterCriticalSection(&cs);
//代码xxxxxx
//代码xxxxxx
//对全局遍历X的操作
//代码xxxxxx
//代码xxxxxx
LeaveCriticalSection(&cs);
}
DWORD WINAPI 线程B(PVOID pvParam)
{
EnterCriticalSection(&cs);
//代码xxxxxx
//代码xxxxxx
//对全局遍历X的操作
//代码xxxxxx
//代码xxxxxx
LeaveCriticalSection(&cs);
}
//2. 正确
DWORD WINAPI 线程A(PVOID pvParam)
{
//代码xxxxxx
//代码xxxxxx
EnterCriticalSection(&cs);
//对全局遍历X的操作
LeaveCriticalSection(&cs);
//代码xxxxxx
//代码xxxxxx
}
DWORD WINAPI 线程B(PVOID pvParam)
{
//代码xxxxxx
//代码xxxxxx
EnterCriticalSection(&cs);
//对全局遍历X的操作
LeaveCriticalSection(&cs);
//代码xxxxxx
//代码xxxxxx
}
场景3:
//错误。应该将所有X全局变量放入临界区,否则是没有意义的
DWORD WINAPI 线程A(PVOID pvParam)
{
//代码xxxxxx
//代码xxxxxx
EnterCriticalSection(&cs);
//对全局遍历X的操作
LeaveCriticalSection(&cs);
//代码xxxxxx
//代码xxxxxx
}
DWORD WINAPI 线程B(PVOID pvParam)
{
//代码xxxxxx
//代码xxxxxx
//对全局遍历X的操作
//代码xxxxxx
//代码xxxxxx
}
4.2 应该有多少个CRITICAL_SECTION
全局变量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);
}