信号量 互斥锁 条件变量的区别(讲的很好,值得收藏)

本文详细介绍了信号量和互斥锁在多线程多任务同步中的应用,阐述了它们的区别,包括作用域、上锁时的行为以及成功后是否阻塞等关键点。此外,还探讨了信号量与互斥锁的实现细节及应用实例,特别是如何在某些情况下两者可以相互替代。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在semtake的时候,就阻塞在哪里)。而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这个资源。比如对全局变量的访问,有时要加锁,操作完了,在解锁。有的时候锁和信号量会同时使用的”
也就是说,信号量不一定是锁定某一个资源,而是流程上的概念,比如:有A,B两个线程,B线程要等A线程完成某一任务以后再进行自己下面的步骤,这个任务并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类。而线程互斥量则是“锁住某一资源”的概念,在锁定期间内,其他线程无法对被保护的数据进行操作。在有些情况下两者可以互换。

两者之间的区别:

作用域
信号量: 进程间或线程间(linux仅线程间)
互斥锁: 线程间

上锁时
信号量: 只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减一。若value值不大于0,则sem_wait阻塞,直到sem_post释放后value值加一
互斥锁: 只要被锁住,其他任何线程都不可以访问被保护的资源

成功后否则就阻塞

以下是信号灯(量)的一些概念:

信号灯与互斥锁和条件变量的主要不同在于”灯”的概念,灯亮则意味着资源可用,灯灭则意味着不可用。如果说后两中同步方式侧重于”等待”操作,即资源不可用的话,信号灯机制则侧重于点灯,即告知资源可用;没有等待线程的解锁或激发条件都是没有意义的,而没有等待灯亮的线程的点灯操作则有效,且能保持灯亮状态。当然,这样的操作原语也意味着更多的开销。

信号灯的应用除了灯亮/灯灭这种二元灯以外,也可以采用大于1的灯数,以表示资源数大于1,这时可以称之为多元灯。

1. 创建和 注销

POSIX信号灯标准定义了有名信号灯和无名信号灯两种,但LinuxThreads的实现仅有无名灯,同时有名灯除了总是可用于多进程之间以外,在使用上与无名灯并没有很大的区别,因此下面仅就无名灯进行讨论。

int sem_init(sem_t *sem, int pshared, unsigned int value)
这是创建信号灯的API,其中value为信号灯的初值,pshared表示是否为多进程共享而不仅仅是用于一个进程。LinuxThreads没有实现多进程共享信号灯,因此所有非0值的pshared输入都将使sem_init()返回-1,且置errno为ENOSYS。初始化好的信号灯由sem变量表征,用于以下点灯、灭灯操作。

int sem_destroy(sem_t * sem)
被注销的信号灯sem要求已没有线程在等待该信号灯,否则返回-1,且置errno为EBUSY。除此之外,LinuxThreads的信号灯 注销函数不做其他动作。

2. 点灯和灭灯

int sem_post(sem_t * sem)

点灯操作将信号灯值原子地加1,表示增加一个可访问的资源。

int sem_wait(sem_t * sem)
int sem_trywait(sem_t * sem)

sem_wait()为等待灯亮操作,等待灯亮(信号灯值大于0),然后将信号灯原子地减1,并返回。sem_trywait()为sem_wait()的非阻塞版,如果信号灯计数大于0,则原子地减1并返回0,否则立即返回-1,errno置为EAGAIN。

3. 获取灯值

int sem_getvalue(sem_t * sem, int * sval)

读取sem中的灯计数,存于*sval中,并返回0。

4. 其他

sem_wait()被实现为取消点,而且在支持原子”比较且交换”指令的体系结构上,sem_post()是唯一能用于异步信号处理函数的POSIX异步信号 安全的API。

sem_wait()被实现为取消点。取消点是什么意思???)
sem_wait is a cancellation point.
取消点的含义:
当用pthread_cancel()一个线程时,这个要求会被pending起来,当被cancel的线程走到下一个cancellation point时,线程才会被真正cancel掉。

而且在支持原子”比较且交换CAS”指令的体系结构上,sem_post()是唯一能用于异步信号处理函数的POSIX异步信号 安全的API。

