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


当前回答

维基百科有一个关于信号量和互斥量区别的很好的章节:

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.

其他回答

我的理解是互斥量只能在单个进程中使用,但可以跨多个线程使用,而信号量可以跨多个进程和它们对应的线程集使用。

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

有人能证实我的解释吗?我说的是Linux环境,特别是使用内核2.6.32的Red Hat Enterprise Linux (RHEL)版本6。

维基百科有一个关于信号量和互斥量区别的很好的章节:

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.

我会用一些例子来解释:

Lock:使用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);
    }
}

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

我喜欢的代码示例是@Patric给出的一个bouncer -在这里:

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

互斥量基本上就是信号量(1,1),并且经常在全局范围内使用(应用范围内使用,否则可以说锁更合适)。当从全局可访问的列表中删除节点时,可以使用全局互斥(在删除节点时,最不希望另一个线程做一些事情)。当你获得互斥锁时,如果不同的线程试图获得同一个互斥锁,它将被置于睡眠状态,直到获得互斥锁的同一线程释放它。

@deepe是创建全局互斥的一个很好的例子

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

然后用like:

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

希望这能为您节省一些时间。

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

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

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

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

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

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

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