CRITICAL_SECTION一共就四个函数:
//定义关键段变量后必须先初始化
void InitializeCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
//进入关键区域
void EnterCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
//离开关关键区域
void LeaveCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
//用完关键段变量之后销毁
void DeleteCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
为了解决多线程问题,我们设置二个关键区域:一个是主线程在递增子线程序号时,另一个是各子线程互斥的访问输出全局资源时。详见代码:
#include <stdio.h>
#include <process.h>
#include <windows.h>
long lGlobVar = 0;//全局资源
//关键段变量声明
CRITICAL_SECTION csGlobVar;
CRITICAL_SECTION csPtrParam;
unsigned int WINAPI ThreadProc(void* lpParam){
//由于创建线程是要一定的开销的,所以新线程并不能第一时间执行到这来
int iThreadNo = *(int*)lpParam;
LeaveCriticalSection(&csPtrParam);//离开子线程序号关键区域
Sleep(50);//Do Sth
EnterCriticalSection(&csGlobVar);//进入各子线程互斥区域
lGlobVar++;//处理全局资源
Sleep(50);//Do Sth
printf("线程编号:%d,全局变量:%d.\n", iThreadNo, lGlobVar);
LeaveCriticalSection(&csGlobVar);//离开各子线程互斥区域
return 0;
}
void main()
{
InitializeCriticalSection(&csGlobVar);
InitializeCriticalSection(&csPtrParam);
HANDLE hThreads[10];
for(int i = 0; i < 10; i++){//等子线程接收到参数时主线程可能改变了这个i的值
EnterCriticalSection(&csPtrParam);//进入子线程序号关键区域
hThreads[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadProc, &i, 0, NULL);//创建线程
}
//保证子线程已全部运行结束
WaitForMultipleObjects(10, hThreads, TRUE, INFINITE);
for(int i = 0; i < 10; i++){
CloseHandle(hThreads[i]);//关闭线程句柄,使其内核对象计数减一
}
DeleteCriticalSection(&csGlobVar);
DeleteCriticalSection(&csPtrParam);
}
可以看出来,各子线程已经可以互斥的访问与输出全局资源了,但主线程与子线程之间的同步还是有点问题。
因此可以将关键段比作旅馆的房卡,调用EnterCriticalSection()即申请房卡,得到房卡后自己当然是可以多次进出房间的,在你调用LeaveCriticalSection()交出房卡之前,别人自然是无法进入该房间。
回到这个经典线程同步问题上,主线程正是由于拥有“线程所有权”即房卡,所以它可以重复进入关键代码区域从而导致子线程在接收参数之前主线程就已经修改了这个参数。所以关键段可以用于线程间的互斥,但不可以用于同步。
最后总结下关键段:
1.关键段共初始化化、销毁、进入和离开关键区域四个函数。
2.关键段可以解决线程的互斥问题,但因为具有“线程所有权”,所以无法解决同步问题。
3.推荐关键段与旋转锁配合使用。