Answers:
锁只允许一个线程进入被锁定的部分,并且该锁不与任何其他进程共享。
互斥锁与锁相同,但可以是系统范围的(由多个进程共享)。
甲旗语不相同,为互斥,但允许x个线程进入的,这可以被用于例如限制CPU的数量,IO或RAM在同一时间运行密集型任务。
有关互斥量和信号量之间差异的更多详细信息,请阅读此处。
您还具有读/写锁,在任何给定时间允许无限数量的读取器或1个写入器。
关于这些词有很多误解。
这来自上一篇文章(https://stackoverflow.com/a/24582076/3163691),在这里非常适合:
1)关键部分 =用户对象,用于允许从一个进程中的许多其他线程中仅执行一个活动线程。其他未选择的线程(@获取该对象)进入睡眠状态。
[没有进程间功能,非常原始的对象]。
2)Mutex Semaphore(又名Mutex) =内核对象,用于允许在不同进程之间仅执行来自许多其他线程的一个活动线程。其他未选择的线程(@获取该对象)进入睡眠状态。该对象支持线程所有权,线程终止通知,递归(来自同一线程的多个“获取”调用)和“避免优先级转换”。
[进程间功能,使用非常安全,是一种“高级”同步对象]。
3)计数信号量(aka信号量) =内核对象,用于允许执行许多其他活动线程组。其他未选择的线程(@获取该对象)进入睡眠状态。
[但是,由于缺少以下'互斥'属性,因此进程间功能使用起来不是很安全:线程终止通知,递归?,'避免优先级反转'等”。
4)现在,在谈论“自旋锁”时,首先给出一些定义:
关键区域=由2个或更多进程共享的内存区域。
Lock =一个变量,其值允许或拒绝进入“关键区域”。(可以将其实现为简单的“布尔标志”)。
繁忙等待=连续测试变量,直到出现某个值。
最后:
自旋锁(又名自旋锁) = 使用忙等待的锁。(通过xchg或类似的原子操作获得锁)。
[没有线程休眠,通常仅在内核级别使用。对于用户级代码无效)。
最后一点,我不确定,但是我可以打赌,上面的前三个同步对象(#1,#2和#3)将这个简单的野兽(#4)用作实现的一部分,这可是一笔大钱。
祝你有美好的一天!。
参考文献:
-Qing Li和Caroline Yao(CMP Books)的嵌入式系统实时概念。
-现代操作系统(第3版),作者:安德鲁·塔南鲍姆(Pearson Education International)。
Jeffrey Richter编写的Microsoft Windows编程应用程序(第4版)(Microsoft编程系列)。
另外,您可以看一下:https : //stackoverflow.com/a/24586803/3163691
使用(i)仅锁,(ii)仅信号灯,...,或(iii)两者的组合可以解决大多数问题!正如您可能已经发现的那样,它们非常相似:两者都防止争用情况,都具有acquire()
/ release()
操作,都导致零个或多个线程被阻塞/可疑...确实,关键的区别仅在于它们如何锁定和解锁。
对于这两个锁/信号量,acquire()
在原语处于状态0时尝试进行调用都会导致调用线程被挂起。对于锁-尝试获取处于状态1的锁是成功的。对于信号量-尝试获取状态{1,2,3,...}中的锁定是成功的。
对于状态0的锁,如果以前调用的同一线程acquire()
现在调用release,则释放成功。如果不同的线程尝试了此操作-取决于执行情况/库会发生什么情况(通常是尝试被忽略或抛出错误)。对于状态0的信号量,任何线程都可以调用release,它将成功(无论先前使用哪个线程将信号量置于状态0)。
从前面的讨论中,我们可以看到锁具有所有者的概念(可以调用release的唯一线程是所有者),而信号量没有所有者(任何线程都可以在信号量上调用release)。
引起很多困惑的是,实际上它们有很多差异此高级定义的。
要考虑的重要变化:
acquire()
/ release()
叫?- [不定大规模 ]这些取决于您的书/讲师/语言/图书馆/环境。
这是一个快速导览,指出某些语言如何回答这些细节。
pthread_mutex_t
。默认情况下,它们不能与任何其他进程(PTHREAD_PROCESS_PRIVATE
)共享,但是互斥体具有称为pshared的属性。设置后,互斥锁将在进程(PTHREAD_PROCESS_SHARED
)之间共享。sem_t
。与互斥锁类似,信号量可以在多个进程的威胁之间共享,或者对单个进程的线程保持私有。这取决于提供给的pshared参数sem_init
。threading.RLock
)是大多相同的C / C ++ pthread_mutex_t
秒。两者都是可重入的。这意味着它们只能由锁定它的同一线程来解锁。在这种情况下,sem_t
信号量,threading.Semaphore
信号量和theading.Lock
锁不能重入 -因为任何线程都可以执行解锁/关闭信号量的情况。threading.Semaphore
)是大多相同sem_t
。尽管使用sem_t
了,但线程ID队列用于记住线程被锁定时尝试被阻塞的顺序。当线程解锁信号量时,队列中的第一个线程(如果有)被选择为新所有者。线程标识符从队列中移出,并且信号量再次被锁定。但是,使用时threading.Semaphore
,将使用集合而不是队列,因此不会存储线程被阻塞的顺序- 可以选择集合中的任何线程作为下一个所有者。java.util.concurrent.ReentrantLock
)是大多相同的C / C ++ pthread_mutex_t
的,和Python的threading.RLock
,因为它也实现了一个可重入锁。在Java中,由于JVM充当中介,因此在进程之间共享锁比较困难。如果线程试图解锁它不拥有的锁,IllegalMonitorStateException
则会抛出一个。java.util.concurrent.Semaphore
)是大多相同sem_t
和threading.Semaphore
。Java信号量的构造函数接受一个公平布尔值参数,该参数控制是使用集合(false)还是使用队列(true)来存储等待线程。 从理论上讲,信号灯经常被讨论,但是实际上,信号灯的使用并不多。信号量仅保存一个整数的状态,因此通常它相当不灵活,并且一次需要很多-导致难以理解代码。同样,有时不希望任何线程都可以释放信号量。而是使用更多的面向对象/更高级别的同步原语/抽象,例如“条件变量”和“监视器”。
看看John Kopplin的《多线程教程》。
在“ 线程之间的同步 ”部分中,他解释了事件,锁,互斥,信号量,等待计时器之间的差异。
一个互斥体只能由一个线程在同一时间内资,使线程来协调对共享资源的互斥访问
关键部分对象提供的同步与互斥对象提供的同步类似,不同之处在于关键部分对象只能由单个进程的线程使用
互斥锁和关键节之间的另一个区别是,如果关键节对象当前由另一个线程
EnterCriticalSection()
拥有,则无限期地等待所有权,而WaitForSingleObject()
与互斥锁一起使用的,则可以指定超时甲旗语保持和一些最大值零之间的计数,从而限制了被同时访问共享资源的线程数。
我将尝试通过示例进行介绍:
锁:您将使用的一个示例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给出的保镖之一 -在这里:
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)
量(Mutex)它是非常多的,并且经常在全球范围内使用(应用范围广,否则可以说lock
是更合适的)。Mutex
从全局可访问列表中删除节点时,将使用全局(最后一件事是您希望另一个线程在删除节点时执行某些操作)。当您获取Mutex
是否有其他线程尝试获取Mutex
相同的线程时,它将进入休眠状态,直到获取该线程的SAME线程Mutex
释放它为止。
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)
}
希望这可以节省您一些时间。
我的理解是,互斥锁只能在单个进程中使用,但可以在其多个线程中使用,而信号量可以在多个进程及其相应的线程集中使用。
同样,互斥锁是二进制的(它是锁定的还是未锁定的),而信号量则具有计数的概念,或者具有多个锁定和解锁请求的队列。
有人可以验证我的解释吗?我是在Linux的背景下,特别是使用内核2.6.32的Red Hat Enterprise Linux(RHEL)版本6。
以Linux变体上的C编程为例。
锁:
•通常一个非常简单的构造二进制文件在操作中处于锁定或未锁定状态
•没有线程所有权,优先级,排序等概念。
•通常为自旋锁,线程在该自旋锁中连续检查锁的可用性。
•通常依赖于原子操作,例如“测试并设置”,“比较并交换”,“获取并添加”等。
•通常需要原子操作的硬件支持。
文件锁:
•通常用于协调通过多个进程对文件的访问。
•多个进程可以持有读锁定,但是,当任何一个进程持有写锁定时,则不允许其他进程获取读或写锁定。
•示例:flock,fcntl等。
互斥体:
•Mutex函数调用通常在内核空间中工作,并导致系统调用。
•它使用所有权的概念。当前持有互斥量的线程只能将其解锁。
•互斥体不是递归的(异常:PTHREAD_MUTEX_RECURSIVE)。
•通常与条件变量结合使用,并作为参数传递给例如pthread_cond_signal,pthread_cond_wait等。
•一些UNIX系统允许互斥锁由多个进程使用,尽管可能并非在所有系统上都强制使用此互斥锁。
信号:
•这是内核维护的整数,其值不允许降至零以下。
•可用于同步进程。
•信号量的值可以设置为大于1的值,在这种情况下,该值通常表示可用资源的数量。
•值限制为1和0的信号量称为二进制信号量。
Supporting ownership
,maximum number of processes share lock
并且maximum number of allowed processes/threads in critical section
是决定用的通用名称的名称/类型的并发对象的三个主要因素lock
。由于这些因子的值是二进制的(具有两种状态),因此我们可以在3 * 8的类似真值的表中对其进行汇总。
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表:)