同步基元概述

.NET 提供了一系列可用于同步对共享资源或协调线程交互的访问的类型。

重要

使用相同的同步基元实例保护对共享资源的访问。 如果使用不同的同步基元实例保护同一资源,则将避开同步基元提供的保护。

WaitHandle 类和轻量同步类型

多个 .NET 同步基元派生自 System.Threading.WaitHandle 类,该类会封装本机操作系统同步句柄并将信号机制用于线程交互。 这些类包括:

在 .NET Framework 中,由于 WaitHandle 派生自 System.MarshalByRefObject,因此,这些类型可用于跨应用程序域边界同步线程的活动。

在 .NET Framework、.NET Core 和 .NET 5+ 中,其中的一些类型可以表示已命名的系统同步句柄,这些句柄在整个操作系统中都可见并可用于进程间同步:

有关详细信息,请参阅 WaitHandle API 参考。

轻量同步类型不依赖于基础操作系统句柄,通常会提供更好的性能。 但是,它们不能用于进程间同步。 将这些类型用于一个应用程序中的线程同步。

其中的一些类型是派生自 WaitHandle 的类型的替代项。 例如,SemaphoreSlimSemaphore 的轻量替代项。

同步对共享资源的访问

.NET 提供了一系列用于控制多个线程对共享资源的访问的同步基元。

Monitor 类

System.Threading.Monitor 类通过获取或释放用于标识资源的对象上的 lock 来授予对共享资源的相互独占访问权限。 持有 lock 时,持有 lock 的线程可以再次获取并释放 lock。 阻止任何其他线程获取 lock,Monitor.Enter 方法等待释放 lock。 Enter 方法可获取释放的 lock。 还可以使用 Monitor.TryEnter 方法指定线程尝试获取 lock 的持续时间。 由于 Monitor 类具有线程关联,因此获取了 lock 的线程必须通过调用 Monitor.Exit 方法来释放 lock。

可以通过使用 Monitor.WaitMonitor.PulseMonitor.PulseAll 方法来协调用于获取同一对象上的 lock 的线程的交互。

有关详细信息,请参阅 Monitor API 参考。

注意

使用 C# 中的 lock 语句和 Visual Basic 中的 SyncLock 语句同步对共享资源的访问,而不是直接使用 Monitor 类。 这些语句是使用 EnterExit 方法进行实现,并使用 try…finally 块来确保获取的 lock 已始终释放。

Mutex 类

System.Threading.Mutex 类(与 Monitor 类似),授予对共享资源的独占访问权限。 使用 Mutex.WaitOne 方法重载之一请求 mutex 的所有权。 Mutex(与 Monitor 类似)具有线程关联,并且已获取 mutex 的线程必须通过调用 Mutex.ReleaseMutex 方法来释放它。

Mutex 类(与 Monitor 不同)可用于进程间同步。 为此,请使用命名 mutex,它在整个操作系统中都可见。 若要创建命名 mutex 实例,请使用指定了名称的 Mutex 构造函数。 还可以调用 Mutex.OpenExisting 方法来打开现有的命名系统 mutex。

有关详细信息,请参阅 Mutex 一文和 Mutex API 参考。

SpinLock 结构

System.Threading.SpinLock 结构(类似于 Monitor),基于 lock 的可用性授予对共享资源的独占访问权限。 当 SpinLock 尝试获取不可用的 lock 时,将在反复检查的循环中等待,直到 lock 变为可用。

有关使用 SpinLock 的优缺点的详细信息,请参阅 SpinLock 一文和 SpinLock API 参考。

ReaderWriterLockSlim 类

System.Threading.ReaderWriterLockSlim 类授予对共享资源的独占访问权限以便进行写入,并允许多个线程同时访问资源以便进行读取。 你可能想要使用 ReaderWriterLockSlim 同步对支持线程安全读取操作,但需要独占访问权限才能执行写入操作的共享数据结构的访问。 当某个线程请求独占访问时(例如,通过调用 ReaderWriterLockSlim.EnterWriteLock 方法),后续读取器和编写器请求将被阻止,直到所有现有读取器均已退出 lock,并且编写器已进入并退出 lock。

有关详细信息,请参阅 ReaderWriterLockSlim API 参考。

