线程同步(互斥锁与信号量的作用与区别)

本文深入探讨了信号量和互斥锁在多线程编程中的作用,解释了它们的区别及如何在同步与互斥场景下使用。包括创建、注销、点灯和灭灯操作,以及获取灯值的概念。同时,对比了它们在不同操作系统中的函数实现,以及在实际应用中的具体使用场景。

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

两者之间的区别:

作用域
信号量: 进程间或线程间(linux仅线程间的无名信号量pthread semaphore)
互斥锁: 线程间

上锁时 
信号量: 只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减一。若value值不大于0,则sem_wait使得线程阻塞,直到sem_post释放后value值加一,但是sem_wait返回之前还是会将此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的信号灯 注销函数不做其他动作。
sem_destroy destroys a semaphore object, freeing the resources it  might  hold.  No  threads  should  be  waiting  on  the
       semaphore  at  the  time  sem_destroy  is  called.  In  the  LinuxThreads implementation, no resources are associated with
       semaphore objects, thus sem_destroy actually does nothing except checking that no thread is waiting on the semaphore.


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_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.

互斥量(Mutex)

 

互斥量表现互斥现象的数据结构,也被当作二元信号灯。一个互斥基本上是一个多任务敏感的二元信号,它能用作同步多任务的行为,它常用作保护从中断来的临界段代码并且在共享同步使用的资源。

clip_image001

 

Mutex本质上说就是一把锁,提供对资源的独占访问,所以Mutex主要的作用是用于互斥。Mutex对象的值,只有0和1两个值。这两个值也分别代表了Mutex的两种状态。值为0, 表示锁定状态,当前对象被锁定,用户进程/线程如果试图Lock临界资源,则进入排队等待;值为1,表示空闲状态,当前对象为空闲,用户进程/线程可以Lock临界资源,之后Mutex值减1变为0。

Mutex可以被抽象为四个操作:

- 创建 Create

- 加锁 Lock

- 解锁 Unlock

- 销毁 Destroy

Mutex被创建时可以有初始值,表示Mutex被创建后,是锁定状态还是空闲状态。在同一个线程中,为了防止死锁,系统不允许连续两次对Mutex加锁(系统一般会在第二次调用立刻返回)。也就是说,加锁和解锁这两个对应的操作,需要在同一个线程中完成。

不同操作系统中提供的Mutex函数:

动作\系统

Win32

Linyx

Solaris

创建

CreateMutex

pthread_mutex_init

mutex_init

加锁

WaitForSingleObject

pthread_mutex_lock

mutex_lock

解锁

ReleaseMutex

pthread_mutex_unlock

mutex_unlock

销毁

CloseHandle

pthread_mutex_destroy

mutex_destroy

 

信号量

信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施, 它负责协调各个线程, 以保证它们能够正确、合理的使用公共资源。

信号量可以分为几类:

² 二进制信号量(binary semaphore):只允许信号量取0或1值,其同时只能被一个线程获取。

² 整型信号量(integer semaphore):信号量取值是整数,它可以被多个线程同时获得,直到信号量的值变为0。

² 记录型信号量(record semaphore):每个信号量s除一个整数值value(计数)外,还有一个等待队列List,其中是阻塞在该信号量的各个线程的标识。当信号量被释放一个,值被加一后,系统自动从等待队列中唤醒一个等待中的线程,让其获得信号量,同时信号量再减一。

信号量通过一个计数器控制对共享资源的访问,信号量的值是一个非负整数,所有通过它的线程都会将该整数减一。如果计数器大于0,则访问被允许,计数器减1;如果为0,则访问被禁止,所有试图通过它的线程都将处于等待状态。

计数器计算的结果是允许访问共享资源的通行证。因此,为了访问共享资源,线程必须从信号量得到通行证, 如果该信号量的计数大于0,则此线程获得一个通行证,这将导致信号量的计数递减,否则,此线程将阻塞直到获得一个通行证为止。当此线程不再需要访问共享资源时,它释放该通行证,这导致信号量的计数递增,如果另一个线程等待通行证,则那个线程将在那时获得通行证。

 

Semaphore可以被抽象为五个操作:

- 创建 Create

- 等待 Wait:

线程等待信号量,如果值大于0,则获得,值减一;如果只等于0,则一直线程进入睡眠状态,知道信号量值大于0或者超时。

-释放 Post

执行释放信号量,则值加一;如果此时有正在等待的线程,则唤醒该线程。

-试图等待 TryWait

如果调用TryWait,线程并不真正的去获得信号量,还是检查信号量是否能够被获得,如果信号量值大于0,则TryWait返回成功;否则返回失败。

-销毁 Destroy

信号量,是可以用来保护两个或多个关键代码段,这些关键代码段不能并发调用。在进入一个关键代码段之前,线程必须获取一个信号量。如果关键代码段中没有任何线程,那么线程会立即进入该框图中的那个部分。一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。为了完成这个过程,需要创建一个信号量,然后将Acquire Semaphore VI以及Release Semaphore VI分别放置在每个关键代码段的首末端。确认这些信号量VI引用的是初始创建的信号量。

