.NET Framework - lock (同步、异步、并行、并发、进程、线程)

创建时间:
2014-03-01 18:33
最近更新:
2018-10-10 11:24

Brief

  • lock 是 C# 中同步访问共享资源的首选方式,语法为 lock(expression){ statement_block },其中 expression 代表 被锁的对象,statement_block 代表 互斥代码,这段代码在一个时刻内只可能被一个线程执行。该语法有三层意思: 1. expressionlock 了吗? 没有则由我来 lock,否则一直等待,直至 expression 被释放。2. lock 以后在执行 statement_block 的期间其他线程不能调用 statement_block,也不能使用 expression。3. 执行完 statement_block 之后释放 expression,并且 statement_block 可以被其他线程访问。
  • C# 中同步访问共享资源的另一种方式是 [Synchronization] 特性,它比 lock 编码更少,但有可能会影响性能。
  • lock 语句 获取 expression 对象的互斥锁,执行 statement_block 后释放该锁。确保 statement_block 完成运行,而不会被其他线程中断。
  • expression 必须是一个引用类型的值。如果是一个值类型的值,则会导致 编译时错误。
  • expressionstatement_block 都是线程安全的。
  • lock 关键字将语句块标记为关键部分,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。
  • lock 关键字可确保当一个线程位于代码的关键部分时,另一个线程不会进入该关键部分。如果其他线程尝试进入锁定的代码,则它将一直等待 (即被阻止),直到该对象被释放。
  • lock 关键字可用于确保代码块运行完成,且不会被其他线程中断。这是通过在代码块的持续时间内获得给定对象的互斥锁来实现的。
  • 在多线程中,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数。这可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果。
  • 多线程时效率最高的方式是异步,即各个线程同时运行,其间互不依赖与等待。但当不同的线程都需要访问某个资源的时候,就需要同步机制 使该资源在同一时刻只能被一个线程操作,以确保每个操作的原子性。

lock 的 底层实现

MSDN

监视器 - Thread Synchronization / 线程同步 (C#) - 使用 lock 关键字通常优先于直接使用 Monitor 类,因为 lock 更简洁,而且即使在受保护的代码引发异常时,lock 也可确保基础监视器被释放。这通过由 finally 关键字完成,该关键字执行其关联的代码块,不会受是否引发异常的影响。

lock 关键字
在块的开始处调用 System.Threading.Monitor.Enter 方法
在块的结尾处调用 System.Threading.Monitor.Exit 方法
如果 Interrupt 中断正在等待输入 lock 语句的线程,将引发 ThreadInterruptedException。

以下两段代码完全等效:

lock(expression) {
    statement_block //同步代码
}
System.Threading.Monitor.Enter(expression);
try {
    statement_block
}
catch(Exception e) { }
finally {
    System.Threading.Monitor.Exit(expression); //解除锁定
}

Resource

  1. C# 线程同步技术之 Monitor - lock 语句 被编译为 对 System.Threading.Monitor 类的 EnterExit 方法 的调用,Exit 方法置于 finally 块中,详见下方代码。Monitor 类不仅可以完全取代 lock 语句 (如果只使用 lock 语句本身的功能,最好还是直接用 lock 语句),还可以使用 TryEntry 方法设置锁定超时,单位是毫秒,用于避免死锁。如果 TryEntry 方法的超时时间为 System.Threading.Timeout.InfiniteTryEntry 方法就相当于 Entry 方法。如果超时时间为 0,不管是否解锁,TryEntry 方法都会立即返回。

lock 的 最佳实践

MSDN 准则

通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。常见的结构 lock(this)lock(typeof(MyType))lock("myLock") 违反此准则:

  • 如果可以公开访问实例,将出现 lock (this) 问题。
  • 如果可以公开访问 lock(typeof(MyType)),将出现 MyType 问题。
  • 由于进程中使用同一字符串的任何其他代码都将共享同一个锁,因此出现 lock("myLock") 问题。

最佳做法是定义 private 对象来进行锁定,或者定义 private static 对象变量来保护所有实例所共有的数据。

lock 语句的正文中不能使用 await 关键字。

以上内容摘自:

  1. lock Statement (C# reference) / "锁定" 语句 (C# 参考)
  2. lock Statement (C# reference) / "锁定" 语句 (C# 参考)

Tony 常用代码

//尽可能 lock 简单对象:
private static readonly object syncRoot_xxx = new Object();

Resource

  1. C# 中 lock 死锁实例教程

SpinLock

MSDN

  1. Mutexes
  2. SpinLock

Resource

  1. NET 4.0 推出的 自旋锁 SpinLock,与 lock、Monitor
  2. SpinLock 与 lock 效率对比测试
  3. 自旋锁 spin_lock
  4. C# SpinLock 实现 (源码分析?)
  5. C# 多线程编程中的锁系统 (四): 自旋锁

Linux

  1. spinlock 的实现 (Linux)

Resource - MSDN

  1. lock 关键字 - Thread Synchronization / 线程同步 (C#)
  2. lock Statement (C# reference) / "锁定" 语句 (C# 参考)
  3. lock Statement (C# reference) / "锁定" 语句 (C# 参考)
  1. System.Threading.Monitor.Enter 方法
  2. System.Threading.Monitor.Exit 方法

Resource

  1. C# 多线程中 lock 的用法