锁,互斥体,信号量…有什么区别?

我听说过这些与并发编程相关的词,但它们之间有什么不同呢?

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

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

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

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

对这些词有很多误解。

这是从以前的post( https://stackoverflow.com/a/24582076/3163691 ),这在这里非常适合:

1)关键部分 =用户对象,用于允许在一个进程中执行来自许多其他人的一个活动线程 。 其他未选定的线程进入睡眠状态

[没有进程间能力,非常原始的对象]。

2)互斥量信号量(又称互斥量) =内核对象,用于允许在不同的进程中执行来自许多其他活动线程的一个活动线程 。 其他未选定的线程进入睡眠状态 。 此对象支持线程所有权,线程终止通知,recursion(多个来自同一线程的“获取”调用)和“优先级反转回避”。

[进程间能力,使用非常安全,是一种'高层次'的同步对象]。

3)计算信号量(又名信号量) =内核对象,用于允许从其他许多执行一组活动线程 。 其他未选定的线程进入睡眠状态

[Interprocess能力不是很安全的使用,因为它缺less以下'互斥'属性:线程终止通知,recursion?,'优先倒置',等等]。

4)现在,谈到“自旋锁”,首先是一些定义:

关键区域=由2个或更多进程共享的内存区域。

Lock =一个variables,其值允许或拒绝“关键区域”的入口。 (它可以被实现为一个简单的“布尔标志”)。

忙于等待=持续testing一个variables,直到出现一些值。

最后:

自旋锁(又名Spinlock) =使用繁忙等待的 。 (通过xchg或类似的primefaces操作来获取 )。

[没有线程hibernate,主要用于内核级别。 不能用于用户级别代码]。

作为最后的评论,我不确定,但是我敢打赌,上面的前3个同步对象(#1,#2和#3)使用这个简单的野兽(#4)作为它们实现的一部分。

祝你有美好的一天!。

参考文献:

– 李with与Caroline Yao(CMP图书)embedded式系统的实时概念。

– 由Andrew Tanenbaum(皮尔逊教育国际)撰写的现代操作系统(第三版)。

– Jeffrey Richter编写的Microsoft Windows应用程序(第4版)(Microsoft编程系列)。

另外,你可以看一下看看: https : //stackoverflow.com/a/24586803/3163691

请看John Kopplin的“ multithreading教程” 。

在“ 线程之间同步 ”一节中,他解释了事件,锁,互斥,信号量,等待定时器

互斥锁一次只能由一个线程拥有,使线程能够协调对共享资源的互斥访问

临界区对象提供与互斥对象相似的同步,除了临界区对象只能由单个进程的线程使用

互斥关键部分之间的另一个区别是,如果关键部分对象当前由另一个线程拥有,则EnterCriticalSection()无限期地等待所有权,而与互斥锁一起使用的WaitForSingleObject()允许您指定超时

信号量维持在零和某个最大值之间的计数,从而限制了同时访问共享资源的线程数量。

我会尽量用例子来说明:

locking:您将使用lock一个示例将是一个共享字典,其中必须添加项目(必须具有唯一键)。
锁将确保一个线程不会进入正在检查字典中的项目的代码机制,而另一个线程(即处于关键部分中)已经通过了此检查并正在添加该项目。 如果另一个线程尝试input一个locking的代码,它将等待(被阻止),直到该对象被释放。

 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); } } 

信号量:假设你有一个连接池,那么单个线程可能通过等待信号量来获得一个连接来保留池中的一个元素。 然后使用连接,并在完成工作时通过释放信号释放连接。

我喜欢的代码示例是@Patric给出的保镖之一 – 这里是:

 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); } } } 

互斥这是非常多的Semaphore(1,1)并且经常在全球范围内使用(否则可以认为lock是更合适的)。 从全局可访问列表中删除节点时,会使用全局Mutex (最后一件事情是当另一个线程在删除节点时需要执行某些操作)。 当你获得Mutex如果不同的线程试图获取相同的Mutex ,它将被置于睡眠,直到获得Mutex SAME线程释放它。

创build全局互斥的好例子是@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(); } } } 

