C#中各种线程同步选项之间有什么区别?


Answers:


135

好问题。我可能错了..让我尝试..我的原始答案的修订版#..有了一点更多的了解。谢谢你让我读:)

锁(obj)

  • 是用于(对象内部?)线程同步的CLR构造。确保只有一个线程可以拥有对象锁的所有权并输入锁定的代码块。其他线程必须等到当前所有者通过退出代码块来放弃锁。另外,建议您锁定类的私有成员对象。

监控器

  • lock(obj)是使用Monitor在内部实现的。您应该首选lock(obj),因为它可以防止您像忘记清理过程一样烦恼。如果愿意的话,它可以保证Monitor构造的“白痴”。
    与互斥锁相比,通常首选使用Monitor,因为Monitor是专门为.NET Framework设计的,因此可以更好地利用资源。

使用锁或监视器对于防止同时执行线程敏感的代码块很有用,但是这些构造不允许一个线程将事件传递给另一个线程。这需要同步事件,这些事件是具有信号状态和未信号状态两种状态之一的对象,可用于激活和挂起线程。互斥量,信号量是操作系统级别的概念。例如,使用命名的互斥锁,您可以在多个(托管)exe之间进行同步(确保仅在计算机上运行应用程序的一个实例。)

互斥体:

  • 但是,与监视器不同,互斥锁可用于跨进程同步线程。当用于进程间同步时,互斥锁称为命名互斥锁,因为它将在另一个应用程序中使用,因此不能通过全局变量或静态变量进行共享。必须给它起一个名字,以便两个应用程序都可以访问同一个互斥对象。相反,Mutex类是Win32构造的包装。尽管互斥锁比监视器强大,但互斥体转换所需的互操作转换比Monitor类所需的互操作转换更加昂贵。

信号量(伤了我的大脑)。

  • 使用Semaphore类来控制对资源池的访问。线程通过调用从WaitHandle类继承的WaitOne方法进入信号量,并通过调用Release方法释放信号量。每次线程进入信号量时,信号量的计数就会减少,而当线程释放信号量时,信号量的计数就会增加。当计数为零时,后续请求将阻塞,直到其他线程释放该信号量为止。当所有线程都释放了信号量时,该计数为创建信号量时指定的最大值。 线程可以多次输入信号量。.Semaphore类不会在WaitOne或Release上强制执行线程标识。 信号量有两种类型:局部信号量和命名信号量系统信号量。如果使用接受名称的构造函数创建Semaphore对象,则该对象将与该名称的操作系统信号灯关联。命名的系统信号在整个操作系统中都是可见的,可用于同步进程的活动。 本地信号灯仅存在于您的进程中。进程中任何引用了本地Semaphore对象的线程都可以使用它。每个信号量对象都是一个单独的本地信号量。

