临界区的实现原理

转自:http://my.oschina.net/myspaceNUAA/blog/81244


临界区概述:

用于多线程的互斥访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入临界区后,其他试图访问的线程将被挂起,直到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到对临界区的互斥访问。(临界区中一般都是一个简短的代码段)
在WINDOWS中,临界区是一种应用层的同步对象,非内核对象。并且临界区优先采用自旋的方式进行抢占


临界区API:

临界区初始化以及删除:

InitializeCriticalSection()
DeleteCriticalSection()

临界区两个操作原语: 

EnterCriticalSection()
LeaveCriticalSection()

临界区数据结构:

typedef struct _RTL_CRITICAL_SECTION {
    PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
    //
    //  The following three fields control entering and exiting the critical
    //  section for the resource
    //

    LONG LockCount;
    LONG RecursionCount;
    HANDLE OwningThread;        // from the thread's ClientId->UniqueThread
    HANDLE LockSemaphore;
    ULONG_PTR SpinCount;        // force size on 64-bit systems when packed
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;


_RTL_CRITICAL_SECTION_DEBUG数据结构
typedef struct _RTL_CRITICAL_SECTION_DEBUG {
    WORD   Type;
    WORD   CreatorBackTraceIndex;
    struct _RTL_CRITICAL_SECTION *CriticalSection;
    LIST_ENTRY ProcessLocksList;
    DWORD EntryCount;
    DWORD ContentionCount;
    DWORD Flags;
    WORD   CreatorBackTraceIndexHigh;
    WORD   SpareWORD  ;
} RTL_CRITICAL_SECTION_DEBUG, *PRTL_CRITICAL_SECTION_DEBUG, RTL_RESOURCE_DEBUG, *PRTL_RESOURCE_DEBUG;
(代码来自VS2005 WINNT.h)


_RTL_CRITICAL_SECTION 各字段解释: 
DebugInfo:指向一个调试用的数据,该结构的类型为RTL_CRITICAL_SECTION_DEBUG
LockCount: 初始值-1,若结果大于等于0,表示该临界区已被线程占用。
OwningThread: 当前拥有临界区的线程 
RecursionCount:所有者线程连续进入临界区的次数 
LockSemaphore: 内核对象句柄,用于告知操作系统,该临界区目前处于空闲状态,用于唤醒因等待临界区而挂起的线程  
SpinCount:MSDN对该字段做如下解释:
"On multiprocessor systems, if the critical section is unavailable, the calling thread will spin 
dwSpinCount times before performing a wait operation on a semaphore associated with the critical 
section. If the critical section becomes free during the spin operation, the calling thread 
avoids the wait operation." 

在多处理器系统中,如果临界区已被占用,那么线程就自旋SpinCount次去获取临界区,而不是通过阻塞等待的方式去获取临界区。如果在自旋的过程中临界区空间,就可以直接进入临界区,减少等待时间(如果进入等待状态,需要用户态内核态的切换,代价较大)。主要意思就是为了提高效率,下面我们会分析什么叫自旋。

 

临界区API实现过程 

InitialzeCriticalSection

在初始化的过程中,会测试CPU的数量,若CPU数量为1,则忙等待没有意义。则SpinCount=0,
若CPU数量大于1,则设置SpinCount,在进入临界区时,会采取主动进入策略。

 EnterCriticalSection

1. 若临界区还未被占用,则更新临界区数据结构,表示调用线程已经获得访问临界区的权限,返回。
2. 若线程在已经获取访问权限的情况下,再次EnterCriticalSection,则更新线程获取访问的次数(即连续Enter的次数)。
3. 若临界区已经其他线程占用,则当前线程 通过SpinCount来控制忙等的次数,在SpinCount已经等于0还没有获得临界区对象的情况下,函数直接通过临界区对象内部的事件对象进行等待(等待及唤醒涉及到用户态和内核态的切换,不是最优方案,优先采用自旋的方式进入临界区)。忙等待是通过对LockCount进行原子读写操作实现。

可以在如下网址看具体汇编代码:来源:(http://blog.youkuaiyun.com/joeleechj/article/details/7081124)

 RtlLeaveCriticalSection

1. _RTL_CRITICAL_SECTION数据结构中相关标志位设置 ,比如RecursionCount--,如果为0,表示没有线程占用临界区
2. 将当前占有线程句柄设为0,表示现在临界区目前处于有信号状态,可以被获取
3. 若有其他线程在等待,唤醒等待线程

 

CRITICAL_SECTION和CRITICAL_SECTION_DEBUG之间的关系
http://msdn.microsoft.com/zh-cn/magazine/cc164040(en-us).aspx)

通过该数据结构可以发现,在进程中,所有的临界区的DEBUG信息通过链表进行串接。在已知某个临界区对象的
情况下, 通过链表数据结构,可以访问到数据的临界区对象。

自旋:

对于临界区的操作,(EnterCriticalSection)操作,采用的是主动进入临界区。意思就是,当没有能够进入时,不停的主动尝试进入,直至进入为止。这种主动进入的方式,称为自旋(也叫忙等待)。
被动方式:获取不到后,进入等待队列,当要获取的对象被释放后,系统唤醒等待的线程,这个方式叫做被动方式。

小结:

1.进入灵界区和离开临界区是成对操作,进入临界区必须要有离开临界区否则临界区保护的共享资源将永远不会被释放。

2.在使用临界区时,临界区间使用的代码最好简短,减少其他线程的等待时间,提高程序性能。

3.临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。

4. 临界区是用户态下的对象,非内核对象,所以在使用时无需再用户态和内核态之间切换,效率明显要比其他用户互斥的内核对象高。

 

参考:

http://msdn.microsoft.com/zh-cn/magazine/cc164040(en-us).aspx


<think>好的,用户想了解如何通过临界区实现进程间的通信,需要具体的方法和示例。首先,我得明确临界区的概念。根据引用[1],临界资源是进程间共享的资源,如果不保护可能导致数据不一致。临界区其实就是访问这些资源的代码段,需要互斥访问。 接下来,用户可能混淆了临界区和进程间通信(IPC)的关系。根据引用[2],进程间通信需要看到同一份资源,通常由OS提供。而临界区主要是保护共享资源,属于同步机制,而不是直接的通信方法。需要指出临界区本身不用于传输数据,而是确保安全访问共享资源,从而间接支持通信。 然后,具体实现方法。在单机多进程环境下,可以用信号量或互斥锁。比如POSIX信号量,或者共享内存结合信号量。示例的话,可以用C语言配合共享内存和信号量,展示如何同步访问。比如创建共享内存区域,用信号量控制进入临界区,进程A写入数据,进程B读取,确保操作原子性。 还要注意,临界区通常用于线程同步,而进程间可能需要跨进程的同步机制,比如命名信号量。需要区分线程和进程的不同处理方式。同时,示例代码需要展示初始化和使用信号量保护共享内存的过程,以及正确释放资源。 最后,生成相关问题时要围绕临界区的作用、同步机制的区别、实际应用场景以及潜在问题,帮助用户进一步理解。</think>### 通过临界区实现进程间通信的方法 临界区(Critical Section)本身是一种同步机制,主要用于保护共享资源(即临界资源)在多线程或多进程环境中的互斥访问。**严格来说,临界区本身不是进程间通信(IPC)的直接方法,而是保证进程间通信时数据一致性的核心手段**[^1]。 #### 实现原理 1. **临界区的作用** 当多个进程需要访问共享内存、文件或命名管道等资源时,临界区确保同一时间只有一个进程能访问该资源。通过互斥锁(Mutex)或信号量(Semaphore)实现,防止数据竞争和不一致性[^2]。 2. **与进程间通信的关系** 进程间通信(如共享内存、管道)需要依赖临界区保护对共享资源的操作。例如: - 共享内存写入前需加锁 - 管道读取时需确保原子操作 #### 实现步骤(以共享内存+信号量为例) 1. **创建共享内存区域** 使用`shmget`分配共享内存块,作为进程间通信的缓冲区。 2. **初始化信号量** 使用`sem_init`创建信号量,用于控制对共享内存的访问。 3. **进程操作流程** - 进程A:请求信号量→进入临界区→写入数据→释放信号量 - 进程B:请求信号量→进入临界区→读取数据→释放信号量 #### 示例代码(C语言) ```c #include <sys/shm.h> #include <semaphore.h> // 创建共享内存 int shm_id = shmget(IPC_PRIVATE, sizeof(int), 0666|IPC_CREAT); int* shared_data = (int*)shmat(shm_id, NULL, 0); // 初始化命名信号量(跨进程) sem_t *sem = sem_open("/mysem", O_CREAT, 0644, 1); // 进程A写入数据 sem_wait(sem); // 进入临界区 *shared_data = 42; sem_post(sem); // 退出临界区 // 进程B读取数据 sem_wait(sem); int value = *shared_data; sem_post(sem); ``` ### 关键注意事项 1. **信号量的跨进程特性** 需使用命名信号量(如`sem_open`)而非匿名信号量,才能在不同进程间同步[^4]。 2. **资源释放** 操作完成后需调用`shmdt`分离共享内存,并通过`shmctl`释放资源。 3. **错误处理** 所有系统调用需检查返回值,防止死锁或资源泄漏。 4. **原子性保证** 临界区内的操作应保持简短,避免长时间占用导致性能问题[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值