标题:临界区critical_section


临界区机制是保证在某一时刻只有一个线程能访问数据的简便办法,即在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。临界区不能跨进程。

下面是一个Windows下使用临界区访问共享数据的多线程例子:

 

CRITICAL_SECTION   cs;//临界区

int critical_value = 0;

 

UINT WINAPI ThreadProc(void* arg)

{

    for (int i = 0; i < 5; i++)

    {  

        EnterCriticalSection(&cs);//进入临界区

        critical_value++;

        printf("critical_value = %d\n", critical_value);   

        LeaveCriticalSection(&cs);//退出临界区

    }

    return 1;

}

 

int main(int argc, char* argv[])

{

    HANDLE hThread1;

    HANDLE hThread2;

    UINT uThreadID;

 

    InitializeCriticalSection(&cs);//初始化临界区

 

    hThread1=CreateThread(NULL,0,ThreadProc,NULL,0,NULL);

    hThread2=CreateThread(NULL,0,ThreadProc,NULL,0,NULL);

 

    CloseHandle(hThread1);

    CloseHandle(hThread2);

  

    DeleteCriticalSection(&cs);

    return 0;

}

 

临界区的内部结构解析:

struct RTL_CRITICAL_SECTION

{

    PRTL_CRITICAL_SECTION_DEBUG DebugInfo;

    LONG LockCount;

    LONG RecursionCount;

    HANDLE OwningThread;

    HANDLE LockSemaphore;

    ULONG_PTR SpinCount;

};

 

DebugInfo 此字段包含一个指针,指向系统分配的伴随结构,该结构的类型为

RTL_CRITICAL_SECTION_DEBUG

LockCount 这是临界区中最重要的一个字段。它被初始化为数值 -1;此数值等于或大于 0 时,表示此临界区被占用。当其不等于 -1 时,OwningThread 字段包含了拥有此临界区的线程 ID。此字段与 (RecursionCount -1) 数值之间的差值表示有多少个其他线程在等待获得该临界区。

RecursionCount 此字段包含所有者线程已经获得该临界区的次数。如果该数值为零,下一个尝试获取该临界区的线程将会成功。

OwningThread 此字段包含当前占用此临界区的线程的线程标识符。此线程 ID GetCurrentThreadId 之类的 API 所返回的 ID 相同。

LockSemaphore 它实际上是一个自复位事件,而不是一个信号。它是一个内核对象句柄,用于通知操作系统:该临界区现在空闲。操作系统在一个线程第一次尝试获得该临界区,但被另一个已经拥有该临界区的线程所阻止时,自动创建这样一个句柄。应当调用 DeleteCriticalSection(它将发出一个调用该事件的 CloseHandle 调用,并在必要时释放该调试结构),否则将会发生资源泄漏。

SpinCount 仅用于多处理器系统。在多处理器系统中,如果该临界区不可用,调用线程将在对与该临界区相关的信号执行等待操作之前,旋转 dwSpinCount 次。如果该临界区在旋转操作期间变为可用,该调用线程就避免了等待操作。旋转计数可以在多处理器计算机上提供更佳性能,其原因在于在一个循环中旋转通常要快于进入内核模式等待状态。此字段默认值为零,但可以用InitializeCriticalSectionAndSpinCount API 将其设置为一个不同值。

 

critical_section临界区与mutex,semaphore,event等的区别:

 

1.互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。所以创建互斥量需要的资源更多,如果只为了在进程内部使用的话,使用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的,互斥量一旦被创建,就可以通过名字打开它。

2.互斥量(Mutex),信号量(Semaphore),事件(Event)都可以跨越进程来进行同步数据操作(一个进程创建之后,另外的进程可以通过名字打开它,从而用于进程间的数据同步),而其他的对象与数据同步操作无关。对于进程和线程对象,如果进程和线程在运行状态则为无信号状态,在退出后为有信号状态,所以可以使用WaitForSingleObject()来等待进程和线程退出。

3.通过互斥量可以指定资源被独占的方式使用,但如果有下面一种情况通过互斥量就无法处理,比如现在一位用户购买了一份三个并发访问许可的数据库系统,可以根据用户购买的访问许可数量来决定有多少个线程/进程能同时进行数据库操作,这时候如果利用互斥量就没有办法完成这个要求,信号灯对象可以说是一种资源计数器。



看文字不过瘾?点击我,进入腾讯课堂视频教学
麦洛科菲长期致力于IT安全技术的推广与普及,我们更专业!我们的学员已经广泛就职于BAT360等各大IT互联网公司。详情请参考我们的 业界反馈 《周哥教IT.C语言深学活用》视频

我们的微信公众号,敬请关注