同步用于多线程的数据

当多个线程可以调用单个对象的属性和方法时,必须同步这些调用。 否则,一个线程可能会中断另一个线程正在执行的操作,并且该对象可能处于无效状态。 其成员不受此类中断保护的类称为线程安全。

.NET提供了多个策略来同步对实例和静态成员的访问:

  • 同步的代码区域。 可以使用 Monitor 类,或使用编译器对此类的支持,仅同步需要同步的代码块,从而提高性能。

  • 手动同步。 可以使用.NET类库提供的同步对象。 请参阅 同步基元概述,其中包括类 Monitor 的讨论。

  • 同步上下文。 仅对于 .NET Framework 应用程序,可以使用 SynchronizationAttributeContextBoundObject 对象启用简单的自动同步。

  • 命名空间中的 System.Collections.Concurrent 集合类。 这些类提供内置同步的添加和删除操作。 有关详细信息,请参阅 Thread-Safe 集合

公共语言运行时提供了一个线程模型,其中类属于许多类别,这些类别可以根据要求以各种不同方式同步。 下表显示了为具有给定同步类别的字段和方法提供哪些同步支持。

Category 全局字段 静态字段 静态方法 实例字段 实例方法 特定代码块
无同步
同步上下文 是的 是的
同步的代码区域 仅当标记为 仅当标记时 仅当已标记时
手动同步 Manual Manual Manual Manual Manual Manual

无同步

这是对象的默认值。 任何线程随时都可以访问任何方法或字段。 一次只能有一个线程访问这些对象。

手动同步

.NET类库提供了许多用于同步线程的类。 请参阅 同步基元概述

同步的代码区域

可以使用 Monitor 类或编译器关键字来同步代码块、实例方法和静态方法。 不支持同步的静态字段。

Visual Basic和 C# 都支持使用特定语言关键字、lockC# 中的语句或SyncLockVisual Basic中的语句标记代码块。 当代码由线程执行时,将尝试获取锁。 如果锁已被另一个线程获取,则线程会阻塞,直到锁变为可用为止。 当线程退出同步的代码块时,无论线程如何退出块,该锁都释放。

Note

在 .NET 9 和 C# 13 或更高版本中,如果lock目标是专用System.Threading.Lock实例,则lock语句用于Lock.EnterScope()创建同步区域。

Visual Basic 不支持在SyncLock中使用System.Threading.Lock,因此请继续将SyncLock与专用的私有引用类型配合使用。

如果 lock 目标不是 Lock 实例,并且对于所有 SyncLock 的用法,编译器使用 Monitor.EnterMonitor.Exit。 由于该实现,你仍然可以在同步区域中使用其他 Monitor 方法。

还可以用值为 MethodImplOptions.SynchronizedMethodImplAttribute 修饰方法,其效果与使用 Monitor 或某个编译器关键字来锁定整个方法体相同。

Thread.Interrupt 可用于将线程从阻塞操作中中断,例如等待访问同步的代码区域。 Thread.Interrupt 还用于将线程从操作中分解出来,例如 Thread.Sleep

Important

不要对类型加锁——也就是说,不要对 C# 中的 typeof(MyType)、Visual Basic 中的 GetType(MyType) 或 C++ 中的 MyType::typeid 加锁——以保护 static 方法(在 Visual Basic 中为 Shared 方法)。 请改用专用静态对象。 同样,不要在 C# 中使用 this(在 Visual Basic 中为 Me)来锁定实例方法。 请改用专用对象。 类或实例可能会被你自己的代码以外的代码锁定,从而导致死锁或性能问题。

编译器支持

Visual Basic和 C# 都支持使用Monitor.EnterMonitor.Exit锁定对象的语言关键字。 Visual Basic支持 SyncLock 语句;C# 支持 lock 语句。

在这两种情况下,如果代码块中引发了异常,则由 lockSyncLock 获取的锁会自动释放。 C# 和 Visual Basic 编译器会生成一个 try/finally 块,其中在 try 开始处调用 Monitor.Enter,并在 finally 块中调用 Monitor.Exit。 如果 lockSyncLock 块内部引发了异常,则会运行 finally 处理程序,从而允许执行任何清除工作。

同步上下文

仅在 .NET Framework 应用程序中,可以在任何 ContextBoundObject 上使用 SynchronizationAttribute 来同步所有实例方法和字段。 同一上下文域中的所有对象共享相同的锁。 允许多个线程访问方法和字段,但每次只允许一个线程。

另见