为什么有了互斥锁还要条件变量(二者的不同)

一。互斥量和条件变量简介
互斥量(mutex)从本质上说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁以后,任何其他试图再次对互斥锁加锁的线程将会阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为运行状态的线程可以对互斥锁加锁,其他线程将会看到互斥锁依然被锁住,只能回去再次等待它重新变为可用。
条件变量(cond)是在多线程程序中用来实现”等待–》唤醒”逻辑常用的方法。条件变量利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待”条件变量的条件成立”而挂起;另一个线程使“条件成立”。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。线程在改变条件状态前必须首先锁住互斥量,函数pthread_cond_wait把自己放到等待条件的线程列表上,然后对互斥锁解锁(这两个操作是原子操作)。在函数返回时,互斥量再次被锁住。

二。为什么存在条件变量
首先,举个例子:在应用程序中有连个线程thread1,thread2,thread3和thread4,有一个int类型的全局变量iCount。iCount初始化为0,thread1和thread2的功能是对iCount的加1,thread3的功能是对iCount的值减1,而thread4的功能是当iCount的值大于等于100时,打印提示信息并重置iCount=0。
如果使用互斥量,线程代码大概应是下面的样子:

  thread1/2while (1)
       {
             pthread_mutex_lock(&mutex);
             iCount++;
             pthread_mutex_unlock(&mutex);
       }
       thread4:
       while(1)
       {
             pthead_mutex_lock(&mutex);
             if (100 <= iCount)
             {
                   printf("iCount >= 100\r\n");
                   iCount = 0;
                   pthread_mutex_unlock(&mutex);
             }
             else
             {
                   pthread_mutex_unlock(&mutex);
             }
       }

在上面代码中由于thread4并不知道什么时候iCount会大于等于100,所以就会一直在循环判断,但是每次判断都要加锁、解锁(即使本次并没有修改iCount)。这就带来了问题一,CPU浪费严重。所以在代码中添加了sleep(),这样让每次判断都休眠一定时间。但这由带来的第二个问题,如果sleep()的时间比较长,导致thread4处理不够及时,等iCount到了很大的值时才重置。对于上面的两个问题,可以使用条件变量来解决。
首先看一下使用条件变量后,线程代码大概的样子:

thread1/2:
       while(1)
       {
               pthread_mutex_lock(&mutex);
               iCount++;
               pthread_mutex_unlock(&mutex);
               if (iCount >= 100)
               {
                      pthread_cond_signal(&cond);
               }
       }         
       thread4:
       while (1)
       {
              pthread_mutex_lock(&mutex);
              while(iCount < 100)
              {
                     pthread_cond_wait(&cond, &mutex);
              }
              printf("iCount >= 100\r\n");
              iCount = 0;
              pthread_mutex_unlock(&mutex);
       }

从上面的代码可以看出thread4中,当iCount < 100时,会调用pthread_cond_wait。而pthread_cond_wait在上面应经讲到它会释放mutex,然后等待条件变为真返回。当返回时会再次锁住mutex。因为pthread_cond_wait会等待,从而不用一直的轮询,减少CPU的浪费。在thread1和thread2中的函数pthread_cond_signal会唤醒等待cond的线程(即thread4),这样当iCount一到大于等于100就会去唤醒thread4。从而不致出现iCount很大了,thread4才去处理。

需要注意的一点是在thread4中使用的while (iCount < 100),而不是if (iCount < 100)。这是因为在pthread_cond_singal()和pthread_cond_wait()返回之间有时间差,假如在时间差内,thread3又将iCount减到了100以下了,那么thread4就需要在等待条件为真了。

