Mutex 和 Critical Section 的异同

本文深入探讨了Mutex和CriticalSection的区别,包括性能、进程边界、定义与初始化、结束清除、等待方式、锁释放、等待其他内核对象、线程死亡时的行为以及自我锁定特性。同时提供了一些额外建议,如减少全局共享对象的使用、合理定义锁的生命周期等。

Mutex 和 Critical Section 的异同

MutexCritical Section都是主要用于限制多线程(Multithread)对全局或共享的变量、对象或内存空间的访问。下面是其主要的异同点(不同的地方用绿色表示)。

 

Mutex

Critical Section

性能和速度

慢。

Mutex 是内核对象,相关函数的WaitForSingleObject

ReleaseMutex)需要用户模式(User Mode)到内核模式(Kernel Mode)的转换,在x86处理器上这种转化一般要发费600个左右的 CPU指令周期。

快。

Critical Section本身不是内核对象,相关函数(EnterCriticalSectionLeaveCriticalSection)的调用一般都在用户模式内执行,在x86处理器上一般只需要发费9个左右的 CPU指令周期。只有当想要获得的锁正好被别的线程拥有时才会退化成和Mutex一样,即转换到内核模式,发费600个左右的 CPU指令周期。

能否跨越进程(Process)边界

可以

不可

定义写法

HANDLE hmtx;

CRITICAL_SECTION cs;

初始化写法

hmtx= CreateMutex (NULL, FALSE, NULL);

InitializeCriticalSection(&cs);

结束清除写法

CloseHandle(hmtx);

DeleteCriticalSection(&cs);

无限期等待的写法

WaitForSingleObject (hmtx, INFINITE);

EnterCriticalSection(&cs);

0等待(状态检测)的写法

WaitForSingleObject (hmtx, 0);

TryEnterCriticalSection(&cs);

任意时间等待的写法

WaitForSingleObject (hmtx, dwMilliseconds);

不支持

锁释放的写法

ReleaseMutex(hmtx);

LeaveCriticalSection(&cs);

能否被一道用于等待其他内核对象