然后使用像:

 using (new SingleGlobalInstance(1000)) //1000ms timeout on global lock { //Only 1 of these runs at a time GlobalNodeList.Remove(node) } 

希望这可以为你节省一些时间。

大部分问题都可以用(i)locking,(ii)只是信号量,或者(iii)两者的组合来解决! 正如你可能已经发现的,它们非常相似:既能防止竞争条件 ,又能acquire() / release()操作,都会导致零个或多个线程被阻塞/怀疑……真的,关键的区别仅在于他们如何locking和解锁

  • 一个 (或互斥锁 )有两个状态(0或1)。 它可以被解锁locking 。 它们通常用于确保一次只有一个线程进入关键部分。
  • 信号量有许多状态(0,1,2,…)。 它可以被locking (状态0)或解锁 (状态1,2,3,…)。 一个或多个信号经常一起使用,以确保当某个资源的单元数目达不到特定值时,只有一个线程进入关键部分(通过倒计数到该值或计数到该值)。

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

对于处于状态0的锁,如果先前调用acquire()线程现在调用release,则释放成功。 如果一个不同的线程尝试了这一点 – 就会发生什么(通常是被忽略的尝试或抛出的错误)的实现/库。 对于状态为0的信号量, 任何线程都可以调用释放,并且它会成功(不pipe上一次使用哪个线程获取将信号置于0状态)。

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


造成许多困惑的是,实际上这个高层定义有很多变化

需要考虑的重要变化

  • 应该调用acquire() / release()什么? – [ 大量变化 ]
  • 你的锁/信号量是否使用“队列”或“集合”来记住等待的线程?
  • 你的锁/信号量可以与其他进程的线程共享吗?
  • 你的锁“重入”了吗? – [通常是的]。
  • 你的锁是否“阻塞/非阻塞”? – [通常非阻塞被用作阻塞锁(又称自旋锁)导致忙等待]。
  • 你如何确保这些操作是“primefaces”的?

这些取决于你的书/讲师/语言/图书馆/环境。
这里有一个快速浏览,指出一些语言如何回答这些细节。


C,C ++( pthreads )

  • 互斥体是通过pthread_mutex_t实现的。 默认情况下,它们不能与任何其他进程共享( PTHREAD_PROCESS_PRIVATE ),但是互斥有一个名为pshared的属性。 设置时,互斥量在进程之间共享( PTHREAD_PROCESS_SHARED )。
  • 一个与一个互斥是一回事。
  • 信号量是通过sem_t实现的。 类似于互斥体,信号量可以在许多进程的三线之间共享,或者保持私有的一个进程的线程。 这取决于提供给sem_initpshared参数。

python( threading.py )

  • threading.RLock )大部分与C / C ++ pthread_mutex_t相同。 两者都是可重入的 。 这意味着他们只能通过locking它的同一个线程解锁。 在这种情况下, sem_t信号量, threading.Semaphore信号量信号量和theading.Lock锁的锁是不可重入的 ,因为任何线程都可以解锁锁信号量。
  • 一个互斥锁就像一个锁(这个词在python中不常用)。
  • 信号量threading.Semaphore )与sem_t大体相同。 尽pipe使用sem_t ,线程ID的队列用于记住线程在被locking时尝试locking线程时被阻塞的顺序。 当一个线程解锁一个信号量时,队列中的第一个线程(如果有的话)被选为新的所有者。 线程标识符从队列中取出,信号量再次被locking。 但是,使用threading.Semaphore ,将使用集合而不是队列,所以线程被阻塞的顺序不会被存储 – 集合中的任何线程都可能被选为下一个拥有者。

Java( java.util.concurrent )

  • 一个java.util.concurrent.ReentrantLock )与C / C ++ pthread_mutex_t大致相同,而Python的threading.RLock锁则是实现了一个可重入的锁。 由于JVM充当中介,在Java之间共享进程之间的锁是比较困难的。 如果线程试图解锁它不拥有的锁,则抛出IllegalMonitorStateException
  • 互斥体与锁相同(在Java中这个术语不常用)。
  • 信号量java.util.concurrent.Semaphore )大部分与sem_tthreading.Semaphore相同。 Java信号量的构造函数接受一个公平的布尔参数来控制是否使用set(false)或者队列(true)来存储等待的线程。

