每个进程中访问临界资源的那段代码称为临界区(Critical Section)(临界资源是一次仅允许一个进程使用的共享资源)。
每次只准许一个进程进入临界区,进入后不允许其他进程进入。不论是硬件临界资源,还是软件临界资源,多个进程必须互斥地对它进行访问。
如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。 临界区在使用时以CRITICAL_SECTION结构对象保护共享资源,并分别用EnterCriticalSection()和LeaveCriticalSection()函数去标识和释放一个临界区。所用到的CRITICAL_SECTION结构对象必须经过InitializeCriticalSection()的初始化后才能使用,而且必须确保所有线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。否则临界区将不会起到应有的作用,共享资源依然有被破坏的可能。
在使用临界区时,一般不允许其运行时间过长,只要进入临界区的线程还没有离开,其他所有试图进入此临界区的线程都会被挂起而进入到等待状态,并会在一定程度上影响程序的运行性能。尤其需要注意的是不要将等待用户输入或是其他一些外界干预的操作包含到临界区。如果进入了临界区却一直没有释放,同样也会引起其他线程的长时间等待。换句话说,在执行了EnterCriticalSection()语句进入临界区后无论发生什么,必须确保与之匹配的LeaveCriticalSection()都能够被执行到。可以通过添加结构化异常处理代码来确保LeaveCriticalSection()语句的执行。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。
任一时刻只有一个线程可以拥有临界区对象,拥有临界区的线程可以访问被保护起来的资源或代码段,其他希望进入临界区的线程将被挂起等待,直到拥有临界区的线程放弃临界区时为止,这样就保证了不会在同一时刻出现多个线程访问共享资源。
#include <windows.h>
#include <process.h>
int arr[10];
UINT __stdcall Add(LPVOID lParam)
{
EnterCriticalSection(&g_data);
for (int i = 0; i<10;i++ )
{
arr[i]=i;//0-9
}
LeaveCriticalSection(&g_data);
return 1;
}
UINT __stdcall Add2(LPVOID lParam)
{
EnterCriticalSection(&g_data);
for (int i = 9; i>=0 ;i--)
{
arr [i] = 10-i;//10`1
}
LeaveCriticalSection(&g_data);
return 1;
}
int main()
{
InitializeCriticalSection(&g_data); //初始化临界区(关键代码段)
//MFC使用更简单的CCriticalSection lock加锁,unlock解锁。原理相同
//#include <afxmt.h>
hUp=(HANDLE)_beginthreadex(NULL, 0, Add, NULL, NULL, 0);
hUp=(HANDLE)_beginthreadex(NULL, 0, Add2, NULL, NULL, 0);
//AfxBeginThread(Add, NULL);
Sleep(1000);//注意Sleep在输出之前,否则可能先执行输出的一部分,再执行线程
for (int i = 0;i < 10; i++)
{
cout<<arr[i]<<" ";
}
DeleteCriticalSection(&g_data); //删除临界区(关键代码段)
}