锁,互斥和信号量之间的区别是什么?

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

326921 次浏览

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

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

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

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

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

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

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

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

看看约翰·科普林的多线程教程

线程间同步部分中,他解释了事件、锁、互斥量、信号量和可等待计时器之间的区别

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

临界区对象提供类似的同步 由互斥对象提供,除了临界区对象可以 仅供单个进程

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

a# EYZ0保持计数在0和某个最大值之间, 限制同时访问对象的线程数 共享资源。< / p >

维基百科有一个关于信号量和互斥量之间的区别的很棒的部分:

互斥量本质上和二进制信号量是一样的 有时使用相同的基本实现。两者之间的区别 它们是:

互斥对象有一个所有者的概念,这就是进程 这锁定了互斥锁。只有锁定互斥锁的进程可以 解锁。相反,信号量没有所有者的概念。任何 进程可以解锁信号量。< / p > 与信号量不同,互斥锁提供 优先反转安全。由于互斥锁知道它的当前所有者,它 是否有可能提升业主的优先级 高优先级任务开始等待互斥锁。< / p >

互斥也提供 删除安全,其中持有互斥锁的进程不能被删除 不小心删除了。信号量不提供这个

关于这些词有很多误解。

这是之前的帖子(https://stackoverflow.com/a/24582076/3163691),非常适合这里:

1)临界区=用户对象,用于允许从许多其他在一个进程中执行一个活动线程。其他未被选择的线程(@获取这个对象)被放到睡眠< em > < / em >

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

2)互斥信号量(又名互斥)=内核对象,用于允许从许多其他对象执行一个活动线程在不同进程中。其他未被选择的线程(@获取这个对象)被放到睡眠< em > < / em >。该对象支持线程所有权、线程终止通知、递归(同一个线程的多个“acquire”调用)和“优先级反转避免”。

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

3)计数信号量(又名信号量)=内核对象,用于允许从许多其他对象执行一组活动线程。其他未被选择的线程(@获取这个对象)被放到睡眠< em > < / em >

[然而,进程间功能使用起来不太安全,因为它缺乏以下'互斥'属性:线程终止通知,递归?,“优先倒置规避”?等)。

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

关键区域(Critical Region):由两个或多个进程共享的内存区域。

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

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

最后:

自旋锁(又名Spinlock)=使用< em >忙等待< / em >< em > < / em >锁。(< em > < / em >锁是由< em > xchg < / em >或类似的< em > < / em >原子操作获得的)。

没有线程睡眠,主要只在内核级使用。对于用户级别代码无效]。

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

祝你有愉快的一天!

< em >引用:< / em >

-《嵌入式系统的实时概念》,作者:Qing Li和Caroline Yao (CMP Books)。

——安德鲁·塔南鲍姆(皮尔逊教育国际)的《现代操作系统》(第三期)。

-为微软Windows编程应用程序(第4)由Jeffrey Richter(微软编程系列)。

另外,你可以看一看: # EYZ0 < / p >

我会用一些例子来解释:

你会使用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);
}
}
}

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

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

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

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

大多数问题可以使用(i)仅仅锁,(ii)仅仅信号量,…,或(iii)两者的组合!正如你可能已经发现的,它们非常相似:都阻止竞态条件,都有acquire()/release()操作,都导致零或多个线程被阻塞/怀疑…… 实际上,关键的区别仅仅在于how他们锁定和解锁。< / p >

  • (或互斥锁)有两个状态(0或1)。它可以是解锁锁着的。它们通常用于确保一次只有一个线程进入临界区。
  • 信号量有许多状态(0,1,2,…)。它可以是锁着的(状态0)或解锁(状态1,2,3,…)。一个或多个信号量经常一起使用,以确保当某些资源的单元数达到/未达到特定值时,只有一个线程进入临界区(通过向下计数到该值或向上计数到该值)。

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

对于状态为0的锁,如果之前调用acquire()相同线程现在调用release,则释放成功。如果不同的线程尝试这样做,那么将由实现/库决定发生什么(通常是忽略尝试或抛出错误)。对于处于状态0的信号量,任何线程可以调用release,并且它将成功(不管之前哪个线程使用acquire将信号量置于状态0)。

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


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

# EYZ0:

  • #EYZ0/release()应该叫什么?—[变化大规模]
  • # EYZ0
  • # EYZ0
  • 你的锁是“可重入的”吗?—[通常是]。
  • 你的锁是“阻塞/非阻塞”吗?——[通常非阻塞锁被用作阻塞锁(也称为自旋锁),导致繁忙等待]。
  • 如何确保操作是“原子的”?

这取决于你的书/讲师/语言/图书馆/环境 下面是一些语言是如何回答这些细节的


C, c++ (# eyz0)

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

python (# EYZ0)

  • (threading.RLock)与C/ c++ pthread_mutex_ts基本相同。都是可重入。这意味着它们只能被锁定它的同一线程解锁。sem_t信号量,threading.Semaphore信号量和theading.Lock锁是不可重入的——因为任何线程可以执行解锁/锁定信号量。
  • 互斥锁与锁相同(python中不经常使用这个术语)。
  • 信号量 (threading.Semaphore)基本上与sem_t相同。尽管使用sem_t,线程id队列用于记住当线程被锁定时试图锁定它时线程被阻塞的顺序。当一个线程解锁一个信号量时,队列中的第一个线程(如果有的话)将被选为新的所有者。线程标识符从队列中取出,信号量再次被锁定。然而,对于threading.Semaphore,使用的是一个集合而不是队列,因此线程被阻塞的顺序不会被存储——集合中的任何线程可能被选为下一个所有者。

Java (# EYZ0)

  • (java.util.concurrent.ReentrantLock)在很大程度上与C/ c++的pthread_mutex_t和Python的threading.RLock相同,因为它也实现了可重入锁。在Java中,进程间共享锁更加困难,因为JVM充当中介。如果线程试图解锁不属于它的锁,则抛出IllegalMonitorStateException
  • 互斥锁与锁相同(Java中不经常使用这个术语)。
  • 信号量 (java.util.concurrent.Semaphore)基本上与sem_tthreading.Semaphore相同。Java信号量的构造函数接受一个公平布尔参数,该参数控制是使用集合(false)还是队列(true)来存储等待的线程。

理论上,信号量经常被讨论,但在实践中,信号量的使用并不多。信号量只保存一个整数的状态,所以它通常是相当不灵活的,并且需要同时使用许多信号量——这会导致理解代码的困难。此外,任何线程可以释放信号量的事实有时是不希望看到的。取而代之的是更多面向对象/高级同步原语/抽象,如“条件变量”和“监视器”。

使用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的信号量称为二进制信号量。

Supporting ownershipmaximum number of processes share lockmaximum number of allowed processes/threads in critical section是决定并发对象名称/类型的三个主要因素,通用名称为lock。由于这些因素的值是二进制的(有两种状态),我们可以将它们总结为一个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表进行编辑:)

锁,互斥,信号量

这是一个普遍的愿景。细节取决于真正的语言实现

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

互斥锁。这是一种锁。在某些语言中它是进程间机制,在某些语言中它是lock的同义词。例如,Java在synchronisedjava.util.concurrent.locks.Lock中使用lock

semaphore -允许多个线程访问共享资源。您可以发现mutex也可以由semaphore实现。它是一个独立的对象,用于管理对共享资源的访问。你可以发现任何线程可以signal和解除封锁。它也被用于信号

[iOS锁,互斥量,信号量]