要读取的页面-线程同步(C#)


18
您声称Monitor不允许交流是不正确的;您仍然可以PulseMonitor
Marc Gravell等

3
退房信号灯的备用说明- stackoverflow.com/a/40473/968003。将信号量视为夜总会里的蹦蹦跳跳。一次有大量的人员被允许进入俱乐部。如果俱乐部已满,则不允许任何人进入,但一旦一个人离开,另一个人可能会进入。
亚历克斯·克劳斯

31

关于“使用其他.Net同步类”-您应该了解的其他一些:

CCR / TPL(并行扩展 CTP)中还有更多(低开销)锁定结构-但是IIRC,它们将在.NET 4.0中提供


因此,如果我想进行简单的信号通信(例如异步操作的完成),我应该使用Monitor.Pulse吗?还是使用SemaphoreSlim或TaskCompletionSource?
Vivek 2014年

使用TaskCompletionSource进行异步操作。基本上,停止考虑线程,开始考虑任务(工作单元)。线程是实现细节,并不相关。通过返回TCS,您可以返回结果,错误或处理取消,并且可以与其他异步操作(例如async await或ContinueWith)轻松组合。
西蒙·吉尔比

14

正如ECMA中所述,您可以从Reflected方法中观察到,lock语句基本上等效于

object obj = x;
System.Threading.Monitor.Enter(obj);
try {
   
}
finally {
   System.Threading.Monitor.Exit(obj);
}

从上述示例中,我们可以看到Monitor可以锁定对象。

当您需要进程间同步时,互斥锁很有用,因为它们可以锁定字符串标识符。不同的进程可以使用相同的字符串标识符来获取锁。

信号量就像类固醇上的互斥量,它们通过提供最大数量的并发访问来允许并发访问。一旦达到限制,信号量就会开始阻止对资源的任何进一步访问,直到调用者之一释放该信号量为止。


5
这句法糖在c稍有变化#4退房blogs.msdn.com/ericlippert/archive/2009/03/06/...
彼得Gfader

14

我为DotGNU中的线程做了类和CLR支持,我有几点想法...

除非您需要跨进程锁,否则应始终避免使用Mutex&Semaphores。.NET中的这些类是Win32 Mutex和Semaphores的包装,它们的重量非常大(它们需要将上下文切换到内核中,这很昂贵-尤其是在您的锁不在争用状态时)。

正如其他提到的那样,C#lock语句是Monitor.Enter和Monitor.Exit(存在于try / finally中)的编译器魔术。

监视器具有一个简单但功能强大的信号/等待机制,而互斥量通过Monitor.Pulse / Monitor.Wait方法没有。Win32等效项是通过CreateEvent生成的事件对象,该对象实际上在.NET中也以WaitHandles的形式存在。Pulse / Wait模型类似于Unix的pthread_signal和pthread_wait,但速度更快,因为它们在完全无争议的情况下可以完全是用户模式的操作。

Monitor.Pulse / Wait使用简单。在一个线程中,我们锁定一个对象,检查一个标志/状态/属性,如果不是我们期望的,请调用Monitor.Wait,它将释放锁定并等待直到发送脉冲。等待返回时,我们循环返回并再次检查标志/状态/属性。在另一个线程中,只要更改标志/状态/属性,然后调用PulseAll来唤醒所有侦听线程,就锁定对象。

通常,我们希望类是线程安全的,因此我们在代码中加了锁。但是,通常情况下,我们的类只会被一个线程使用。这意味着锁不必要地减慢了我们的代码的速度...这是CLR中巧妙的优化可以帮助提高性能的地方。

我不确定Microsoft的锁的实现,但是在DotGNU和Mono中,锁状态标志存储在每个对象的标头中。.NET(和Java)中的每个对象都可以变成一个锁,因此每个对象都需要在其标头中支持此锁。在DotGNU实现中,有一个标志允许您为用作锁定的每个对象使用全局哈希表-这样做的好处是消除了每个对象的4字节开销。这对于内存(特别是对于线程不多的嵌入式系统)而言不是很好,但会影响性能。

Mono和DotGNU都有效地使用互斥锁来执行锁定/等待,但是除非有必要,否则使用自旋锁样式的比较和交换操作来消除实际执行硬锁的需要:

您可以在此处查看如何实现监视器的示例:

http://cvs.savannah.gnu.org/viewvc/dotgnu-pnet/pnet/engine/lib_monitor.c?revision=1.7&view=markup


9

锁定已标识为字符串ID的任何共享Mutex的另一项警告是,它将默认为“ Local \”互斥量,并且不会在终端服务器环境中的各个会话之间共享。

为字符串标识符加上“ Global \”前缀,以确保正确控制对共享系统资源的访问。在意识到这一点之前,我刚遇到了一大堆问题,即如何与在SYSTEM帐户下运行的服务进行通信同步。


5

如果可以的话,我会尽量避免使用“ lock()”,“ Mutex”和“ Monitor”。

在.NET 4中签出新的命名空间System.Collections.Concurrent。
它具有一些不错的线程安全的集合类。

http://msdn.microsoft.com/zh-CN/library/system.collections.concurrent.aspx

并发词典岩石!我不再需要手动锁定!


2
避免锁定但使用Monitor?为什么?
mafu 2011年

@mafutrct因为您需要自己进行同步。
Peter Gfader 2011年

哦,现在我明白了,您的意思是避免提到所有三个想法。听起来您会使用Monitor,但不会使用lock / Mutex。
mafu 2011年

永远不要使用System.Collections.Concurrent。它们是争用条件的主要来源,并且也是阻止调用者线程的原因。
亚历山大·丹尼洛夫

-2

在大多数情况下,您不应使用锁(= Monitors)或互斥/信号量。它们都阻塞了当前线程。

而且你绝对不应该使用 System.Collections.Concurrent类-它们是争用条件的主要来源,因为它们不支持多个集合之间的事务,并且不支持当前线程。

令人惊讶的是,.NET没有有效的同步机制。

我在C#上从GCD(世界)实现了串行队列Objc/Swift -非常轻巧,没有阻塞使用线程池的同步工具以及测试。

在大多数情况下,这是同步所有内容的最佳方法-从数据库访问(hello sqlite)到业务逻辑。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.