<think>好的,我现在需要向用户介绍信号量互斥锁的关系。首先,我应该回忆一下这两个概念的基本定义作用,然后分析它们的异同点,以及它们在实际应用中的使用场景。 首先,互斥锁(Mutex)是用于多线程编程中,确保同一时间只有一个线程可以访问共享资源,防止数据竞争。当一个线程获取了互斥锁后,其他线程必须等待该锁被释放才能继续执行。这适用于保护临界区,确保线程安全。 然后是信号量(Semaphore),它是由Dijkstra提出的,用于控制对多个共享资源的访问。信号量维护了一个计数器,表示可用资源的数量。线程可以通过P操作(wait)申请资源,计数器减一,如果计数器为负则阻塞;V操作(signal)释放资源,计数器加一,唤醒等待的线程。 接下来需要比较两者的关系。互斥锁实际上是信号量的一种特例,当信号量的计数器初始化为1时,就变成了互斥锁。这时候信号量相当于一个二元信号量,只有01两种状态,互斥锁的功能一致。但需要注意的是,二元信号量互斥锁使用上可能有细微差别,比如信号量通常可以由不同线程释放,而互斥锁通常要求由获取它的同一个线程来释放。 然后要考虑它们的用途差异。互斥锁主要用于互斥访问,确保独占式使用资源;而信号量更适用于同步操作,比如控制多个线程的执行顺序,或者管理有限数量的资源。例如,数据库连接池可以用信号量来管理连接的数量,而修改共享变量时用互斥锁保护。 还需要提到它们的高级类型,比如递归互斥锁允许同一线程多次加锁,而信号量可能有计数信号量二元信号量之分。此外,互斥锁通常带有优先级继承等机制来防止优先级反转,而信号量一般不具备这些特性。 用户可能想知道在什么情况下选择使用哪一个。比如,当需要严格互斥时,互斥锁更合适;当需要协调多个线程的执行顺序或管理资源池时,信号量更合适。同时,要避免误用信号量作为互斥锁,比如未正确初始化计数器导致的问题。 需要确保回答结构清晰,分点说明两者的联系与区别,并举例说明应用场景。同时,注意使用中文,避免技术术语的混淆,用通俗的语言让用户理解。最后检查是否有遗漏点,比如死锁风险、系统级实现差异等,但可能不需要深入细节,保持回答简洁明了。</think>信号量互斥锁是操作系统中实现线程/进程同步的两种核心机制,它们的关系可以通过以下对比来理解: --- ### 一、本质区别 1. **互斥锁 (Mutex)** - 是**二元开关**,只有锁定/解锁两种状态 - 保证**独占访问**,同一时刻仅允许一个线程访问资源 - 示例:$$ \text{锁} = \begin{cases} 0 & \text{(已占用)} \\ 1 & \text{(可用)} \end{cases} $$ 2. **信号量 (Semaphore)** - 是**计数器**,可表示多个可用资源 - 通过PV操作控制资源访问,允许多个线程有限并发 - 示例:$$ \text{信号量值} = N \quad (N \text{为可用资源总数}) $$ --- ### 二、核心联系 - **互斥锁是信号量的特例** 当信号量初始值为1时,退化为互斥锁(即二元信号量) $$ \text{Semaphore}(1) \approx \text{Mutex} $$ - **同步功能互补** - 互斥锁专注**互斥访问**(谁先抢到谁用) - 信号量擅长**资源预约**(例如控制最大并发数) --- ### 三、关键差异表 | 特性 | 互斥锁 | 信号量 | |---------------------|-------------------------|-------------------------| | 所有权 | 必须由加锁者解锁 | 任意线程可操作 | | 资源数量 | 只保护1个资源 | 可管理N个资源 | | 使用场景 | 临界区保护 | 生产者-消费者问题 | | 性能开销 | 较低 | 较高 | | 递归锁 | 部分实现支持 | 不支持 | --- ### 四、典型应用场景 1. **互斥锁适用场景** - 修改全局变量 - 文件写入操作 ```c pthread_mutex_lock(&mutex); balance += 100; // 临界区操作 pthread_mutex_unlock(&mutex); ``` 2. **信号量适用场景** - 数据库连接池(控制最大10连接) ```c sem_wait(&pool); // 申请连接 use_database(); sem_post(&pool); // 释放连接 ``` - 生产者-消费者模型(通过空/满信号量协调) --- ### 五、选择建议 - 需要严格互斥时选**互斥锁** - 需要控制并发度或线程调度时选**信号量** - 注意:误用信号量作为互斥锁可能导致**死锁**(如忘记释放) 通过理解二者特性,可以更精准地设计线程安全架构。实际开发中常组合使用,例如用互斥锁保护共享队列,同时用信号量控制队列长度。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值