有人可以解释以下之间的区别:
锁定(某个对象){}
使用互斥锁
使用信号量
使用监视器
使用其他 .Net 同步类
我就是想不通。在我看来,前两个是一样的?
好问题。我可能错了..让我试试..我的原始答案的修订版#2..有了更多的理解。谢谢你让我阅读:)
锁(对象)
是用于(对象内?)线程同步的 CLR 构造。确保只有一个线程可以获取对象锁的所有权并进入锁定的代码块。其他线程必须等到当前所有者通过退出代码块来放弃锁定。此外,建议您锁定类的私有成员对象。
监视器
lock(obj) 在内部使用 Monitor 实现。你应该更喜欢 lock(obj) 因为它可以防止你像忘记清理过程一样搞砸。如果你愿意的话,它'白痴''Monitor 构造。使用 Monitor 通常比互斥锁更受欢迎,因为 Monitor 是专门为 .NET Framework 设计的,因此可以更好地利用资源。
使用锁或监视器对于防止同时执行对线程敏感的代码块很有用,但这些构造不允许一个线程将事件传递给另一个线程。这需要同步事件,它们是具有两种状态之一的对象,有信号和无信号,可用于激活和挂起线程。互斥量、信号量是操作系统级别的概念。例如,使用命名互斥锁,您可以跨多个(托管)exe 进行同步(确保您的应用程序只有一个实例在机器上运行。)
互斥体:
然而,与监视器不同的是,互斥锁可用于跨进程同步线程。当用于进程间同步时,互斥锁被称为命名互斥锁,因为它要在另一个应用程序中使用,因此不能通过全局或静态变量共享。必须给它一个名称,以便两个应用程序都可以访问同一个互斥对象。相反,Mutex 类是 Win32 构造的包装器。虽然它比监视器更强大,但互斥体需要的互操作转换比 Monitor 类所需的计算成本更高。
Semaphores(伤脑筋)。
使用 Semaphore 类来控制对资源池的访问。线程通过调用WaitOne方法进入信号量,该方法继承自WaitHandle类,并通过调用Release方法释放信号量。每次线程进入信号量时,信号量上的计数都会减少,而当线程释放信号量时,计数会增加。当计数为零时,后续请求会阻塞,直到其他线程释放信号量。当所有线程都释放信号量时,计数为创建信号量时指定的最大值。一个线程可以多次进入信号量。信号量类不会在 WaitOne 或 Release 上强制执行线程标识。程序员有责任不搞砸。信号量有两种类型:本地信号量和命名系统信号量。如果使用接受名称的构造函数创建 Semaphore 对象,则它与该名称的操作系统信号量相关联。命名系统信号量在整个操作系统中都是可见的,可用于同步进程的活动。本地信号量仅存在于您的进程中。它可以被进程中任何引用本地信号量对象的线程使用。每个 Semaphore 对象都是一个单独的本地信号量。
THE PAGE TO READ - Thread Synchronization (C#)
重新“使用其他 .Net 同步类”-您应该了解的其他一些:
ReaderWriterLock - 允许多个读取器或单个写入器(不是同时)
ReaderWriterLockSlim - 和上面一样,开销更低
ManualResetEvent - 打开时允许代码通过的门
AutoResetEvent - 同上,但打开后自动关闭
CCR/TPL(Parallel Extensions CTP)中还有更多(低开销)锁定结构 - 但 IIRC,这些将在 .NET 4.0 中提供
正如 ECMA 中所述,您可以从 Reflected 方法中观察到 lock 语句基本上等同于
object obj = x;
System.Threading.Monitor.Enter(obj);
try {
…
}
finally {
System.Threading.Monitor.Exit(obj);
}
从上述示例中,我们看到监视器可以锁定对象。
当您需要进程间同步时,互斥锁很有用,因为它们可以锁定字符串标识符。不同的进程可以使用相同的字符串标识符来获取锁。
信号量就像类固醇上的互斥锁,它们通过提供最大并发访问计数来允许并发访问。一旦达到限制,信号量就会开始阻止对资源的任何进一步访问,直到其中一个调用者释放信号量。
我为 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 它将释放锁定并等待直到发送一个脉冲。当等待返回时,我们循环返回并再次检查标志/状态/属性。在另一个线程中,每当我们更改标志/状态/属性时,我们都会锁定对象,然后调用 PulseAll 来唤醒任何侦听线程。
通常我们希望我们的类是线程安全的,所以我们在代码中加了锁。但是,通常情况下,我们的类只会被一个线程使用。这意味着锁不必要地减慢了我们的代码......这就是 CLR 中的巧妙优化可以帮助提高性能的地方。
我不确定 Microsoft 的锁实现,但在 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
锁定您使用字符串 ID 标识的任何共享互斥锁的另一个警告是,它将默认为“本地\”互斥锁,并且不会在终端服务器环境中的会话之间共享。
使用“Global\”为您的字符串标识符添加前缀,以确保正确控制对共享系统资源的访问。在我意识到这一点之前,我刚刚遇到了一大堆与在 SYSTEM 帐户下运行的服务同步通信的问题。
如果可以的话,我会尽量避免使用“lock()”、“Mutex”和“Monitor”...
查看 .NET 4 中的新命名空间 System.Collections.Concurrent 它有一些不错的线程安全集合类
http://msdn.microsoft.com/en-us/library/system.collections.concurrent.aspx
并发字典摇滚!我不再需要手动锁定!
在大多数情况下,您不应该使用锁(=Monitors)或互斥体/信号量。它们都在同步操作时阻塞等待线程。所以它们只适用于非常小的操作。
而且您绝对不应该使用 System.Collections.Concurrent
类 - 它们不支持具有多个集合的事务,并且还使用阻塞同步。
令人惊讶的是,.NET 没有有效的非阻塞同步机制。
我在 C# 上实现了来自 GCD(Objc/Swift
世界)的 serial queue - 非常轻量级、非阻塞同步工具,它使用线程池和测试。
在大多数情况下,这是同步任何东西的最佳方式——从数据库访问(hello sqlite)到业务逻辑。
Monitor
不允许通信是不正确的;您仍然可以使用Monitor
Pulse
等