16.2关键代码段
关键代码段,也成为临界区,工作在用户方式下。它是指一个小代码段,在代码能够执行之前,它必须独占对某些资源的访问权。
16.2.1相关的API函数
在进入关键代码段之前,首先得初始化这样一个关键代码段,这个用InitializeCriticalSection函数实现。
VOID InitializeCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // address of critical
// section object
);
//要想进入关键代码段,需要调用EnterCriticalSection函数,以获得指定的临界区对象的所有权;同样的,线程使用完所保护的资源后,需要调用LeaveCriticalSection函数,释放指定临界区对象的所有权;当不再需要临界区对象时候,可以调用DeleteCriticalSection函数释放该对象。
16.2.2利用关键代码段实现线程同步
#include<windows.h>
#include<iostream.h>
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);
int tickets=100;
CRITICAL_SECTION g_cs;
void 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);
Sleep(4000);//让主线程睡四秒,系统给线程1和线程2分配时间片运行
//关闭事件对象句柄
DeleteCriticalSection(&g_cs);
}
//线程1的入口函数
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
while(TRUE)
{
EnterCriticalSection(&g_cs);
if(tickets>0)
{
Sleep(1);
cout<<"thread1 sell ticket:"<<tickets--<<endl;
LeaveCriticalSection(&g_cs);
}
else
{
LeaveCriticalSection(&g_cs);
break;
}
}
return 0;
}
//线程2的入口函数
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
while(TRUE)
{
EnterCriticalSection(&g_cs);
if(tickets>0)
{
Sleep(1);
cout<<"thread2 sell ticket:"<<tickets--<<endl;
LeaveCriticalSection(&g_cs);
}
else
{
LeaveCriticalSection(&g_cs);
break;
}
}
return 0;
}
//运行结果如图
16.3线程死锁
//以多线程说,如果线程1拥有临界区对象A,等待临界区对象B的拥有权;而线程2拥有临界区对象B,等待临界区对象A的拥有权,这就造成了死锁。
#include<windows.h>
#include<iostream.h>
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);
int tickets=100;
CRITICAL_SECTION g_csA;
CRITICAL_SECTION g_csB;
void 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_csA);
InitializeCriticalSection(&g_csB);
Sleep(4000);//让主线程睡四秒,系统给线程1和线程2分配时间片运行
//关闭事件对象句柄
DeleteCriticalSection(&g_csA);
DeleteCriticalSection(&g_csB);
}
//线程1的入口函数
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
while(TRUE)
{
EnterCriticalSection(&g_csA);
Sleep(1);
EnterCriticalSection(&g_csB);
if(tickets>0)
{
Sleep(1);
cout<<"thread1 sell ticket:"<<tickets--<<endl;
LeaveCriticalSection(&g_csB);
LeaveCriticalSection(&g_csA);
}
else
{
LeaveCriticalSection(&g_csB);
LeaveCriticalSection(&g_csA);
break;
}
}
return 0;
}
//线程2的入口函数
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
while(TRUE)
{
EnterCriticalSection(&g_csB);
Sleep(1);
EnterCriticalSection(&g_csA);
if(tickets>0)
{
Sleep(1);
cout<<"thread2 sell ticket:"<<tickets--<<endl;
LeaveCriticalSection(&g_csA);
LeaveCriticalSection(&g_csB);
}
else
{
LeaveCriticalSection(&g_csA);
LeaveCriticalSection(&g_csB);
break;
}
}
return 0;
}
//运行结果如图,该售票程序没有卖出一张票。所以实现线程同步时,尽量避免死锁。
16.4互斥对象、事件对象与关键代码段的比较
互斥对象和事件对象属于内核对象,利用内核对象进行线程同步时,速度比较慢,但是可以在多个进程的各个线程中进行同步。
关键代码段工作在用户方式下,同步速度较快,但在使用相关代码段时候,很容易进入死锁状态。因为在等待进入关键代码段时无法设定超时值。