代码改变世界

小记《Safe Thread Synchronization -- Jeffrey Richter》

2012-03-27 22:33  Oliver_Zhao  阅读(207)  评论(0编辑  收藏  举报

More details, please click here.

.NET的同步实现

  当创建一个object在托管堆上时,每个object都有2个附加字段。一个字段是方法列表指针(MethodTablePointer)。这个方法列表指针包含了这个类中方法的内存地址。这个指针让我们能够获取在托管堆中的类型信息。实际上,当我们调用System.Object的GetType方法时,这个方法根据方法列表指针来确定object的真实类型。还有一个字段叫做同步块索引(SyncBlockIndex),它是一个32位的整数。

  当一个object被构造出来时,object的同步块索引被初始化为一个负数的值(通常为-1)。当一个方法被调用时,CLR在缓存中存找到一个未被使用的同步块,同时根据这个同步块为同步块索引赋值。换言之,当一个object需要同步字段时,同步块就会跟这个object有一定关系。当不再有超过一个线程访问这个object,这个对象的同步块索引将被重置为一个负值,然后其他对象就可以使用这个同步块。

可以通过上图看到,在CLR Data Structures 区域中,每个类型中有一个系统知道的数据结构;同时,以可以看到同步模块结构。在托管堆中,ObjectA,ObjectB,and ObjectC 被创建。每个对象都有方法列表指针。

使用Monitor操作同步块

  在了解同步块之后,我们来使用System.Threading.Monitor锁或解锁一个对象。下面这个方法用来锁一个对象:

public static void Enter(object obj);

当调用Enter方法,首先要查看对象的同步块索引是否为负值,如果同步块索引为负值,寻找同步块并对对象的同步块索引赋值。如果查看同步块发现已经存在与其相关的类型,我们要检查它是否有其他线程拥有同步块。如果没有线程拥有当前对象,那么调用的线程将拥有这个对象。如果有另外一个线程想要拥有这个对象,则需要等到线程放弃拥有这个线程块。

  如果你想要你的代码更具有防护性,下面的代码能够帮助你:

public static Boolean TryEnter(object obj);

public static Boolean TryEnter(object obj,
int millisecondsTimeout);

public static Boolean TryEnter(object obj,
TimeSpan timeout);

第一个方法与其他两个方法的区别是拥有一个超时的设置。这个时间是指定线程能够等待多长时间。
我们可以使用Exit来释放同步块:

public static void Exit(object obj);

当然,要注意如果调用Exit方法的线程没有对象的同步块,这个Exit方法将会抛出异常。这意味着这里根本不需要释放同步块。

Microsoft方式同步

  首先来看一段代码:

View Code
class Transaction {

// Private field holding the time of
// the last transaction performed
private DateTime timeOfLastTransaction;

public void PerformTransaction() {
// Lock this object
Monitor.Enter(this);

// Perform the transaction...

// Record time of the most recent transaction
timeOfLastTransaction = DateTime.Now;

// Unlock this object
Monitor.Exit(this);
}

// Public read-only property returning
// the time of the last transaction
public DateTime LastTransaction {
get {
// Lock this object
Monitor.Enter(this);

// Save the time of the last transaction
// in a temporary variable
DateTime dt = timeOfLastTransaction;

// Unlock this object
Monitor.Exit(this);

// Return the value in the temporary variable
return(dt);
}
}
}

Lock语句块

View Code
class Transaction {

// Private field holding the time of
// the last transaction performed
private DateTime timeOfLastTransaction;

public void PerformTransaction() {
lock (this) {
// Perform the transaction...

// Record time of the most recent transaction
timeOfLastTransaction = DateTime.Now;
}
}

// Public read-only property returning
// the time of the last transaction
public DateTime LastTransaction {
get {
lock (this) {
// Return the time of the last transaction
return timeOfLastTransaction;
}
}
}
}
View Code
// Regular function
public void SomeMethod() {
// Lock the object
Object oTemp = this;
Monitor.Enter(oTemp);
try {
// Access the object
...
// Unlock the object
}
finally {
Monitor.Exit(oTemp);
}
// Return
}

// Simple function
public void SomeMethod() {
// Lock the object
lock (this) {

// Access the object
...
// Unlock the object
}

// Return
}

静态方法同步

View Code
class Transaction {

// Private field holding the time of
// the last transaction performed
private static DateTime timeOfLastTransaction;

public static void PerformTransaction() {
lock (typeof(Transaction)) {
// Perform the transaction...

// Record time of the most recent transaction
timeOfLastTransaction = DateTime.Now;
}
}

// Public read-only property returning
// the time of the last transaction
public static DateTime LastTransaction {
get {
lock (typeof(Transaction)) {
// Return the time of the last transaction
return timeOfLastTransaction;
}
}
}
}

缺陷

View Code
using System;
using System.Threading;

class App {
static void Main() {
// Construct an instance of the App object
App a = new App();

// This malicious code enters a lock on
// the object but never exits the lock
Monitor.Enter(a);

// For demonstration purposes, let's release the
// root to this object and force a garbage collection
a = null;
GC.Collect();

// For demonstration purposes, wait until all Finalize
// methods have completed their execution - deadlock!
GC.WaitForPendingFinalizers();

// We never get to the line of code below!
Console.WriteLine("Leaving Main");
}

// This is the App type's Finalize method
~App() {
// For demonstration purposes, have the CLR's
// Finalizer thread attempt to lock the object.
// NOTE: Since the Main thread owns the lock,
// the Finalizer thread is deadlocked!
lock (this) {
// Pretend to do something in here...
}
}
}
View Code
class Transaction {

// Private, static Object field
// used purely for synchronization
private static Object objLock = new Object();

// Private field holding the time of
// the last transaction performed
private static DateTime timeOfLastTransaction;

public static void PerformTransaction() {
lock (objLock) {
// Perform the transaction...

// Record time of the most recent transaction
timeOfLastTransaction = DateTime.Now;
}
}

// Public read-only property returning
// the time of the last transaction
public static DateTime LastTransaction {
get {
lock (objLock) {
// Return the time of the last transaction
return timeOfLastTransaction;
}
}
}
}
View Code
class Transaction {

// Private Object field used
// purely for synchronization
private Object objLock = new Object();

// Private field holding the time of
// the last transaction performed
private DateTime timeOfLastTransaction;

public void PerformTransaction() {
lock (objLock) {
// Perform the transaction...

// Record time of the most recent transaction
timeOfLastTransaction = DateTime.Now;
}
}

// Public read-only property returning
// the time of the last transaction
public DateTime LastTransaction {
get {
lock (objLock) {
// Return the time of the last transaction
return timeOfLastTransaction;
}
}
}
}

值类型做标识

View Code
class AnotherType {

// An unboxed Boolean value type
private Boolean flag = false;

public Boolean Flag {
set {
Monitor.Enter(flag); // Boxes flag and locks the object
flag = value; // The actual value is unprotected
Monitor.Exit(flag); // Boxes flag, attempts to unlock
// the object
}
}
}

非装箱的对象没有方法列表指针和同步块索引,这意味着未装箱的值类型不能够拥有同步块与其相关。所以,当一个值类型传入Enter方法或Exit方法后,将执行装箱操作。

View Code
class AnotherType {

// An unboxed Boolean value type
private Boolean flag = false;

// A private Object field used to
// synchronize access to the flag field
private Object flagLock = new Object();

public Boolean Flag {
set {
Monitor.Enter(flagLock);
flag = value;
Monitor.Exit(flagLock);
}
}
}

传入bool值时,编译器会报错:error CS0185: 'bool' is not a reference type as required by the lock statement.
More details, please click here.