一、概述
Lock关键字,确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
Lock关键字属于语法糖,其本质是Threading.Monitor来控制的。
二、原理
对于任何一个对象来说,它在内存中的第一部分放置的是所有方法的地址,第二部分放着一个索引,这个索引指向CLR中的SyncBlock Cache区域中的一个SyncBlock。
当你执行Monitor.Enter(Object)时,如果object的索引值为负数,就从SyncBlock Cache中选取一个SyncBlock,将其地址放在object的索引中。这样就完成了以object为标志的锁定,其他的线程想再次进行Monitor.Enter(object)操作,将获得object的已经为正值的索引,然后就等待。直到索引变为负数,即调用Monitor.Exit(object)将索引变为负数,等待的线程开始执行。
三、特征
- 不能锁定空值
- 不能锁定string类型,整个程序中任何给定字符串都只有一个实例,具有相同内容的字符串都代表着同一个实例。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中与该字符串具有相同内容的字符串。因此,最好锁定不会被暂留的私有或受保护成员
- 锁定的对象是一个程序块的内存边界
- 值类型不能被Lock
- 避免锁定public 类型或不受程序控制的对象;如果该实例可以被公开访问,不受控制的代码也可能会锁定该对象。这可能导致死锁,即两个或更多个线程等待释放同一对象。出于同样的原因,锁定公共数据类型(相比于对象)也可能导致问题
- Unity直接使用Lock机制阻止用户直接在子线程中使用Unity的一些特定属性
四、加锁方式
- 使用对象本身作为锁对象,对写线程进行加锁
- 在写线程中,对象将会被锁定,执行完毕之后释放锁,此时对象已经被更改(因为写入操作引起的更改)
- 使用对象本身作为锁对象,对读线程进行加锁
- 在读线程中,对象将会被锁定,所以读的过程中对象都将保持不变
- 使用System.Object作为锁对象
- 使用其他的引用类型,比如System.Object,此时线程不会访问该类型的任何属性和方法,适用于锁代码块
五、同步块(SyncBlock)
SyncBlock,也称为 Monitor,是在 .NET 平台下用于支持多线程同步的一种机制。它用于实现 C# 中的 lock 关键字和 Monitor 类的功能。
每一个托管对象的头部之前都存在一个Object Header,存储着各种各样的信息,其中就包含SyncBlock的信息。
在多线程编程中,当多个线程同时访问共享资源时,可能会引发竞争条件和数据不一致的问题。SyncBlock 是一种轻量级的同步对象,用于确保对共享资源的互斥访问,从而避免竞争条件和数据不一致性。
当一个线程要进入一个由 Lock 关键字或 Monitor 类保护的代码块时,它会尝试获取 SyncBlock 的所有权。如果 SyncBlock 已被其他线程持有,则当前线程会被阻塞,直到 SyncBlock 可用为止。一旦线程执行完由 lock 关键字或 Monitor 类保护的代码块,它将释放 SyncBlock 的所有权,以便其他线程可以继续访问这个代码块。