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

有人能解释一下:

  • Lock (some object){}
  • 使用互斥技术
  • 使用信号量
  • 使用监视器
  • 使用其他.Net 同步类

我就是想不明白,在我看来前两个是一样的?

40486 次浏览

问得好。也许我错了。.让我试试。.我的设计答案的第二修订版。.多一点理解。谢谢你让我读:)

Lock (obj)

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

监视器

  • Lock (obj)在内部使用 Monitor 实现。您应该更喜欢 lock (obj) ,因为它可以防止您像忘记清理过程一样出错。如果你愿意的话,这是监视器的防白痴构造。
    使用 Monitor 通常优于互斥锁,因为监视器是专门为。NET 框架,从而更好地利用资源。

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

互斥:

  • 然而,与监视器不同的是,互斥体可用于跨进程同步线程。当用于进程间同步时,互斥锁被称为 叫做互斥体,因为它将在另一个应用程序中使用,因此它不能通过全局变量或静态变量共享。必须给它一个名称,这样两个应用程序都可以访问同一个互斥对象。 相比之下,Mutex 类是 Win32构造的包装器。虽然互斥锁比监视器更强大,但它需要的互操作转换比 Monitor 类所需要的转换在计算上更加昂贵。

信号灯伤害了我的大脑。

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

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

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

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

从上面的例子中,我们可以看到 Monitor 可以锁定对象。

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

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

关于“使用其他.Net 同步类”——其他一些你应该知道的:

在 CCR/TPL (并行扩展 CTP)中还有更多(低开销)的锁定构造-但是 IIRC,这些将在。NET 4.0

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

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

我会尽量避免“锁()”,“互斥”和“监视器”,如果你可以..。

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

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

并行字典加冰! 我再也不用手动锁定了!

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

除非您需要跨进程锁,否则应该始终避免使用 Mutex & Semaphores。这些课程。NET 是围绕 Win32互斥和信号量的包装器,并且相当重量级(它们需要将上下文切换到内核,这是昂贵的——特别是如果您的锁不在争用范围之内)。

正如前面提到的,C # lock 语句是 Monitor 的编译魔术。输入并监视。退出(存在于 try/finally 中)。

显示器有一个简单但强大的信号/等待机制,互斥体没有通过显示器。脉搏/监视器。等待方法。Win32等价物是通过 CreateEvent 的事件对象,它实际上也存在于。作为 WaitHandles。Pulse/Wait 模型类似于 Unix 的 pthread _ information 和 pthread _ Wait,但是速度更快,因为它们在非竞争情况下可以完全是用户模式的操作。

监视器。脉搏/等待使用起来很简单。在一个线程中,我们锁定一个对象,检查标志/状态/属性,如果它不是我们所期望的,则调用 Monitor。等待,这将释放锁和等待,直到一个脉冲发送。当等待返回时,我们循环返回并再次检查标志/状态/属性。在另一个线程中,每当我们更改标志/状态/属性时,我们都会锁定该对象,然后调用 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

在大多数情况下,不应该使用锁(= Monitor)或互斥量/信号量。它们都会在同步操作时阻塞等待的线程。因此,它们只适用于非常小的操作。

而且您肯定使用 不应该使用 System.Collections.Concurrent类-它们不支持具有多个集合的事务,并且还使用阻塞同步。

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

我在 C # 上从 GCD (Objc/Swift world)实现了 串行队列——非常轻量级,没有阻塞使用线程池的同步工具,带有测试。

在大多数情况下,这是同步任何事情的最佳方式——从数据库访问(hello sqlite)到业务逻辑。