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


当前回答

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

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

有人能证实我的解释吗?我说的是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)
}

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

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

看一看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.

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

锁:

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

•没有线程所有权、优先级、顺序等概念。

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

•通常依赖于原子操作,例如Test-and-set, compare-and-swap, fetch-and-add等。

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

文件锁:

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

多个进程可以持有读锁,但是当任何一个进程持有写锁时,不允许其他进程获得读或写锁。

•示例:flock, fcntl等。

互斥:

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

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

互斥不是递归的(异常:PTHREAD_MUTEX_RECURSIVE)。

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

•一些UNIX系统允许多个进程使用互斥锁,尽管这可能不是在所有系统上强制执行。

信号量:

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

•可用于同步进程。

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

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