临界区

临界区 [1]  指的是一个访问共用资源(例如:共用设备或是共用存储器)的程序片段,而这些共用资源又无法同时被多个 线程访问的特性。当有线程进入临界区段时,其他线程或是 进程必须等待(例如:bounded waiting 等待法),有一些同步的机制必须在临界区段的进入点与离开点实现,以确保这些共用资源是被互斥获得使用,例如: semaphore。只能被单一线程访问的设备,例如: 打印机 [1]  
中文名
临界区
外文名
Critical section
所属范围
计算机程序

简介

每个进程中访问 临界资源的那段代码称为临界区(Critical Section)(临界资源是一次仅允许一个进程使用的共享资源)。每次只准许一个进程进入临界区,进入后不允许其他进程进入。不论是硬件 临界资源,还是 软件 临界资源,多个进程必须互斥地对它进行访问。
多个进程中涉及到同一个 临界资源的临界区称为 相关临界区。.

程序调度法则

进程进入临界区的调度原则是:
1、如果有若干进程要求进入空闲的临界区,一次仅允许一个进程进入。
2、任何时候,处于临界区内的进程不可多于一个。如已有进程进入自己的临界区,则其它所有试图进入临界区的进程必须等待。
3、进入临界区的进程要在有限时间内退出,以便其它进程能及时进入自己的临界区。
4、如果进程不能进入自己的临界区,则应让出CPU,避免进程出现“忙等”现象。

线程同步问题

有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。
临界区在使用时以CRITICAL_SECTION结构对象保护共享资源,并分别用EnterCriticalSection()和LeaveCriticalSection()函数去标识和释放一个临界区。所用到的CRITICAL_SECTION结构对象必须经过InitializeCriticalSection()的初始化后才能使用,而且必须确保所有线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。否则临界区将不会起到应有的作用,共享资源依然有被破坏的可能。
下面通过一段代码展示了临界区在保护 多线程访问的共享资源中的作用。通过两个线程来分别对 全局变量g_cArray[10]进行写入操作,用临界区结构对象g_cs来保持线程的同步,并在开启线程前对其进行初始化。为了使实验效果更加明显,体现出临界区的作用,在线程函数对共享资源g_cArray[10]的写入时,以Sleep()函数延迟1毫秒,使其他线程同其抢占CPU的可能性增大。如果不使用临界区对其进行保护,则共享资源数据将被破坏(参见图1(a)所示计算结果),而使用临界区对线程保持同步后则可以得到正确的结果(参见图1(b)所示计算结果)。
图一 图一
代码实现清单附下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 临界区结构对象
CRITICAL_SECTION g_cs;
// 共享资源
char  g_cArray[10];
UINT  ThreadProc10( LPVOID  pParam)
{
     // 进入临界区
     EnterCriticalSection(&g_cs);
     // 对共享资源进行写入操作
     for  ( int  i = 0; i < 10; i++)
     {
     g_cArray[i]  = a;
     Sleep(1);
     }
     // 离开临界区
     LeaveCriticalSection(&g_cs);
     return  0;
}
UINT  ThreadProc11( LPVOID  pParam)
{
     // 进入临界区
     EnterCriticalSection(&g_cs);
     // 对共享资源进行写入操作
     for  ( int  i = 0; i < 10; i++)
     {
         g_cArray[10 - i - 1] = b;
         Sleep(1);
     }
     // 离开临界区
     LeaveCriticalSection(&g_cs);
     return  0;
}
……
void  CSample08View::OnCriticalSection()
{
     // 初始化临界区
     InitializeCriticalSection(&g_cs);
     // 启动线程
     AfxBeginThread(ThreadProc10, NULL);
     AfxBeginThread(ThreadProc11, NULL);
     // 等待计算完毕
     Sleep(300);
     // 报告计算结果
     CString sResult = CString(g_cArray);
     AfxMessageBox(sResult);
}

临界区存在的几个问题

