What is the difference between lock, mutex and semaphore?
I've heard these words related to concurrent programming, but wh开发者_如何转开发at's the difference between lock, mutex and semaphore?
A lock allows only one thread to enter the part that's locked and the lock is not shared with any other processes.
A mutex is the same as a lock but it can be system wide (shared by multiple processes).
A semaphore does the same as a mutex but allows x number of threads to enter, this can be used for example to limit the number of cpu, io or ram intensive tasks running at the same time.
For a more detailed post about the differences between mutex and semaphore read here.
You also have read/write locks that allows either unlimited number of readers or 1 writer at any given time.
There are a lot of misconceptions regarding these words.
This is from a previous post (https://stackoverflow.com/a/24582076/3163691) which fits superb here:
1) Critical Section= User object used for allowing the execution of just one active thread from many others within one process. The other non selected threads (@ acquiring this object) are put to sleep.
[No interprocess capability, very primitive object].
2) Mutex Semaphore (aka Mutex)= Kernel object used for allowing the execution of just one active thread from many others, among different processes. The other non selected threads (@ acquiring this object) are put to sleep. This object supports thread ownership, thread termination notification, recursion (multiple 'acquire' calls from same thread) and 'priority inversion avoidance'.
[Interprocess capability, very safe to use, a kind of 'high level' synchronization object].
3) Counting Semaphore (aka Semaphore)= Kernel object used for allowing the execution of a group of active threads from many others. The other non selected threads (@ acquiring this object) are put to sleep.
[Interprocess capability however not very safe to use because it lacks following 'mutex' attributes: thread termination notification, recursion?, 'priority inversion avoidance'?, etc].
4) And now, talking about 'spinlocks', first some definitions:
Critical Region= A region of memory shared by 2 or more processes.
Lock= A variable whose value allows or denies the entrance to a 'critical region'. (It could be implemented as a simple 'boolean flag').
Busy waiting= Continuosly testing of a variable until some value appears.
Finally:
Spin-lock (aka Spinlock)= A lock which uses busy waiting. (The acquiring of the lock is made by xchg or similar atomic operations).
[No thread sleeping, mostly used at kernel level only. Ineffcient for User level code].
As a last comment, I am not sure but I can bet you some big bucks that the above first 3 synchronizing objects (#1, #2 and #3) make use of this simple beast (#4) as part of their implementation.
Have a good day!.
References:
-Real-Time Concepts for Embedded Systems by Qing Li with Caroline Yao (CMP Books).
-Modern Operating Systems (3rd) by Andrew Tanenbaum (Pearson Education International).
-Programming Applications for Microsoft Windows (4th) by Jeffrey Richter (Microsoft Programming Series).
Also, you can take a look at look at: https://stackoverflow.com/a/24586803/3163691
Most problems can be solved using (i) just locks, (ii) just semaphores, ..., or (iii) a combination of both! As you may have discovered, they're very similar: both prevent race conditions, both have acquire()
/release()
operations, both cause zero or more threads to become blocked/suspected...
Really, the crucial difference lies solely on how they lock and unlock.
- 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).
For both locks/semaphores, trying to call acquire()
while the primitive is in state 0 causes the invoking thread to be suspended. For locks - attempts to acquire the lock is in state 1 are successful. For semaphores - attempts to acquire the lock in states {1, 2, 3, ...} are successful.
For locks in state state 0, if same thread that had previously called acquire()
, now calls release, then the release is successful. If a different thread tried this -- it is down to the implementation/library as to what happens (usually the attempt ignored or an error is thrown). For semaphores in state 0, any thread can call release and it will be successful (regardless of which thread previous used acquire to put the semaphore in state 0).
From the preceding discussion, we can see that locks have a notion of an owner (the sole thread that can call release is the owner), whereas semaphores do not have an owner (any thread can call release on a semaphore).
What causes a lot of confusion is that, in practice they are many variations of this high-level definition.
Important variations to consider:
- What should the
acquire()
/release()
be called? -- [Varies massively] - Does your lock/semaphore use a "queue" or a "set" to remember the threads waiting?
- Can your lock/semaphore be shared with threads of other processes?
- Is your lock "reentrant"? -- [Usually yes].
- Is your lock "blocking/non-blocking"? -- [Normally non-blocking are used as blocking locks (aka spin-locks) cause busy waiting].
- How do you ensure the operations are "atomic"?
These depends on your book / lecturer / language / library / environment.
Here's a quick tour noting how some languages answer these details.
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 tosem_init
.
python (threading.py)
- A lock (
threading.RLock
) is mostly the same as C/C++pthread_mutex_t
s. Both are both reentrant. This means they may only be unlocked by the same thread that locked it. It is the case thatsem_t
semaphores,threading.Semaphore
semaphores andtheading.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 assem_t
. Although withsem_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, withthreading.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'sthreading.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, anIllegalMonitorStateException
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 assem_t
andthreading.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.
In theory, semaphores are often discussed, but in practice, semaphores aren't used so much. A semaphore only hold the state of one integer, so often it's rather inflexible and many are needed at once -- causing difficulty in understanding code. Also, the fact that any thread can release a semaphore is sometimes undesired. More object-oriented / higher-level synchronization primitives / abstractions such as "condition variables" and "monitors" are used instead.
Take a look at Multithreading Tutorial by John Kopplin.
In the section Synchronization Between Threads, he explain the differences among event, lock, mutex, semaphore, waitable timer
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 whereasWaitForSingleObject()
, which is used with a mutex, allows you to specify a timeoutA semaphore maintains a count between zero and some maximum value, limiting the number of threads that are simultaneously accessing a shared resource.
I will try to cover it with examples:
Lock: One example where you would use lock
would be a shared dictionary into which items (that must have unique keys) are added.
The lock would ensure that one thread does not enter the mechanism of code that is checking for item being in dictionary while another thread (that is in the critical section) already has passed this check and is adding the item. If another thread tries to enter a locked code, it will wait (be blocked) until the object is released.
private static readonly Object obj = new Object();
lock (obj) //after object is locked no thread can come in and insert item into dictionary on a different thread right before other thread passed the check...
{
if (!sharedDict.ContainsKey(key))
{
sharedDict.Add(item);
}
}
Semaphore: Let's say you have a pool of connections, then an single thread might reserve one element in the pool by waiting for the semaphore to get a connection. It then uses the connection and when work is done releases the connection by releasing the semaphore.
Code example that I love is one of bouncer given by @Patric - here it goes:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace TheNightclub
{
public class Program
{
public static Semaphore Bouncer { get; set; }
public static void Main(string[] args)
{
// Create the semaphore with 3 slots, where 3 are available.
Bouncer = new Semaphore(3, 3);
// Open the nightclub.
OpenNightclub();
}
public static void OpenNightclub()
{
for (int i = 1; i <= 50; i++)
{
// Let each guest enter on an own thread.
Thread thread = new Thread(new ParameterizedThreadStart(Guest));
thread.Start(i);
}
}
public static void Guest(object args)
{
// Wait to enter the nightclub (a semaphore to be released).
Console.WriteLine("Guest {0} is waiting to entering nightclub.", args);
Bouncer.WaitOne();
// Do some dancing.
Console.WriteLine("Guest {0} is doing some dancing.", args);
Thread.Sleep(500);
// Let one guest out (release one semaphore).
Console.WriteLine("Guest {0} is leaving the nightclub.", args);
Bouncer.Release(1);
}
}
}
Mutex It is pretty much Semaphore(1,1)
and often used globally (application wide otherwise arguably lock
is more appropriate). One would use global Mutex
when deleting node from a globally accessible list (last thing you want another thread to do something while you are deleting the node). When you acquire Mutex
if different thread tries to acquire the same Mutex
it will be put to sleep till SAME thread that acquired the Mutex
releases it.
Good example on creating global mutex is by @deepee
class SingleGlobalInstance : IDisposable
{
public bool hasHandle = false;
Mutex mutex;
private void InitMutex()
{
string appGuid = ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value.ToString();
string mutexId = string.Format("Global\\{{{0}}}", appGuid);
mutex = new Mutex(false, mutexId);
var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
var securitySettings = new MutexSecurity();
securitySettings.AddAccessRule(allowEveryoneRule);
mutex.SetAccessControl(securitySettings);
}
public SingleGlobalInstance(int timeOut)
{
InitMutex();
try
{
if(timeOut < 0)
hasHandle = mutex.WaitOne(Timeout.Infinite, false);
else
hasHandle = mutex.WaitOne(timeOut, false);
if (hasHandle == false)
throw new TimeoutException("Timeout waiting for exclusive access on SingleInstance");
}
catch (AbandonedMutexException)
{
hasHandle = true;
}
}
public void Dispose()
{
if (mutex != null)
{
if (hasHandle)
mutex.ReleaseMutex();
mutex.Dispose();
}
}
}
then use like:
using (new SingleGlobalInstance(1000)) //1000ms timeout on global lock
{
//Only 1 of these runs at a time
GlobalNodeList.Remove(node)
}
Hope this saves you some time.
Wikipedia has a great section on the differences between Semaphores and Mutexes:
A mutex is essentially the same thing as a binary semaphore and sometimes uses the same basic implementation. The differences between them are:
Mutexes have a concept of an owner, which is the process that locked the mutex. Only the process that locked the mutex can unlock it. In contrast, a semaphore has no concept of an owner. Any process can unlock a semaphore.
Unlike semaphores, mutexes provide priority inversion safety. Since the mutex knows its current owner, it is possible to promote the priority of the owner whenever a higher-priority task starts waiting on the mutex.
Mutexes also provide deletion safety, where the process holding the mutex cannot be accidentally deleted. Semaphores do not provide this.
My understanding is that a mutex is only for use within a single process, but across its many threads, whereas a semaphore may be used across multiple processes, and across their corresponding sets of threads.
Also, a mutex is binary (it's either locked or unlocked), whereas a semaphore has a notion of counting, or a queue of more than one lock and unlock requests.
Could someone verify my explanation? I'm speaking in the context of Linux, specifically Red Hat Enterprise Linux (RHEL) version 6, which uses kernel 2.6.32.
Using C programming on a Linux variant as a base case for examples.
Lock:
• Usually a very simple construct binary in operation either locked or unlocked
• No concept of thread ownership, priority, sequencing etc.
• Usually a spin lock where the thread continuously checks for the locks availability.
• Usually relies on atomic operations e.g. Test-and-set, compare-and-swap, fetch-and-add etc.
• Usually requires hardware support for atomic operation.
File Locks:
• Usually used to coordinate access to a file via multiple processes.
• Multiple processes can hold the read lock however when any single process holds the write lock no other process is allowed to acquire a read or write lock.
• Example : flock, fcntl etc..
Mutex:
• Mutex function calls usually work in kernel space and result in system calls.
• It uses the concept of ownership. Only the thread that currently holds the mutex can unlock it.
• Mutex is not recursive (Exception: PTHREAD_MUTEX_RECURSIVE).
• Usually used in Association with Condition Variables and passed as arguments to e.g. pthread_cond_signal, pthread_cond_wait etc.
• Some UNIX systems allow mutex to be used by multiple processes although this may not be enforced on all systems.
Semaphore:
• This is a kernel maintained integer whose values is not allowed to fall below zero.
• It can be used to synchronize processes.
• The value of the semaphore may be set to a value greater than 1 in which case the value usually indicates the number of resources available.
• A semaphore whose value is restricted to 1 and 0 is referred to as a binary semaphore.
lock, mutex, semaphore
It is a general vision. Details are depended on real language realisation
lock
- thread synchronization tool. When thread get a lock it becomes a single thread which is able to execute a block of code. All others thread are blocked. Only thread which owns the lock can unlock it
mutex
- mutual exclusion lock. It is a kind of lock. On some languages it is inter-process mechanism, on some languages it is a synonym of lock
. For example Java uses lock
in synchronised
and java.util.concurrent.locks.Lock
semaphore
- allows a number of threads to access a shared resource. You can find that mutex
also can be implemented by semaphore
. It is a standalone object which manage an access to shared resource. You can find that any thread can signal
and unblock. Also it is used for signalling
[iOS lock, mutex, semaphore]
Supporting ownership
, maximum number of processes share lock
and the maximum number of allowed processes/threads in critical section
are three major factors that determine the name/type of the concurrent object with general name of lock
. Since the value of these factors are binary (have two states), we can summarize them in a 3*8 truth-like table.
- X (Supports Ownership?): no(0) / yes(1)
- Y (#sharing processes): > 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
Feel free to edit or expand this table, I've posted it as an ascii table to be editable:)
精彩评论