动作\系统

Win32

POSIX

创建

CreateSemaphore

sem_init

等待

WaitForSingleObject

sem _wait

释放

ReleaseMutex

sem _post

试图等待

WaitForSingleObject

sem _trywait

销毁

CloseHandle

sem_destroy

互斥量和信号量的区别

1. 互斥量用于线程的互斥,信号量用于线程的同步。

这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源

2. 互斥量值只能为0/1,信号量值可以为非负整数。

也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。

3. 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。


<think>首先,用户的问题是:“线程同步互斥锁信号量的本质区别是什么?” 这是基于之前的上下文,我在之前的回答中已经覆盖了这个主题。 回顾我的上一个回答:我详细解释了互斥锁信号量的核心概念、本质区别、代码行为对比、应用场景和底层实现差异。然后,我提出了相关问题。 现在,用户再次问同样的问题:“线程同步互斥锁信号量的本质区别是什么?” 这可能是测试一致性或希望一个更精炼的回答。我需要确保我的回答符合规则:使用中文回答问题,并在末尾提出3-5个相关问题,以“§§相关问题§§”开头,禁止换行分散。 规则重申: - 回答完问题后,必须提出3-5个 §§相关问题。 - 第一行必须完整输出 "§§相关问题§§"。 - 禁止换行分散,意思是相关问题部分应该紧凑。 由于用户的问题重复,我可以参考之前的回答,但要确保内容原创且适应这个查询。上一次的回答已经很全面,但这次可以更聚焦于本质区别。 结构化我的回答: 1. **核心区别总结**:快速概述互斥锁信号量的基本差异。 2. **详细对比**:包括关键点如所有权、资源管理、状态等。 3. **示例代码**:提供Python代码片段以展示用法差异。 4. **场景应用**:简要说明适用场景。 5. **相关问题**:提出3-5个相关问题。 相关问题必须基于用户的问题和我的回答。从上一个回答中,我有一些,但需要调整以确保它们相关。 基于当前查询,可能的§§相关问题: 1. 为什么互斥锁要求同一线程解锁? 2. 信号量如何用于控制多个资源? 3. 在实际编程中,何时选择互斥锁而不是信号量? 4. 互斥锁信号量在死锁预防中的作用有何不同? 5. 信号量的计数器机制如何影响性能? 确保3-5个问题。 最终回答结构: - 先回答问题部分。 - 然后"§§相关问题§§"行。 - 接着问题列表,用数字点。 用中文写所有内容。 草拟回答: - 本质区别互斥锁是二元锁,用于独占访问共享资源,只允许一个线程进入临界区;信号量是计数器,用于控制对多个资源的并发访问。 - 关键点:所有权(互斥锁必须由获得锁的线程释放,信号量可以由任何线程释放)、状态(0/1 vs 整数计数)、目的(保护单个资源 vs 管理资源池)。 - 代码示例:展示互斥锁信号量的使用。 - 应用:互斥锁适合账户更新,信号量适合连接池。 然后相关问题。 完整输出。 </think> ### 互斥锁信号量的本质区别解析 #### 一、核心定义对比 - **互斥锁(Mutex)**: 是一种二元同步机制(状态为“锁定”或“解锁”),确保**同一时间只有一个线程**能访问共享资源。线程获取锁后,必须由**同一线程释放**,用于保护临界区(如修改变量)。 - **信号量Semaphore)**: 是一种计数器同步机制(状态为非负整数),控制**指定数量的线程**并发访问资源池。线程通过P(获取)和V(释放)操作管理资源,**任意线程可释放信号量**。 #### 二、本质区别详解 | 维度 | 互斥锁 (Mutex) | 信号量 (Semaphore) | |--------------------|----------------------------------|---------------------------------| | **资源模型** | 保护单个资源(钥匙模型:有/无钥匙) | 管理资源池(车位模型:可用资源数) | | **状态机制** | 二元状态(0或1) | 计数器状态(≥0的整数) | | **所有权要求** | 必须由获取锁的线程释放(强绑定) | 任何线程都可执行V操作释放(无绑定) | | **设计目的** | 实现资源独占访问 | 控制并发线程数量 | | **适用场景** | 如银行账户余额更新 | 如数据库连接池管理 | #### 三、代码示例对比 **互斥锁(Python)**: ```python import threading lock = threading.Lock() lock.acquire() # 获取锁(独占) try: # 临界区:仅1个线程执行 shared_data = "modified" finally: lock.release() # 必须同一线程释放 ``` **信号量(Python)**: ```python sem = threading.Semaphore(3) # 允许3个线程并发 sem.acquire() # 获取资源 try: # 资源区:最多3个线程同时访问 print("Accessing shared pool") finally: sem.release() # 任意线程可释放 ``` #### 四、关键行为差异 - **互斥锁**:未获锁时线程完全阻塞,等待唤醒;适用于严格互斥场景(如文件写入)。 - **信号量**:资源不足时线程排队等待;适用于限制并发度(如API请求限流)。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值