我听说过这些与并发编程有关的词,但是锁、互斥量和信号量之间有什么区别呢?


当前回答

锁,互斥,信号量

这是一个普遍的愿景。细节取决于真正的语言实现

锁线程同步工具。当线程获得锁时,它就变成了一个能够执行代码块的单一线程。所有其他线程都被阻塞。只有拥有锁的线程才能解锁

互斥锁——互斥锁。这是一种锁。在某些语言中它是进程间机制,在某些语言中它是锁的同义词。例如,Java在synchronized和Java .util.concurrent.locks. lock中使用锁

信号量——允许多个线程访问共享资源。你可以发现互斥也可以通过信号量来实现。它是一个独立的对象,用于管理对共享资源的访问。您可以发现任何线程都可以发出信号并解除阻塞。它也被用于信号

[iOS锁,互斥量,信号量]

其他回答

锁只允许一个线程进入被锁的部分,并且锁不与任何其他进程共享。

互斥锁与锁相同,但它可以是系统范围的(由多个进程共享)。

信号量的作用与互斥量相同,但允许x个线程进入,这可以用于限制同时运行的cpu、io或ram密集型任务的数量。

关于互斥量和信号量区别的更详细的文章请阅读这里。

您还可以使用读/写锁,在任何给定时间允许无限数量的读取器或1个写入器。

这些描述是从。net的角度出发的,对于所有操作系统/语言可能不是100%准确。

大多数问题都可以用(i)锁,(ii)信号量来解决……,或(iii)两者的组合!正如您可能已经发现的,它们非常相似:都防止竞争条件,都有acquire()/release()操作,都导致零或多个线程被阻塞/怀疑…… 实际上,关键的区别仅仅在于它们如何锁定和解锁。

A lock (or mutex) has two states (0 or 1). It can be either unlocked or locked. They're often used to ensure only one thread enters a critical section at a time. A semaphore has many states (0, 1, 2, ...). It can be locked (state 0) or unlocked (states 1, 2, 3, ...). One or more semaphores are often used together to ensure that only one thread enters a critical section precisely when the number of units of some resource has/hasn't reached a particular value (either via counting down to that value or counting up to that value).

