互斥锁和信号量

时间:2023-01-21 15:18:22

Mutex是一把钥匙,一个人拿了就可进入一个房间,出来的时候把钥匙交给队列的第一个。一般的用法是用于串行化对critical section代码的访问,保证这段代码不会被并行的运行。

Is a key to a toilet. One person can have the key - occupy the toilet - at the time. When finished, the person gives (frees) the key to the next person in the queue.

Officially: "Mutexes are typically used to serialise access to a section of re-entrant code that cannot be executed concurrently by more than one thread. A mutex object only allows one thread into a controlled section, forcing other threads which attempt to gain access to that section to wait until the first thread has exited from that section."

Semaphore是一件可以容纳N人的房间,如果人不满就可以进去,如果人满了,就要等待有人出来。对于N=1的情况,称为binary semaphore。一般的用法是,用于限制对于某一资源的同时访问。

Semaphore:

Is the number of free identical toilet keys. Example, say we have four toilets with identical locks and keys. The semaphore count - the count of keys - is set to 4 at beginning (all four toilets are free), then the count value is decremented as people are coming in. If all toilets are full, ie. there are no free keys left, the semaphore count is 0. Now, when eq. one person leaves the toilet, semaphore is increased to 1 (one free key), and given to the next person in the queue.

Officially: "A semaphore restricts the number of simultaneous users of a shared resource up to a maximum number. Threads can request access to the resource (decrementing the semaphore), and can signal that they have finished using the resource (incrementing the semaphore)." Ref: Symbian Developer Library

信号量限制一份资源的最大同步个数。线程可以请求进入资源,并在用完资源时发出信号.

对于Binary semaphore与Mutex,这两者之间就存在了很多相似之处:
在有的系统中Binary semaphore与Mutex是没有差异的。在有的系统上,主要的差异是mutex一定要由获得锁的进程来释放。而semaphore可以由其它进程释放(这时的semaphore实际就是个原子的变量,大家可以加或减),因此semaphore可以用于进程间同步。Semaphore的同步功能是所有系统都支持的,而Mutex能否由其他进程释放则未定,因此建议mutex只用于保护critical section。而semaphore则用于保护某变量,或者同步。

1、Mutex 互斥量/互斥锁

Mutex本质上说就是一把锁,提供对资源的独占访问,所以Mutex的主要作用是用于互斥的访问共享资源

Mutex对象的值,只有0和1两个值。这两个值也分别代表了Mutex的两种状态。

值为0,表示锁定状态,当前对象被锁定,用户进程/线程如果试图Lock临界资源,则进入排队等待。

值为1,表示空闲状态,当前对象为空闲,用户进程/线程可以Lock临界资源,之后Mutex值减1变为0.

Mutex可以被抽象为四个操作:创建 Create;加锁 Lock;解锁 Unlock;销毁 Destroy。

Mutex被创建时可以有初始值,表示Mutex被创建后,是锁定状态还是空闲状态。

在同一个线程中,为了防止死锁,系统不允许连续两次对Mutex加锁。就是加锁和解锁这两个对应的操作,需要在同一个线程中完成

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

动作/系统 Windows Linux 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

避免互斥锁死锁的几个基本原则:

-- 对共享资源操作前一定要获得锁。

-- 完成操作以后一定要释放锁。

-- 尽量短时间的占用锁,即占用锁的时间不要过长。

-- 如果有多锁,如获得顺序是ABC连环扣,释放顺序也应该是ABC。

-- 线程错误返回时应该释放它所获得的锁。

>>“挂起等待”和“唤醒等待线程”的操作?

每个Mutex有一个等待队列,一个线程要在Mutex上挂起等待,首先把自己加入等待队列中,然后置线程状态为睡眠,然后调用调度器函数切换到别的线程。一个线程要唤醒等待队列中的其它线程,只需从等待队列中取出一项,把它的状态从睡眠改为就绪,加入就绪队列,那么下次调度器函数执行时就有可能切换到被唤醒的线程。

一般情况下,如果同一个线程先后两次调用lock,在第二次调用时,由于锁已经被占用,该线程会挂起等待别的线程释放锁,然而锁正是被自己占用着的,该线程又被挂起而没有机会释放锁,因此就永远处于挂起等待状态了,这叫做死锁(Deadlock)。

另一种典型的死锁情形是这样:线程A获得了锁1,线程B获得了锁2,这时线程A调用lock试图获得锁2,结果是需要挂起等待线程B释放锁2,而这时线程B也调用lock试图获得锁1,结果是需要挂起等待线程A释放锁1,于是线程A和线程B都永远处于挂起状态了。如果涉及更多的线程和更多的锁,则死锁的可能性更大。(例子,上厕所,要锁厕所门)

2、信号量 semaphore

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

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

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

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

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

计数器计算的结果是允许访问共享资源的通行证。因此,为了访问共享资源,线程必须从信号量得到通行证,如果该信号量的计数大于0,则此线程获得一个通行证,这将导致信号量的计数递减,否则,此线程将阻塞直到获得一个通行证为止。

当此线程不再需要访问共享资源时,它释放该通行证,这导致信号量的计数递增,如果另一个线程等待通行证,则那个线程将在那时获得通行证。

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

>> 创建 Create

>> 等待 Wait,线程等待信号量,如果值>0,则获得,值减1;如果<=0,则线程进入睡眠状态,直到信号量值>0或超时。

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

>> 试图等待 TryWait,线程并不真正的去获得信号量,而是检查信号量是否能够被获得,如果信号量值大于0,则TryWait返回成功,否则返回失败。

>> 销毁 Destroy

信号量,是可以用来保护两个或多个关键代码段,这些关键代码段不能并发调用。在进入一个关键代码段之前,线程必须获取一个信号量。首先要创建一个信号量,然后将Acquire Semaphore 以及Release Semaphore分别放置在每个关键代码段的首末段。

POSIX Semaphore(Named Semaphores / Unnamed Semaphores)

Named Semaphores:this type of semaphore has a name. By calling sem_open() with the same name, unrelated process can access the same semaphore.

To work with a named semaphore, we employ the following functions:

-- The sem_open() function opens or create a semaphore, initializes the semaphore if it is created by the call, and returns a handle for use in later calls.

-- The sem_post(sem) and sem_wait(sem) functions respectively increment and decrement a semaphore's value.

-- The sem_getvalue() function retrieves a semaphore's current value.

-- The sem_close() function removes the calling process's association with a semaphore that it previously opened.

-- The sem_unlink() function removes a semaphore name and marks the semaphore for deletion when all processes have closed it.

3)互斥量和信号量的区别:

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

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

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

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

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

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

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