On processors supporting atomic compare-and-swap (Intel 486, Pentium and later, Alpha, PowerPC, MIPS  II,  Motorola  68k),
       the  sem_post function is async-signal safe and can therefore be called from signal handlers. This is the only thread syn-
       chronization function provided by POSIX threads that is async-signal safe.

       On the Intel 386 and the Sparc, the current LinuxThreads implementation of sem_post is not async-signal safe  by  lack  of
       the required atomic operations.



----------------------------
线程同步:何时互斥锁不够,还需要条件变量?

假设有共享的资源sum,与之相关联的mutex 是lock_s.假设每个线程对sum的操作很简单的,与sum的状态无关,比如只是sum++.那么只用mutex足够了.程序员只要确保每个线程操作前,取得lock,然后sum++,再unlock即可.每个线程的代码将像这样
add()
{
pthread_mutex_lock(lock_s);
sum++;
pthread_mutex_unlock(lock_s);
}

  如果操作比较复杂,假设线程t0,t1,t2的操作是sum++,而线程t3则是在sum到达100的时候,打印出一条信息,并对sum清零. 这种情况下,如果只用mutex, 则t3需要一个循环,每个循环里先取得lock_s,然后检查sum的状态,如果sum>=100,则打印并清零,然后unlock.如果sum& amp; lt;100,则unlock,并sleep()本线程合适的一段时间.

 这个时候,t0,t1,t2的代码不变,t3的代码如下
print()
{
while (1)
{
pthread_mutex_lock(lock_s);
if(sum<100)
{
printf(“sum reach 100!”);
pthread_mutex_unlock(lock_s);
}
else
{
pthread_mutex_unlock(lock_s);
my_thread_sleep(100);
return OK;
}
}
}

这种办法有两个问题
1) sum在大多数情况下不会到达100,那么对t3的代码来说,大多数情况下,走的是else分支,只是lock和unlock,然后sleep().这浪费了CPU处理时间.
2) 为了节省CPU处理时间,t3会在探测到sum没到达100的时候sleep()一段时间.这样却又带来另外一个问题,亦即t3响应速度下降.可能在sum到达200的时候,t3才会醒过来.
3) 这样,程序员在设置sleep()时间的时候陷入两难境地,设置得太短了节省不了资源,太长了又降低响应速度.真是难办啊!

  这个时候,condition variable内裤外穿,从天而降,拯救了焦头烂额的你.

  你首先定义一个condition variable.
pthread_cond_t cond_sum_ready=PTHREAD_COND_INITIALIZER;

  t0,t1,t2的代码只要后面加两行,像这样
add()
{
pthread_mutex_lock(lock_s);
sum++;
pthread_mutex_unlock(lock_s);
if(sum>=100)
pthread_cond_signal(&cond_sum_ready);
}
而t3的代码则是
print
{
pthread_mutex_lock(lock_s);
while(sum<100)
pthread_cond_wait(&cond_sum_ready, &lock_s);
printf(“sum is over 100!”);
sum=0;
pthread_mutex_unlock(lock_s);
return OK;
}

