C#中各种线程同步选项之间的区别是什么?
有人可以解释之间的区别:
- 锁(someobject){}
- 使用互斥体
- 使用信号量
- 使用监视器
- 使用其他.Net同步类
我只是无法弄清楚。 在我看来,前两个是一样的?
伟大的问题。 我可能错了..让我试试..我的原始答案修订#2 ..有一点点了解。 感谢使我读:)
锁(OBJ)
- 是(对象内?)线程同步的CLR构造。 确保只有一个线程可以获取对象锁的所有权并inputlocking的代码块。 其他线程必须等到当前所有者放弃代码块才放弃locking。 另外,build议您lockingclass级的私人成员对象。
显示器
- 锁(obj)是使用Monitor在内部实现的。 你应该更喜欢lock(obj),因为它可以防止你忘记清理过程。 如果你愿意的话,那就是“监视”结构。
使用监视器通常比互斥锁更受欢迎,因为监视器是专门为.NET Frameworkdevise的,因此可以更好地利用资源。
使用locking或监视器对于防止同时执行线程敏感的代码块很有用,但这些构造不允许一个线程将事件传递给另一个线程。 这需要同步事件 ,这些事件是具有两种状态之一的对象,可以用来激活和挂起线程。 互斥量,信号量是操作系统级别的概念。 例如使用一个已命名的互斥体,可以跨多个(受pipe理的)exes进行同步(确保机器上只运行一个应用程序实例)。
互斥:
- 但是,与监视器不同的是, 可以使用互斥锁来跨进程同步线程。 当用于进程间同步时,互斥体被称为已命名的互斥体,因为它将被用在另一个应用程序中,因此它不能通过全局variables或静态variables共享。 必须给它一个名字,以便两个应用程序都可以访问同一个互斥对象。 相比之下, Mutex类是Win32构造的包装。 虽然它比监视器更强大,但互斥转换需要互操作转换,这些转换在计算上比Monitor类所需的更为昂贵。
信号量 (伤害了我的大脑)。
- 使用Semaphore类来控制对资源池的访问。 线程通过调用从WaitHandle类inheritance的WaitOne方法来input信号量,并通过调用Release方法来释放信号量。 每次线程进入信号量时,信号量的计数都会递减,而当线程释放信号量时递增。 当计数为零时,后续请求将阻塞,直到其他线程释放信号量。 当所有的线程都释放了信号量时,计数值是在创build信号量时指定的最大值。 一个线程可以多次进入信号量。Semaphore类不会在WaitOne或Release上强制执行线程标识。程序员的责任不在于此。 信号量有两种types:本地信号量和命名系统信号量。 如果使用接受名称的构造函数创buildSemaphore对象,则该对象将与该名称的操作系统信号相关联。 命名系统信号在整个操作系统中都是可见的,并且可以用来同步进程的活动。 一个本地的信号量只存在于你的进程中。 它可以被你的进程中有本地Semaphore对象的引用的任何线程使用。 每个信号量对象是一个单独的本地信号量。
读取页面 – 线程同步(C#)
重新“使用其他.Net同步类” – 其他一些你应该知道的:
- ReaderWriterLock – 允许多个读者或一个作者(不在同一时间)
- ReaderWriterLockSlim – 如上所述,降低开销
- ManualResetEvent – 打开时允许代码过去的门
- AutoResetEvent – 如上所述,但打开后自动closures
在CCR / TPL( 并行扩展 CTP)中也有更多(低开销)locking结构 – 但是IIRC,这些将在.NET 4.0
正如ECMA所述,正如您从反思方法中所观察到的,locking语句基本上等同于
object obj = x; System.Threading.Monitor.Enter(obj); try { … } finally { System.Threading.Monitor.Exit(obj); }
从前面提到的例子中,我们看到监视器可以locking对象。
Mutexe在需要进程间同步时非常有用,因为它们可以lockingstring标识符。 相同的string标识符可以被不同的进程使用来获取锁。
信号量就像类固醇上的Mutexes,它们允许通过提供最大并发访问次数来同时访问。 一旦达到限制,信号量开始阻止任何对资源的进一步访问,直到其中一个调用者释放信号量。
我在DotGNU做了线程和CLR线程支持,我有一些想法…
除非需要跨进程locking,否则应该始终避免使用互斥锁和信号量。 .NET中的这些类是Win32 Mutex和Semaphores的包装,并且相当重(它们需要上下文切换到内核中,这很昂贵 – 特别是如果你的锁不在竞争中)。
正如其他人所提到的,C#locking语句对于Monitor.Enter和Monitor.Exit(存在于try / finally中)是编译器魔术。
监视器有一个简单但强大的信号/等待机制,互斥量不通过Monitor.Pulse / Monitor.Wait方法。 相当于Win32的事件对象通过CreateEvent实际上也是以.NET的forms存在的WaitHandles。 Pulse / Wait模型类似于Unix的pthread_signal和pthread_wait,但速度更快,因为它们在非竞争情况下完全可以是用户模式操作。
Monitor.Pulse / Wait使用简单。 在一个线程中,我们locking一个对象,检查一个标志/状态/属性,如果它不是我们所期望的,调用Monitor.Wait将释放锁,并等待一个脉冲发送。 当等待返回时,我们循环返回并再次检查标志/状态/属性。 在另一个线程中,每当我们改变标志/状态/属性时,我们locking对象,然后调用PulseAll来唤醒任何监听线程。
通常我们希望我们的类是线程安全的,所以我们把锁放在我们的代码中。 但是,我们的课程往往只能由一个线程来使用。 这意味着这些锁不必要地拖慢我们的代码…这是CLR中的聪明优化可以帮助提高性能的地方。
我不确定微软的锁的实现,但在DotGNU和Mono中,锁状态标志存储在每个对象的头部。 .NET(和Java)中的每个对象都可以成为一个锁,因此每个对象都需要在头中支持这个对象。 在DotGNU实现中,有一个标志允许您为每个用作锁的对象使用一个全局哈希表 – 这有利于消除每个对象的4字节开销。 这对于内存来说并不好(特别是对于那些没有大量线程的embedded式系统),但是对性能有很大的影响。
Mono和DotGNU都有效地使用互斥锁来执行locking/等待,但是使用自旋锁样式的比较和交换操作来消除实际执行硬锁的需要,除非确实需要:
你可以看到一个如何在这里实现监视器的例子:
http://cvs.savannah.gnu.org/viewvc/dotgnu-pnet/pnet/engine/lib_monitor.c?revision=1.7&view=markup
另外需要注意的是,在使用stringID识别的共享互斥锁上,它将默认为“本地”互斥锁,并且不会在terminal服务器环境中的会话间共享。
将string标识符前缀为“全局”,以确保对共享系统资源的访问得到适当控制。 在我意识到这一点之前,我只是遇到了一大堆同步通信与SYSTEM帐户下运行的服务的问题。
我会尽量避免“锁()”,“互斥”和“监测”,如果你可以…
在.NET 4中检出新的命名空间System.Collections.Concurrent
它有一些很好的线程安全的集合类
http://msdn.microsoft.com/en-us/library/system.collections.concurrent.aspx
ConcurrentDictionary的岩石! 没有手动locking了我!