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

时间:2008-11-19 06:26:57

标签: c# multithreading synchronization locking mutex

有人可以解释之间的区别:

  • lock(someobject){}
  • 使用Mutex
  • 使用信号量
  • 使用监视器
  • 使用其他.Net同步类

我无法弄明白。在我看来前两个是相同的?

7 个答案:

答案 0 :(得分:130)

好问题。我可能错了..让我试试..我的原始答案的修订版#2 ......稍微有点理解。谢谢你让我读到:)

锁定(obj)

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

监视器

  • lock(obj)是使用Monitor在内部实现的。你应该更喜欢lock(obj),因为它可以防止你像忘记清理过程那样搞砸了。如果你愿意的话,那就是监视器构造的傻瓜证明 使用Monitor通常比互斥锁更受欢迎,因为监视器是专门为.NET Framework设计的,因此可以更好地利用资源。

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

Mutex:

  • 与监视器不同,可以使用互斥锁来跨进程同步线程。当用于进程间同步时,互斥锁称为名为mutex ,因为它将在另一个应用程序中使用,因此不能通过全局变量或静态变量共享它。必须为其指定名称,以便两个应用程序都可以访问相同的互斥对象。 相反, Mutex类是Win32构造的包装器。虽然它比监视器更强大,但是互斥锁需要互操作转换,这些转换的计算成本比Monitor类所需的更高。

Semaphores (伤害了我的大脑)。

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

THE PAGE TO READ - Thread Synchronization (C#)

答案 1 :(得分:28)

重新“使用其他.Net同步类” - 您应该了解的其他一些内容:

在CCR / TPL(Parallel Extensions CTP)中还有更多(低开销)锁定结构 - 但是IIRC,这些将在.NET 4.0中提供

答案 2 :(得分:13)

正如ECMA中所述,正如您可以从Reflected方法中看到的那样,lock语句基本上等同于

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

从前面提到的例子中我们看到监视器可以锁定对象。

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

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

答案 3 :(得分:12)

我做了课程& CLR支持DotGNU中的线程,我有一些想法...

除非您需要跨进程锁定,否则应始终避免使用Mutex& amp;信号灯。 .NET中的这些类是Win32 Mutex和Semaphores的包装器,并且相当重要(它们需要上下文切换到内核,这很昂贵 - 特别是如果你的锁没有争用)。

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

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

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

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

我不确定微软的锁实现,但在DotGNU和Mono中,锁状态标志存储在每个对象的标头中。 .NET(和Java)中的每个对象都可以成为一个锁,因此每个对象都需要在其标题中支持它。在DotGNU实现中,有一个标志允许您为每个用作锁的对象使用全局哈希表 - 这有利于消除每个对象的4字节开销。这对于内存来说并不是很好(特别是对于没有高度线程化的嵌入式系统)但是性能受到了打击。

Mono和DotGNU都有效地使用互斥锁来执行锁定/等待,但使用自旋锁式compare-and-exchange操作来消除实际执行硬锁的需要,除非确实有必要:

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

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

答案 4 :(得分:9)

锁定在您使用字符串ID标识的任何共享互斥锁上的另一个警告是它将默认为“本地”互斥锁,并且不会在终端服务器环境中的会话之间共享。

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

答案 5 :(得分:5)

如果可以的话,我会尽量避免“锁定()”,“互斥”和“监视”......

在.NET 4中查看新命名空间System.Collections.Concurrent 它有一些很好的线程安全的集合类

http://msdn.microsoft.com/en-us/library/system.collections.concurrent.aspx

ConcurrentDictionary摇滚!我不再手动锁定了!

答案 6 :(得分:-2)

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

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

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

我在C#上从GCD(caret世界)实现了serial queue-非常轻巧,没有阻塞使用线程池进行测试的同步工具。

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