当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《C#中各种线程同步选项有什么区别?》经验,为你挑选了6个好方法。

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

锁定(someobject){}

使用Mutex

使用信号量

使用Monitor

使用其他.Net同步类

我只是想不出来.在我看来前两个是一样的?



1> Gishu..:

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

锁(OBJ)

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

显示器

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

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

互斥:

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

信号量(伤害了我的大脑).

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

阅读页面 - 线程同步(C#)


您声称"监视器"不允许通信不正确; 你仍然可以使用`Monitor`来"脉冲"等
查看Semaphores的替代说明 - http://stackoverflow.com/a/40473/968003.把信号量想象成夜总会的保镖.俱乐部一次允许有一定数量的人.如果俱乐部已经满员,则不允许任何人进入,但只要一个人离开另一个人就可以进入.

2> Marc Gravell..:

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

ReaderWriterLock - 允许多个读者或单个作者(不是同时)

ReaderWriterLockSlim - 如上所述,降低了开销

ManualResetEvent - 允许代码在打开时过去的门

AutoResetEvent - 如上所述,但一旦打开就会自动关闭

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



3> arul..:

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

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

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

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

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


这个语法糖在C#4中略有变化.查看http://blogs.msdn.com/ericlippert/archive/2009/03/06/locks-and-exceptions-do-not-mix.aspx

4> tumtumtum..:

我在DotGNU中为线程做了类和CLR支持,我有一些想法......

除非您需要跨进程锁定,否则应始终避免使用互斥锁和信号量..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都有效地使用互斥锁执行锁定/等待,但使用自旋锁式比较和交换操作,除非确实需要,否则无需实际执行硬锁:

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

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



5> nvuono..:

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

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



6> Peter Gfader..:

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

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

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

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


避免锁定但使用监视器?为什么?
@mafutrct谢谢,我更新了我的条目以使其更清晰
推荐阅读
农大军乐团_697
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有