锁定,互斥,信号量...有什么区别?

时间:2010-02-25 09:03:19

标签: concurrency locking mutex semaphore

我听说过这些与并发编程相关的词,但它们之间的区别是什么?

11 个答案:

答案 0 :(得分:439)

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

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

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

有关互斥锁和信号量之间差异的更详细信息,请阅读here

您还拥有读/写锁,允许在任何给定时间无限数量的读者或1位作者。

答案 1 :(得分:94)

对这些词有很多误解。

这是来自上一篇文章(https://stackoverflow.com/a/24582076/3163691),它非常适合这里:

1)关键部分 =用于允许执行 一个活动线程 的用户对象 在一个过程中 。其他未选择的线程(@获取此对象)将被置于 sleep

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

2)Mutex Semaphore(又名Mutex) =用于允许从其他许多人执行 一个活动线程 的内核对象,< strong> 在不同的进程中 。其他未选择的线程(@获取此对象)将被置于 sleep 。此对象支持线程所有权,线程终止通知,递归(多次&#39;从同一线程获取&#39;调用)和&#39;优先级反转避免&#39;。

[进程间功能,使用非常安全,是一种高级别的&#39;同步对象]。

3)计算信号量(又名信号量) =用于允许从许多其他人执行 一组活动线程 的内核对象。其他未选择的线程(@获取此对象)将被置于 sleep

[进程间功能但使用起来不太安全,因为它缺少以下&#39;互斥体&#39;属性:线程终止通知,递归?,&#39;优先级反转避免&#39;?等等。

4)现在,谈论&#39;自旋锁&#39;首先是一些定义:

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

Lock =一个变量,其值允许或拒绝进入关键区域&#39;。 (它可以实现为一个简单的布尔标志&#39;)。

忙碌等待=连续测试变量直到出现某个值。

最后:

自旋锁定(又称自旋锁) = 锁定 ,使用 忙等待 。 (获取 是由 xchg 或类似的 原子操作 )。

[没有线程休眠,主要仅在内核级别使用。用户级代码的能力]。

作为最后的评论,我不确定,但我可以打赌你们上面的前3个同步对象(#1,#2和#3)使用这个简单的野兽(#4)作为他们的实施。

祝你有个美好的一天!

<强> 参考文献:

- 李庆和Caroline Yao(CMP Books)的嵌入式系统的实时概念。

- Andrew Tanenbaum(Pearson Education International)的现代操作系统(第3版)。

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

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

答案 2 :(得分:20)

看看John Kopplin撰写的Multithreading Tutorial

线程间同步一节中,他解释了事件,锁,互斥,信号量,等待计时器之间的差异

  

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

     

关键部分对象提供与此类似的同步   由互斥对象提供,但临界区对象除外   仅由单个进程的线程使用

     

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

     

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

答案 3 :(得分:18)

大多数问题可以使用(i)锁定,(ii)只是信号量,......,或(iii)两者的组合来解决!正如您可能已经发现的那样,它们非常相似:两者都阻止了race conditions,两者都进行了acquire() / release()操作,都会导致零个或多个线程被阻塞/怀疑。 。 实际上,关键的区别仅在于 如何锁定和解锁

  • 锁定(或互斥锁)有两种状态(0或1)。它可以是解锁已锁定。它们通常用于确保一次只有一个线程进入临界区。
  • 信号量有许多状态(0,1,2,...)。它可以锁定(状态0)或解锁(状态1,2,3,...)。一个或多个信号量通常一起使用,以确保当某个资源的单元数达到特定值时(通过倒计数到该值或计数到达),只有一个线程准确地进入关键部分。那个价值)。

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

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

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

导致很多混乱的原因是,在实践中,他们是这个高级定义的很多变种

需要考虑的重要变体

  • acquire() / release()应该被叫什么? - [变异massively]
  • 你的锁/信号量是否使用&#34;队列&#34;或者&#34;设置&#34;记住线程在等待吗?
  • 您的锁/信号量可以与其他进程的线程共享吗?
  • 你的锁是&#34;可重入&#34;? - [通常是]。
  • 你的锁是&#34;阻塞/非阻塞&#34;? - [通常非阻塞被用作阻塞锁(又名自旋锁)导致忙碌等待]。< / LI>
  • 您如何确保操作&#34;原子&#34;?

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

C,C ++(pthreads

  • 互斥是通过pthread_mutex_t实施的。默认情况下,它们无法与任何其他进程(PTHREAD_PROCESS_PRIVATE)共享,但互斥锁具有名为 pshared 的属性。设置后,进程(PTHREAD_PROCESS_SHARED)之间共享互斥锁。
  • 与互斥锁相同。
  • 信号量是通过sem_t实现的。与互斥锁类似,信号量可以在许多进程的threasd之间共享,也可以保持对一个进程的线程的私有。这取决于提供给sem_init pshared 参数。

python(threading.py

  • threading.RLock)与C / C ++ pthread_mutex_t大致相同。两者都是可重入。这意味着它们只能被锁定它的同一个线程解锁。情况是sem_t信号量,threading.Semaphore信号量和theading.Lock不可重入 - 因为情况 any 线程可以执行解锁锁定/关闭信号量。
  • 互斥锁与锁相同(该术语在python中不经常使用)。
  • 信号量threading.Semaphore)与sem_t大致相同。虽然使用sem_t,但是线程ID队列用于记住在锁定线程时尝试锁定线程时线程被阻塞的顺序。当线程解锁信号量时,队列中的第一个线程(如果有的话)被选为新的所有者。线程标识符从队列中取出,信号量再次被锁定。但是,对于threading.Semaphore,使用集合而不是队列,因此不会存储线程被阻塞的顺序 - 集合中的任何线程可能被选为下一个所有者。

Java(java.util.concurrent

  • 锁定java.util.concurrent.ReentrantLock)与C / C ++ pthread_mutex_t&和Python threading.RLock中的IllegalMonitorStateException大致相同它还实现了一个重入锁。由于JVM充当中介,因此在Java中共享锁之间的锁是更难的。如果一个主题试图解锁它不拥有的锁,则会抛出java.util.concurrent.Semaphore
  • 互斥锁与锁相同(该术语在Java中不经常使用)。
  • 信号量sem_t)与threading.Semaphore和{{1}}大致相同。 Java信号量的构造函数接受 fairness 布尔参数,该参数控制是使用set(false)还是队列(true)来存储等待的线程。

