上面两节中,我们学习了利用互斥对象、事件对象实现线程同步的方法,这一节,我们继续学习利用关键代码段(临界区)实现线程同步的方法。
如果对互斥对象和事件对象不熟悉,请移步到
Windows中多线程的基础知识2——事件对象
Windows中多线程的基础知识1——互斥对象
关键代码段,也称为临界区,工作在用户方式下。它是指一个小代码段,在代码能够执行前,它必须独占对某些资源的访问权。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。
临界资源是一次仅允许一个进程使用的共享资源。各进程采取互斥的方式,实现共享的资源称作临界资源。
属于临界资源的硬件:打印机,磁带机等;
软件:消息队列,变量,数组,缓冲区等。
诸进程间采取互斥方式,实现对这种资源的共享。
CRITICAL_SECTION不是针对于资源的,而是针对于不同线程间的代码段的,我们能够用它来进行所谓资源的“锁定”,其实是因为我们在任何访问共享资源的地方都加入了EnterCriticalSection和 LeaveCriticalSection语句,使得同一时间只能够有一个线程的代码段访问到该共享资源而已(其它想访问该资源的代码段不得不等待)。
其声明为:
CRITICAL_SECTION cs;//可以理解为锁定一个资源
InitializeCriticalSection函数用来初始化一个临界资源对象。“临界区”CCriticalSection 是临界资源对象指针,该函数无返回值。单进程的各个线程可以使用临界资源对象来解决同步互斥问题,该对象不能保证哪个线程能够获得到临界资源对象,该系统能公平的对待每一个线程。
其原型为:
VOID WINAPI InitializeCriticalSection(
LPCRITICAL_SECTION lpCriticalSection //临界资源对象指针。
);
EnterCriticalSection等待指定临界区对象的所有权,如果该所有权赋予了调用线程,该函数返回;否则,该函数一直等待,从而导致线程等待。LeaveCriticalSection释放指定的临界区对象所有权。
加锁————接下来的代码处理过程中不允许其他线程进行操作,除非遇到LeaveCriticalSection。
VOID WINAPI EnterCriticalSection(
_Inout_ LPCRITICAL_SECTION lpCriticalSection //临界资源对象指针。
);
解锁————EnterCriticalSection之间代码资源已经释放了,其他线程可以进行操作。
VOID WINAPI LeaveCriticalSection(
_Inout_ LPCRITICAL_SECTION lpCriticalSection //临界资源对象指针。
);
删除临界区,先前必须已在InitializeCriticalSection函数中将该对象初始化。
VOID WINAPI DeleteCriticalSection(
Inout LPCRITICAL_SECTION lpCriticalSection //临界资源对象指针。
);
#include
#include
int iTickets = 1000;
CRITICAL_SECTION g_cs;
//线程1的入口函数
DWORD WINAPI Fun1Proc(__in LPVOID lpParameter)
{
while (true)
{
//等待获取关键段对象
EnterCriticalSection(&g_cs);
if (iTickets > 0)
{
std::cout << "thread1 sell tickes:" << iTickets-- << std::endl;
LeaveCriticalSection(&g_cs);
}
else
{
LeaveCriticalSection(&g_cs);
break;
}
}
return 0;
}
//线程2的入口函数
DWORD WINAPI Fun2Proc(__in LPVOID lpParameter)
{
while (true)
{
//等待获取关键段对象
EnterCriticalSection(&g_cs);
if (iTickets > 0)
{
std::cout << "thread2 sell tickes:" << iTickets-- << std::endl;
LeaveCriticalSection(&g_cs);
}
else
{
LeaveCriticalSection(&g_cs);
break;
}
}
return 0;
}
int main()
{
//创建线程
HANDLE hThread1;
HANDLE hThread2;
hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
InitializeCriticalSection(&g_cs);
//以下代码为了防止主线程提前退出,从而使进程退出,也就终止了进程下所有线程的运行
while (true)
{
if (iTickets > 0)
Sleep(200);
else
{
//释放关键代码段对象
DeleteCriticalSection(&g_cs);
getchar();
return 0;
}
}
}
通过以上实例,我们可以看出,利用临界区实现线程同步的方法是:
首先,互斥对象、事件对象和临界区都可以实现线程同步,但是它们又具有显著的差别:
在我们编写多线程程序的时候,首选关键代码段,因为它的使用比较简单,不过一定要防止线程死锁的发生;另外,如果需要在多个进程间的各个线程间实现同步的话,可以使用互斥对象和事件对象。