对于这两个锁/信号量,在原语处于状态0时试图调用acquire()会导致调用线程挂起。对于锁——获取状态为1的锁的尝试是成功的。对于信号量——尝试获取状态{1,2,3,…是成功的。

对于状态为0的锁,如果之前调用acquire()的同一个线程现在调用release,则释放成功。如果一个不同的线程尝试了这个操作,那么将由实现/库决定发生什么(通常是忽略尝试或抛出错误)。对于处于状态0的信号量,任何线程都可以调用release,并且它将成功(不管之前哪个线程使用acquire将信号量置于状态0)。

从前面的讨论中,我们可以看到锁有一个所有者的概念(唯一可以调用释放的线程是所有者),而信号量没有所有者(任何线程都可以在信号量上调用释放)。


造成很多困惑的是,在实践中,它们是这个高级定义的许多变体。

需要考虑的重要变化:

acquire()/release()应该被称为什么?—[变化很大] 您的锁/信号量是否使用“队列”或“集”来记住等待的线程? 你的锁/信号量可以与其他进程的线程共享吗? 你的锁是“可重入的”吗?——[通常是的]。 你的锁是“阻塞/非阻塞”吗?-[通常非阻塞锁被用作阻塞锁(又名自旋锁),导致繁忙等待]。 如何确保操作是“原子的”?

这取决于你的书/讲师/语言/图书馆/环境。 下面是一些语言是如何回答这些细节的。


C, C+ (pthreads)

A mutex is implemented via pthread_mutex_t. By default, they can't be shared with any other processes (PTHREAD_PROCESS_PRIVATE), however mutex's have an attribute called pshared. When set, so the mutex is shared between processes (PTHREAD_PROCESS_SHARED). A lock is the same thing as a mutex. A semaphore is implemented via sem_t. Similar to mutexes, semaphores can be shared between threasds of many processes or kept private to the threads of one single process. This depends on the pshared argument provided to sem_init.

python (threading.py)

A lock (threading.RLock) is mostly the same as C/C++ pthread_mutex_ts. Both are both reentrant. This means they may only be unlocked by the same thread that locked it. It is the case that sem_t semaphores, threading.Semaphore semaphores and theading.Lock locks are not reentrant -- for it is the case any thread can perform unlock the lock / down the semaphore. A mutex is the same as a lock (the term is not used often in python). A semaphore (threading.Semaphore) is mostly the same as sem_t. Although with sem_t, a queue of thread ids is used to remember the order in which threads became blocked when attempting to lock it while it is locked. When a thread unlocks a semaphore, the first thread in the queue (if there is one) is chosen to be the new owner. The thread identifier is taken off the queue and the semaphore becomes locked again. However, with threading.Semaphore, a set is used instead of a queue, so the order in which threads became blocked is not stored -- any thread in the set may be chosen to be the next owner.

Java (Java . util . concurrent)

A lock (java.util.concurrent.ReentrantLock) is mostly the same as C/C++ pthread_mutex_t's, and Python's threading.RLock in that it also implements a reentrant lock. Sharing locks between processes is harder in Java because of the JVM acting as an intermediary. If a thread tries to unlock a lock it doesn't own, an IllegalMonitorStateException is thrown. A mutex is the same as a lock (the term is not used often in Java). A semaphore (java.util.concurrent.Semaphore) is mostly the same as sem_t and threading.Semaphore. The constructor for Java semaphores accept a fairness boolean parameter that control whether to use a set (false) or a queue (true) for storing the waiting threads.


理论上,信号量经常被讨论,但在实践中,信号量的使用并不多。一个信号量只保存一个整数的状态,所以它通常是相当不灵活的,并且需要同时使用许多个,这导致理解代码的困难。此外,任何线程都可以释放信号量的事实有时是不希望看到的。取而代之的是更多面向对象/高级同步原语/抽象,如“条件变量”和“监视器”。

锁,互斥,信号量

这是一个普遍的愿景。细节取决于真正的语言实现

锁线程同步工具。当线程获得锁时,它就变成了一个能够执行代码块的单一线程。所有其他线程都被阻塞。只有拥有锁的线程才能解锁

互斥锁——互斥锁。这是一种锁。在某些语言中它是进程间机制,在某些语言中它是锁的同义词。例如,Java在synchronized和Java .util.concurrent.locks. lock中使用锁

信号量——允许多个线程访问共享资源。你可以发现互斥也可以通过信号量来实现。它是一个独立的对象,用于管理对共享资源的访问。您可以发现任何线程都可以发出信号并解除阻塞。它也被用于信号

[iOS锁,互斥量,信号量]

看一看John Kopplin的多线程教程。

在线程间同步一节中,他解释了事件、锁、互斥量、信号量和可等待计时器之间的区别

A mutex can be owned by only one thread at a time, enabling threads to coordinate mutually exclusive access to a shared resource Critical section objects provide synchronization similar to that provided by mutex objects, except that critical section objects can be used only by the threads of a single process Another difference between a mutex and a critical section is that if the critical section object is currently owned by another thread, EnterCriticalSection() waits indefinitely for ownership whereas WaitForSingleObject(), which is used with a mutex, allows you to specify a timeout A semaphore maintains a count between zero and some maximum value, limiting the number of threads that are simultaneously accessing a shared resource.

支持所有权、最大进程共享锁数和临界区允许的最大进程/线程数是决定具有通用锁名的并发对象的名称/类型的三个主要因素。由于这些因素的值是二进制的(有两种状态),我们可以将它们总结为一个3*8的类真值表。

X(支持所有权?):no(0) / yes(1) Y(#共享进程):> 1(∞)/ 1 Z (#processes/threads in CA): > 1(∞)/ 1


  X   Y   Z          Name
 --- --- --- ------------------------
  0   ∞   ∞   Semaphore              
  0   ∞   1   Binary Semaphore       
  0   1   ∞   SemaphoreSlim          
  0   1   1   Binary SemaphoreSlim(?)
  1   ∞   ∞   Recursive-Mutex(?)     
  1   ∞   1   Mutex                  
  1   1   ∞   N/A(?)                 
  1   1   1   Lock/Monitor           

请随意编辑或展开这个表,我已经把它作为一个ascii表进行编辑:)