理论上,通常会讨论信号量,但在实践中,信号量并没有被使用过。信号量只保持一个整数的状态,因此它通常相当不灵活,并且需要立即执行许多 - 导致难以理解代码。此外,任何线程可以释放信号量的事实有时是不受欢迎的。更多面向对象/更高级别的同步原语/抽象,例如&#34;条件变量&#34;和&#34;监视&#34;而是用来代替。

答案 4 :(得分:13)

我将尝试用例子来说明:

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

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

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

Code example that I love is one of bouncer given by @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);
        }
    }
}

Mutex 它几乎是Semaphore(1,1)并且经常在全球范围内使用(在应用程序范围内,可以说lock更合适)。从全局可访问列表中删除节点时,可以使用全局Mutex(在删除节点时,您希望另一个线程执行某些操作)。当你获得Mutex时,如果不同的线程试图获取相同的Mutex,它将被置于睡眠状态,直到获得Mutex的SAME线程释放它为止。

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

然后使用:

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

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

答案 5 :(得分:7)

维基百科在differences between Semaphores and Mutexes上有一个很棒的部分:

  

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

     

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

     

与信号量不同,互斥量提供   优先倒置安全。由于互斥锁知道它的当前所有者,它   每当a。有可能提升所有者的优先权   优先级较高的任务开始等待互斥锁。

     

互斥体也提供   删除安全性,持有互斥锁的进程不能   意外删除。信号量不提供此功能。

答案 6 :(得分:4)

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

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

有人可以核实我的解释吗?我是在Linux的背景下发言,特别是使用内核2.6.32的Red Hat Enterprise Linux(RHEL)版本6。

答案 7 :(得分:2)

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

<强>锁定

•通常一个非常简单的构造二进制文件在操作中被锁定或解锁

•没有线程所有权,优先级,排序等概念。

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

•通常依赖原子操作,例如测试和设置,比较和交换,获取和添加等。

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

文件锁定:

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

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

•示例:flock,fcntl等..

<强>互斥:

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

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

•互斥锁不是递归的(例外:PTHREAD_MUTEX_RECURSIVE)。

•通常用于与条件变量关联,并作为参数传递给例如pthread_cond_signal,pthread_cond_wait等。

•某些UNIX系统允许多个进程使用互斥锁,但可能并未在所有系统上强制执行。

<强>信号量:

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

•它可用于同步流程。

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

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

答案 8 :(得分:1)

Supporting ownershipmaximum number of processes share lockmaximum number of allowed processes/threads in critical section是确定通用名称为lock的并发对象的名称/类型的三个主要因素。由于这些因子的值是二进制的(具有两种状态),我们可以将它们汇总在一个3 * 8的类似真值的表中。

  • X(支持所有权吗?):否(0)/是(1)
  • Y(#个共享过程):> 1(∞)/ 1
  • Z(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表:)

答案 9 :(得分:0)

这是一个总体愿景。细节取决于实际语言实现

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

mutex - 互斥锁。它是一种锁。在某些语言上它是进程间机制,在某些语言上它是 lock 的同义词。例如 Java 在 locksynchronised

中使用 java.util.concurrent.locks.Lock

semaphore - 允许多个线程访问共享资源。你会发现 mutex 也可以通过 semaphore 来实现。它是一个独立的对象,用于管理对共享资源的访问。您会发现任何线程都可以signal并解除阻塞。也用于信令

[iOS lock, mutex, semaphore]

答案 10 :(得分:0)

C# 公开了一个语句(锁)和两种主要类型的同步原语来拯救你:互斥体和信号量。

要使用 lock 语句锁定资源并且一次只允许一个线程对其进行操作,请使用 lock([RESOURCE]){…} 语法:

lock(netWorth) {
  ...
}

netWorth 变量在锁定代码块的持续时间内被锁定(代码离开代码块后,锁定被释放)并且一次只能被一个线程访问。