不论是硬件临界资源,还是软件临界资源,多个线程必须互斥地对它进行访问。每个线程中访问临界资源的那段代码称为临界区(Critical Section)。
每个线程中访问临界资源的那段程序称为临界区(Critical Section)(临界资源是一次仅允许一个线程使用的共享资源)。每次只准许一个线程进入临界区,进入后不允许其他线程进入。不论是硬件临界资源,还是软件临界资源,多个线程必须互斥地对它进行访问。
windows的Critical Section提供了4个API,分别是:
void WINAPI InitializeCriticalSection(
_Out_ LPCRITICAL_SECTION lpCriticalSection
);
void WINAPI DeleteCriticalSection(
_Inout_ LPCRITICAL_SECTION lpCriticalSection
);
void WINAPI EnterCriticalSection(
_Inout_ LPCRITICAL_SECTION lpCriticalSection
);
void WINAPI LeaveCriticalSection(
_Inout_ LPCRITICAL_SECTION lpCriticalSection
);
临界区是一种轻量级机制,在某一时间内只允许一个线程执行某个给定代码段。通常在修改全局数据(如集合类)时会使用临界区。事件、多用户终端执行程序和信号量也用于多线程同步,但临界区与它们不同,它并不总是执行向内核模式的控制转换,这一转换成本昂贵。稍后将会看到,要获得一个未占用临界区,事实上只需要对内存做出很少的修改,其速度非常快。只有在尝试获得已占用临界区时,它才会跳至内核模式。这一轻量级特性的缺点在于临界区只能用于对同一进程内的线程进行同步。
通过InitializeCriticalSection完成初始化,通过DeleteCriticalSection结束控制;通过EnterCriticalSection开启控制,在这个阶段系统只执行下面的代码;其他进程等待他的结束,最后通过LeaveCriticalSection来结束这种控制。
我们通过以下两段代码予以分析:
#include "stdafx.h"
#include <Windows.h>
#include <process.h>
int num = 0;
CRITICAL_SECTION cs;
unsigned WINAPI ThreadFun1(void *arg)
{
int cnt = *(int*)arg;
EnterCriticalSection(&cs);
for (int i = 0; i < cnt; i++)
{
num += 1;
printf("Inc ");
Sleep(10);
}
LeaveCriticalSection(&cs);
return 0;
}
unsigned WINAPI ThreadFun2(void *arg)
{
int cnt = *(int*)arg;
EnterCriticalSection(&cs);
for (int i = 0; i < cnt; i++)
{
num -= 1;
printf("Dec ");
Sleep(10);
}
LeaveCriticalSection(&cs);
return 0;
}
int main()
{
int param = 50;
HANDLE h[2];
InitializeCriticalSection(&cs);
h[0]= (HANDLE)_beginthreadex(NULL, 0, ThreadFun1, ¶m, 0, NULL);
if (h[0] == 0)
{
printf("Can not create a thread 1.\n");
return 0;
}
h[1] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun2, ¶m, 0, NULL);
if (h[1] == 0)
{
printf("Can not create a thread 2.\n");
return 0;
}
WaitForMultipleObjects(2, h, true, INFINITE);
DeleteCriticalSection(&cs);
printf("The num is %d, and end of main.\n", num);
return 0;
}
我们在每个进程的计算过程均使用了临界资源进行了控制,因此在这两个进程的执行过程中,其计算循环都是独立完成的,其运行结果如下:
Dec Dec Dec Dec Dec Dec Dec Dec Dec Dec Dec Dec Dec Dec Dec Dec Dec Dec Dec Dec
Dec Dec Dec Dec Dec Dec Dec Dec Dec Dec Dec Dec Dec Dec Dec Dec Dec Dec Dec Dec
Dec Dec Dec Dec Dec Dec Dec Dec Dec Dec Inc Inc Inc Inc Inc Inc Inc Inc Inc Inc
Inc Inc Inc Inc Inc Inc Inc Inc Inc Inc Inc Inc Inc Inc Inc Inc Inc Inc Inc Inc
Inc Inc Inc Inc Inc Inc Inc Inc Inc Inc Inc Inc Inc Inc Inc Inc Inc Inc Inc Inc
The num is 0, and end of main.
请按任意键继续. . .
从结果上我们可以明细看出,两个函数的循环计算过程中,没有其他进程参与进来是独立运行完成的;
如果我们将控制只独立在计算本身而不是控制到循环全过程,代码如下:
#include "stdafx.h"
#include <Windows.h>
#include <process.h>
int num = 0;
CRITICAL_SECTION cs;
unsigned WINAPI ThreadFun1(void *arg)
{
int cnt = *(int*)arg;
for (int i = 0; i < cnt; i++)
{
EnterCriticalSection(&cs);
num += 1;
printf("Inc ");
LeaveCriticalSection(&cs);
Sleep(10);
}
return 0;
}
unsigned WINAPI ThreadFun2(void *arg)
{
int cnt = *(int*)arg;
for (int i = 0; i < cnt; i++)
{
EnterCriticalSection(&cs);
num -= 1;
printf("Dec ");
LeaveCriticalSection(&cs);
Sleep(10);
}
return 0;
}
int main()
{
int param = 50;
HANDLE h[2];
InitializeCriticalSection(&cs);
h[0]= (HANDLE)_beginthreadex(NULL, 0, ThreadFun1, ¶m, 0, NULL);
if (h[0] == 0)
{
printf("Can not create a thread 1.\n");
return 0;
}
h[1] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun2, ¶m, 0, NULL);
if (h[1] == 0)
{
printf("Can not create a thread 2.\n");
return 0;
}
WaitForMultipleObjects(2, h, true, INFINITE);
DeleteCriticalSection(&cs);
printf("The num is %d, and end of main.\n", num);
return 0;
}
将控制移到循环体内,这样只能保证计算本身是独立的,而循环是无控制的,其运行结果就变得混乱了:
Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc
Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc
Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec
Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec
Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec Inc Dec
The num is 0, and end of main.