注意两点:
1) 在thread_cond_wait()之前,必须先lock相关联的mutex, 因为假如目标条件未满足,pthread_cond_wait()实际上会unlock该mutex, 然后block,在目标条件满足后再重新lock该mutex, 然后返回.
2) 为什么是while(sum<100),而不是if(sum<100) ?这是因为在pthread_cond_signal()和pthread_cond_wait()返回之间,有时间差,假设在这个时间差内,还有另外一个线程t4又把sum减少到100以下了,那么t3在pthread_cond_wait()返回之后,显然应该再检查一遍sum的大小.这就是用 while的用意
<think>好的,用户想了解互斥锁信号量区别。首先,我需要回忆一下操作系统和并发控制的相关知识。互斥锁(Mutex)和信号量(Semaphore)都是用来管理多线程或多进程之间的同步和资源访问的,但它们的用途和机制有所不同。 首先,互斥锁的主要作用是保证同一时间只有一个线程可以访问共享资源,防止数据竞争。它通常用于临界区的保护,确保互斥访问。而信号量则更灵活,可以用来控制多个线程对资源的访问,比如限制同时访问的线程数量。 接下来,我需要理清它们的核心区别互斥锁是二元状态的,只能锁定或解锁,而信号量可以有一个计数器,允许指定数量的线程同时访问。比如,信号量初始化为3的话,可以允许三个线程同时操作资源,而互斥锁一次只能一个。 然后,所有权的问题也很重要。互斥锁具有所有权概念,即哪个线程获取了锁,必须由同一个线程释放。这避免了其他线程误释放锁导致的问题。而信号量没有这种限制,任何线程都可以增减信号量的值。 另外,使用场景不同。互斥锁适合保护临界区,确保一次一个线程执行。信号量适合生产者-消费者问题,或者资源池的管理,比如数据库连接池,限制同时使用的连接数。 可能用户还会混淆这两者的用途,比如用信号量实现互斥锁,虽然技术上可行,但互斥锁更高效,因为它是专门为互斥设计的。同时,互斥锁可能带有优先级继承等高级特性,避免优先级反转问题,而信号量一般不具备这些。 需要注意的是,信号量分为计数信号量和二进制信号量,后者功能类似互斥锁,但依然有所有权的区别。例如,二进制信号量可能在释放时不需要由获取者来执行,而互斥锁必须由持有者释放,这可能导致不同的行为。 还需要检查是否有其他区别,比如系统支持情况。互斥锁通常作为线程同步原语,而信号量可能用于进程间同步,不过具体实现可能因系统而异。 最后,总结它们的区别时,要明确互斥锁用于互斥访问,信号量用于控制并发数量,以及所有权和使用场景的不同。确保回答结构清晰,分点说明,帮助用户逐步理解两者的区别。</think>互斥锁(Mutex)和信号量(Semaphore)是并发编程中用于资源同步的两种机制,它们的核心区别体现在设计目的、实现方式和应用场景上。以下是逐步分析: --- ### **1. 设计目的不同** - **互斥锁** 用于实现**互斥访问**,确保同一时刻仅有一个线程/进程能访问共享资源。 例如:线程A修改全局变量时,锁住资源;线程B必须等待A释放锁后才能操作。 - **信号量** 用于控制**资源访问的并发数量**,通过计数器管理多个线程/进程的访问权限。 例如:若信号量初始值为3,则允许最多3个线程同时访问资源。 --- ### **2. 操作机制差异** - **互斥锁** - 二元状态:只有锁定(`Lock`)和解锁(`Unlock`)两种状态。 - **所有权概念**:必须由获取锁的线程释放锁,其他线程无法直接释放。 $$ \text{操作流程:Lock() → 访问资源 → Unlock()} $$ - **信号量** - 计数器机制:通过`Wait()`(P操作)和`Signal()`(V操作)增减计数器。 - **无所有权限制**:任意线程均可调用`Signal()`释放资源。 $$ \text{初始值} N \geq 0, \quad \text{当} N=0 \text{时阻塞后续线程} $$ --- ### **3. 典型应用场景** - **互斥锁适用场景** - 保护临界区(Critical Section),如修改全局变量、写文件等需要严格互斥的操作。 - 示例代码: ```c pthread_mutex_lock(&mutex); // 加锁 // 临界区操作 pthread_mutex_unlock(&mutex); // 解锁 ``` - **信号量适用场景** - 控制资源池(如数据库连接池)、生产者-消费者问题(缓冲队列同步)。 - 示例代码: ```c sem_wait(&sem); // 计数器减1,若计数器为0则阻塞 // 访问资源 sem_post(&sem); // 计数器加1 ``` --- ### **4. 底层实现差异** - **互斥锁** 通常通过硬件支持的原子指令(如CAS,Compare-And-Swap)实现,确保锁操作的原子性。 - **信号量** 依赖操作系统内核的调度机制,`Wait()`和`Signal()`可能触发上下文切换。 --- ### **5. 关键区别总结** | **特性** | **互斥锁** | **信号量** | |----------------|--------------------------|-------------------------| | 核心功能 | 严格互斥 | 控制并发数量 | | 计数器范围 | 0或1(二元) | 任意非负整数(N ≥ 0) | | 所有权 | 必须由持有者释放 | 任意线程可释放 | | 性能开销 | 较低(用户态实现) | 较高(可能涉及内核切换) | | 典型问题防范 | 死锁、优先级反转 | 资源竞争、饥饿 | --- ### **6. 常见误区** - **信号量可以替代互斥锁吗?** 可以(通过初始值为1的信号量),但互斥锁更高效且能避免逻辑错误(如误释放)。 - **互斥锁能控制多线程并发数吗?** 不能。若需允许N个线程并发访问,必须使用信号量。 --- 通过理解上述区别,开发者可以更精准地选择同步机制:优先用互斥锁解决互斥问题,用信号量解决资源配额问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值