从理论上讲,信号量经常被讨论,但是在实践中,信号量并没有被使用太多。 一个信号量只能保持一个整数的状态,所以它通常是非常不灵活的,而且很多都是需要一次的 – 导致难以理解代码。 此外, 任何线程可以释放信号量的事实有时是不受欢迎的。 代替使用更多面向对象/更高级别的同步原语/抽象,如“条件variables”和“监视器”。

维基百科在信号和互斥之间有一个很大的区别 :

互斥量与二进制信号量基本相同,有时使用相同的基本实现。 他们之间的差异是:

互斥锁拥有一个所有者的概念,这是locking互斥锁的过程。 只有locking互斥锁的进程才能解锁它。 相比之下,信号量没有所有者的概念。 任何进程都可以解锁信号量。

与信号量不同,互斥锁提供了优先级反转安全性。 由于互斥体知道其当前所有者,因此只要优先级较高的任务开始等待互斥体,就可以提升所有者的优先级。

互斥锁还提供了删除安全性,其中持有互斥锁的进程不会被意外删除。 信号量不提供这个。

我的理解是,一个互斥体只能在单个进程中使用,而只能在多个线程中使用,而信号量可以跨多个进程使用,也可以跨越相应的线程组。

此外,互斥锁是二进制的(它是locking的或解锁的),而信号量有一个计数的概念,或一个以上的locking和解锁请求的队列。

有人可以validation我的解释吗? 我在Linux环境下发言,特别是使用内核2.6.32的红帽企业Linux(RHEL)版本6。

互斥:

是厕所的关键。 一个人可以拥有钥匙 – 占用厕所 – 当时。 完成后,该人员将(释放)队列中下一个人的钥匙。

正式地说:“互斥锁通常用来串行访问一段不能同时被多个线程执行的重入代码,一个互斥对象只允许一个线程进入一个受控节,强制其他线程尝试访问该部分要等到第一个线程退出该部分。“

(互斥量实际上是一个值为1的信号量。)

信号:

是免费的相同的马桶钥匙的数字。 比如说,我们有四个带有相同的锁和钥匙的洗手间。 信号计数 – 密钥计数 – 在开始时设置为4(所有四个厕所都是免费的),然后计数值随着人们进入而递减。如果所有的厕所都满了, 没有空闲密钥,信号计数为0.现在,当等式 一个人离开厕所,信号量增加到1(一个自由键),并给予队列中的下一个人。

正式地说:“一个信号量限制共享资源的并发用户数达到最大数量,线程可以请求访问资源(递减信号量),并且可以表示他们已经完成使用资源(递增信号量)。 “

在Linux变体上使用C编程作为示例的基本案例。

锁:

•通常是一个非常简单的构造二进制操作,无论是locking或解锁

•没有线程所有权,优先级,sorting等概念

•通常是一个旋转锁,线程会不断检查锁的可用性。

•通常依赖于primefaces操作,例如,testing和设置,比较和交换,提取和添加等。

•通常需要硬件支持primefaces操作。

文件locking:

•通常用于通过多个进程来协调对文件的访问。

•多个进程可以保持读取locking,但是当任何单个进程持有写入locking时,不允许其他进程获取读取或写入locking。

•例如:flock,fcntl等。

互斥:

•互斥函数调用通常在内核空间中工作,并导致系统调用。

•它使用所有权的概念。 只有当前拥有互斥锁的线程才能解锁它。

互斥量不是recursion的(例外:PTHREAD_MUTEX_RECURSIVE)。

•通常用于与条件variables关联,并作为parameter passing给例如pthread_cond_signal,pthread_cond_wait等

•一些UNIX系统允许多个进程使用互斥锁,尽pipe这可能不会在所有系统上实施。

信号:

•这是一个内核维护的整数,其值不得低于零。

它可以用来同步进程。

•信号量的值可以设置为大于1的值,在这种情况下,值通常表示可用资源的数量。

信号量的值被限制为1和0被称为二进制信号量。