Semaphore 和 SemaphoreSlim 类

System.Threading.SemaphoreSystem.Threading.SemaphoreSlim 限制可同时访问某一共享资源或资源池的线程数。 请求资源的其他线程将等待,直到任何线程释放信号量。 由于信号量没有线程关联,因此一个线程可以获取信号量,而另一个线程可以释放它。

SemaphoreSlimSemaphore 的轻量替代项,并且只能在单个流程边界内用于同步。

在 Windows 上,可以将 Semaphore 用于进程间同步。 为此,通过使用指定了名称或 Semaphore.OpenExisting 方法的 Semaphore 构造函数之一来创建表示指定了已命名系统信号量的 Semaphore 实例。 SemaphoreSlim 不支持已命名系统信号量。

有关详细信息,请参阅 Semaphore 和 SemaphoreSlim 一文以及 SemaphoreSemaphoreSlim API 参考。

线程交互或信号

线程交互(或线程信号)表示线程必须等待来自一个或多个线程的通知或信号才能继续。 例如,如果线程 A 调用线程 B 的 Thread.Join 方法,则线程 A 将被阻止,直到完成线程 B。 前面部分中所述的同步基元提供不同的信号机制:通过释放 lock,一个线程通知另一个线程可以通过获取 lock 来继续。

本部分介绍 .NET 提供的其他信号构造。

EventWaitHandle、AutoResetEvent、ManualResetEvent 和 ManualResetEventSlim 类

System.Threading.EventWaitHandle 类表示一个线程同步事件。

同步事件可以处于未发出信号状态或已发出信号状态。 当事件的状态为未发出信号时,调用了事件的 WaitOne 重载的线程会被阻止,直到事件处于已发出信号状态。 EventWaitHandle.Set 方法可将事件的状态设置为已发出信号。

已发出信号的 EventWaitHandle 的行为取决于其重置模式:

在 Windows 上,可以将 EventWaitHandle 用于进程间同步。 为此,通过使用指定了名称或 EventWaitHandle.OpenExisting 方法的 EventWaitHandle 构造函数之一来创建表示指定了已命名系统信号量的 EventWaitHandle 实例。

有关详细信息,请参阅文章 EventWaitHandle。 对于 API 参考,请参阅 EventWaitHandleAutoResetEventManualResetEventManualResetEventSlim

CountdownEvent 类

System.Threading.CountdownEvent 类表示当其计数为零时将被设置的事件。 当 CountdownEvent.CurrentCount 大于零时,调用了 CountdownEvent.Wait 的线程会被阻止。 调用 CountdownEvent.Signal 会递减事件的计数。

与可用于通过来自一个线程的信号取消阻止多个线程的 ManualResetEventManualResetEventSlim 相反,你可以使用 CountdownEvent 通过来自多个线程的信号取消阻止一个或多个线程。

有关详细信息,请参阅 CountdownEvent 一文和 CountdownEvent API 参考。

Barrier 类

System.Threading.Barrier 类表示线程执行屏障。 调用了 Barrier.SignalAndWait 方法的线程发出信号,它已到达屏障并等待其他参与线程到达屏障。 当所有参与线程到达屏障时,它们将继续前进,屏障将进行重置,使之再次可用。

在继续执行下一个计算阶段之前,当一个或多个线程需要其他线程的结果时,可以使用 Barrier

有关详细信息,请参阅 Barrier 一文和 Barrier API 参考。

Interlocked 类

System.Threading.Interlocked 类提供了可对变量执行简单原子操作的静态方法。 这些原子操作包括添加、递增和递减、交换、取决于比较的条件交换以及读取 64 位整数值的操作。

有关详细信息,请参阅 Interlocked API 参考。

SpinWait 结构

System.Threading.SpinWait 结构为基于自旋的等待提供支持。 如果线程必须等待事件收到信号或必须满足某种条件,但实际等待时间应短于使用等待句柄或以其他方式阻止线程所需的等待时间,你可能想要使用该结构。 通过使用 SpinWait,你可以指定等待期间要旋转的一小段时间,且只在特定时间不满足条件时让行(例如,通过等待或休眠)。

有关详细信息,请参阅 SpinWait 一文和 SpinWait API 参考。

请参阅