可以(使用WaitForMultipleObjectsWaitForMultipleObjectsExMsgWaitForMultipleObjectsMsgWaitForMultipleObjectsEx等等

不可

当拥有锁的线程死亡时

Mutex变成abandoned状态,其他的等待线程可以获得锁。

Critical Section的状态不可知(undefined),以后的动作就不能保证了。

自己会不会锁住自己

不会(对已获得的Mutex,重复调用WaitForSingleObject不会锁住自己。但最后你别忘了要调用同样次数的ReleaseMutex

不会(对已获得的Critical Section,重复调用EnterCriticalSection不会锁住自己。但最后你别忘了要调用同样次数的LeaveCriticalSection

 

下面是一些补充:

l         请先检查你的设计,把不必要的全局或共享对象改为局部对象。全局的东西越少,出问题的可能就越小。

l         每次你使用EnterCriticalSection时,请不要忘了在函数的所有可能返回的地方都加上LeaveCriticalSection。对于Mutex也同样。若你把这个问题和Win32 structured exception或C++ exception一起考虑,你会发现问题并不是那么简单。自定义一个封装类可能是一种解决方案,以Critical Section为例的代码如下所示:

class csholder

{

    CRITICAL_SECTION *cs;

public:

    csholder(CRITICAL_SECTION *c): cs(c)

    { EnterCriticalSection(cs); }

    ~csholder() { LeaveCriticalSection(cs); }

};

 

CRITICAL_SECTION some_cs;

void foo()

{

    // ...

    csholder hold_some(&some_cs);

 

    // ... CS protected code here

 

    // at return or if an exception happens

    // hold_some's destructor is automatically called

}

l         根据你的互斥范围需求的不同,把Mutex或Critical Section定义为类的成员变量,或者静态类变量。

l         若你想限制访问的全局变量只有一个而且类型比较简单(比如是LONG或PVOID型),你也可以使用InterlockedXXX系列函数来保证一个线程写多个线程读。

 

createMutex();

OpenMutex();

ReleaseMutex();

CloseMutex();

         如果你创建的Mutex已经被使用(Mutex已经被创建),则CreateMutex()返回的是指向已经存在的那个Mutex的句柄,此时调用GetLastError()会获得ERROR_ALREADY_EXISTS   的通知,表示Mutex已经存在!而如果Mutex未被创建过,则返回NULL。

<think>嗯,用户问的是P(mutex)是什么意思。首先,我得确定这个术语出现的上下文。通常,“P”“V”操作在操作系统中与进程同步有关,特别是信号量(Semaphore)的概念。P操作可能对应的是wait,而V是signal。不过用户提到的是mutex,也就是互斥锁,所以可能需要区分信号量互斥锁的区别。 首先,我应该回顾一下信号量的基本知识。Dijkstra提出的PV操作,P代表proberen(荷兰语的“尝试”),V代表verhogen(“增加”)。信号量用于控制多个进程对共享资源的访问。而互斥锁(mutex)是一种特殊的信号量,通常初始值为1,用于确保一次只有一个线程访问资源。 那P(mutex)在这里应该是指对互斥锁执行P操作,也就是申请锁。当线程调用P(mutex)时,如果mutex的值大于0,就减1并继续执行;如果等于0,线程会被阻塞,直到mutex可用。这确保了互斥访问。 不过,需要确认用户是否混淆了信号量互斥锁。有时候互斥锁的实现可能信号量不同,比如互斥锁可能有所有权概念,而信号量没有。但P/V操作通常还是与信号量相关,不过可能有些教材或资料中会用P/V来描述互斥锁的操作,这时候需要说明两者之间的联系区别。 另外,用户可能是在学习进程同步或线程同步时遇到了这个术语,需要解释清楚P操作在互斥锁中的作用,以及它在实际编程中的应用场景,比如保护临界区。还要注意可能涉及的上下文,比如不同的编程语言或操作系统中的具体实现差异,但保持一般性的解释可能更合适。 可能还需要举一个例子,比如两个线程试图访问共享资源,P(mutex)确保其中一个进入临界区,另一个等待。然后V(mutex)释放锁。但用户只问了P(mutex),所以重点在申请锁的过程。 另外,确认是否存在其他可能的含义,比如某些特定框架或库中的P函数,但一般来说,在操作系统理论中,P(mutex)应指信号量的P操作应用于互斥锁的情况。 最后,整理思路,分步骤解释:首先定义P操作,然后联系到mutex,说明其在互斥访问中的作用,以及可能的行为(阻塞或继续)。同时比较信号量互斥锁的异同,确保用户不会混淆。</think>P(mutex) 是操作系统中与 **进程同步/线程同步** 相关的核心概念,其含义实现原理可拆解如下: --- ### 一、基础概念 1. **P操作的本质** P操作(源自荷兰语 *proberen*,意为“尝试”)是信号量(Semaphore)机制中的 **原子操作**,用于 **申请资源**。 对互斥锁(mutex)执行 `P(mutex)` 时,实际行为是: ``` if (mutex > 0) { mutex -= 1; // 获取锁 } else { 阻塞当前线程/进程; // 等待锁释放 } ``` 2. **mutex 的含义** mutex 是值为 1 的二进制信号量,用于保证 **临界区(Critical Section)的互斥访问**。 --- ### 二、P(mutex) 的具体行为 | 步骤 | 行为描述 | 结果 | |------|----------|------| | 1 | 检查 mutex 值 | 若 mutex = 1 → 进入临界区;若 mutex = 0 → 阻塞 | | 2 | 原子性修改 mutex | mutex 减 1(仅当 mutex > 0 时) | | 3 | 控制权移交 | 成功获取锁 → 继续执行;未获取 → 加入等待队列 | --- ### 三、与 V(mutex) 的对应关系 - **P(mutex)**:**进入临界区前**调用,尝试获取锁 ```c // 伪代码示例 P(mutex); // 申请锁 访问共享资源; // 临界区代码 V(mutex); // 释放锁 ``` - **V(mutex)**:**退出临界区后**调用,释放锁并唤醒等待线程 ``` mutex += 1; // 释放锁 唤醒等待线程; // 通知其他线程资源可用 ``` --- ### 四、实际应用场景 1. **多线程共享数据** ```python import threading mutex = threading.Semaphore(1) # 初始化 mutex def thread_func(): mutex.acquire() # P(mutex) # 修改共享变量 mutex.release() # V(mutex) ``` 2. **避免竞态条件(Race Condition)** - 例如:多线程同时修改全局变量时,必须通过 `P(mutex)` 保证串行访问。 --- ### 五、与普通信号量的区别 | 特性 | 互斥锁 (mutex) | 普通信号量 (Semaphore) | |--------------|----------------------|------------------------| | 初始值 | 1 | 可设为任意非负整数 | | 所有者概念 | 有(需由获取者释放) | 无 | | 用途 | 互斥访问 | 资源计数/同步 | --- ### 六、常见问题 - **死锁风险**:若线程获取 mutex 后未释放,将导致其他线程无限等待。 - **优先级反转**:低优先级线程占用 mutex 时,可能阻塞高优先级线程。 --- 通过 `P(mutex)` `V(mutex)` 的配合,操作系统能够有效管理并发环境下的资源访问冲突,这是实现线程安全的核心机制之一。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值