在使用临界区时,一般不允许其运行时间过长,只要进入临界区的线程还没有离开,其他所有试图进入此临界区的线程都会被挂起而进入到等待状态,并会在一定程度上影响程序的运行性能。尤其需要注意的是不要将等待用户输入或是其他一些外界干预的操作包含到临界区。如果进入了临界区却一直没有释放,同样也会引起其他线程的长时间等待。换句话说,在执行了EnterCriticalSection()语句进入临界区后无论发生什么,必须确保与之匹配的LeaveCriticalSection()都能够被执行到。可以通过添加 结构化异常处理代码来确保LeaveCriticalSection()语句的执行。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。
1、 临界区的退出,不会检测是否是已经进入的线程,也就是说,我可以在A线程中调用进入临界区函数,在B线程调用退出临界区的函数,同样是成功;  [2]  
2、 我在测试临界区的时候,如果我没有调用进入临界区的函数,直接退出的话,系统没有进行判断,但是计数发现了改变,此时此临界区就再也用不了了,因为结构中的数据已经乱掉了。  [2]  
解决方法如下:
ypedef class mutex_lock
{
public:
mutex_lock()
: LockCount(-1)
, hEvent(0)
{
}
~mutex_lock()
{
if(NULL != this->hEvent)
{
CloseHandle(this->hEvent);
}
this->hEvent = NULL;
this->LockCount = -1;
}
long GetLock();
long ReleaseLock();
private:
mutex_lock(mutex_lock&);
long LockCount;
HANDLE hEvent;
} MUTEXLOCK, *LPMUTEXLOCK;
long mutex_lock::GetLock()
{
__asm
{
movebx, [this];//把基址保存在ebx中
lock incdword ptr [ebx];//对LockCount进行加锁加,保证多CPU时的唯一性,
jeLRET1;//如果LockCount加1为0的话,表示没有人在使用资源,同时利用上面的互斥加对资源进行占用。
cmpdword ptr [ebx+4], 0;//此时LockCount加1大于0的情况,表示已有人使用此资源,要对此资源进行加内核锁
jneL1;//如果平常没有创建内核锁,则进行创始一个
push 0;
push 0;
push 0;
push 0;
call dword ptr [CreateEvent];
movedx, eax;//创建内存锁成功后,在同样对hEvent变量进行互斥比较后替换,以处理多个线程同时对此值进行替换的情况,保证只有一个能够成功替换
moveax, 0;
lock cmpxchg dword ptr [ebx+4], edx; //互斥比较替换
jeL1;
push edx;//如果已经被别人替换过了,需要把自己创建的内核锁释放
call dword ptr [CloseHandle];
L1:
push INFINITE;
push [ebx+4];
call dword ptr [WaitForSingleObject]; // 在内核级进行等待
LRET1:
moveax, 0
}
}
long mutex_lock::ReleaseLock()
{
__asm
{
movebx, [this];//把基址保存在ebx中
moveax, -1;
lock xadd dword ptr [ebx], eax; //进行交换自减交换操作,运行后,eax中是第一操作数先前的值,此值用会返回用来判断是否多调用了ReleaseLock
jlLRET2;//没有别的线程占用资源,直接返回,用户可以通过返回值,分析是否失败
cmpdword ptr [ebx+4], 0;//有别的线程在等待,检查内核锁,如果没有则进行创建
jneL2;
push 0;
push 0;
push 0;
push 0;
call dword ptr [CreateEvent];
movedx, eax;
moveax, 0;
lock cmpxchg dword ptr [ebx+4], edx; //互斥替换内核锁句柄
jeL2;//已经有内核锁了,把自已申请的关闭
push edx;
call dword ptr [CloseHandle];
L2:
push [ebx+4];//设计信号,唤醒一个线程
call dword ptr [SetEvent];
moveax, 0;
LRET2:
}
}
[2]  

猜你喜欢

转载自blog.csdn.net/xujingcheng123/article/details/79928503