现在的位置: 首页 > 综合 > 正文

第二部分:线程同步基础3

2014年03月17日 ⁄ 综合 ⁄ 共 3367字 ⁄ 字号 评论关闭
文章目录
2008-05-11 01:09

同步环境

与手工的锁定相比,你可以进行说明性的锁定,用衍生自ContextBoundObject 并标以Synchronization特性的类,它告诉CLR自动执行锁操作,看这个例子:

using System;
using System.Threading;
using System.Runtime.Remoting.Contexts;
 
[Synchronization]
public class AutoLock : ContextBoundObject {
  public void Demo() {
     Console.Write ("Start...");
     Thread.Sleep (1000);            // 我们不能抢占到这
     Console.WriteLine ("end");      // 感谢自动锁!
  }
}
 
public class Test {
  public static void Main() {
     AutoLock safeInstance = new AutoLock();
     new Thread (safeInstance.Demo).Start();      // 并发地
     new Thread (safeInstance.Demo).Start();      // 调用Demo
     safeInstance.Demo();                         // 方法3次
  }
}

Start... end
Start... end
Start... end

CLR确保了同一时刻只有一个线程可以执行 safeInstance中的代码。它创建了一个同步对象来完成工作,并在每次调用safeInstance的方法和属性时在其周围只能够行锁定。锁的作用域——这里是safeInstance对象,被称为同步环境

那么,它是如何工作的呢?Synchronization特性的命名空间:System.Runtime.Remoting.Contexts是一个线索。ContextBoundObject可以被认为是一个“远程”对象,这意味着所有方法的调用是被监听的。让这个监听称为可能,就像我们的例子AutoLock,CLR自动的返回了一个具有相同方法和属性的AutoLock对象的代理对象,它扮演着一个中间者的角色。总的来说,监听在每个方法调用时增加了数微秒的时间。

自动同步不能用于静态类型的成员,和非继承自 ContextBoundObject(例如:Windows Form)的类。

锁在内部以相同的方式运作,你可能期待下面的例子与之前的有一样的结果:

[Synchronization]
public class AutoLock : ContextBoundObject {
  public void Demo() {
     Console.Write ("Start...");
     Thread.Sleep (1000);
     Console.WriteLine ("end");
  }
 
  public void Test() {
     new Thread (Demo).Start();
     new Thread (Demo).Start();
     new Thread (Demo).Start();
     Console.ReadLine();
  }
 
  public static void Main() {
     new AutoLock().Test();
  }
}

(注意我们放入了Console.ReadLine语句。)因为在同一时刻的同一个此类的对象中只有一个线程可以执行代码,三个新线程将保持被阻止在Demo 放中,直到Test 方法完成,需要等待ReadLine来完成。因此我们以与之前的有相同结果而告终,但是只有在按完Enter键之后。这是一个线程安全的手段,差不多足够能在类中排除任何有用的多线程!

此外,我们仍未解决之前描述的一个问题:如果AutoLock是一个集合类,比如说,我们仍然需要一个像下面一样的锁,假设运行在另一个类里:

if (safeInstance.Count > 0) safeInstance.RemoveAt (0);

除非使用这代码的类本身是一个同步的ContextBoundObject

同步环境可以扩展到超过一个单独对象的区域。默认地,如果一个同步对象被实例化从在另一段代码之内,它们拥有共享相同的同步环境(换言之,一个大锁!)。这个行为可以由改变Synchronization特性的构造器的参数来指定。使用SynchronizationAttribute类定义的常量之一:

常量

含义

NOT_SUPPORTED

相当于不使用同步特性

SUPPORTED

如果从另一个同步对象被实例化,则合并已存在的同步环境,否则只剩下非同步。

REQUIRED
(默认)

如果从另一个同步对象被实例化,则合并已存在的同步环境,否则创建一个新的同步环境。

REQUIRES_NEW

总是创建新的同步环境

所以如果SynchronizedA的实例被实例化于SynchronizedB的对象中,如果SynchronizedB像下面这样声明的话,它们将有分离的同步环境:

[Synchronization (SynchronizationAttribute.REQUIRES_NEW)]
public class SynchronizedB : ContextBoundObject { ...

越大的同步环境越容易管理,但是减少机会对有用的并发。换个有限的角度,分离的同步环境会造成死锁,看这个例子:

[Synchronization]
public class Deadlock : ContextBoundObject {
  public DeadLock Other;
  public void Demo() { Thread.Sleep (1000); Other.Hello(); }
  void Hello()        { Console.WriteLine ("hello");          }
}
 
public class Test {
  static void Main() {
     Deadlock dead1 = new Deadlock();
     Deadlock dead2 = new Deadlock();
     dead1.Other = dead2;
    dead2.Other = dead1;
     new Thread (dead1.Demo).Start();
     dead2.Demo();
  }
}

因为每个Deadlock的实例在Test内创建——一个非同步类,每个实例将有它自己的同步环境,因此,有它自己的锁。当它们彼此调用的时候,不会花太多时间就会死锁(确切的说是一秒!)。如果DeadlockTest是由不同开发团队来写的,这个问题特别容易发生。别指望Test知道如何产生的错误,更别指望他们来解决它了。在死锁显而易见的情况下,这与使用明确的锁的方式形成鲜明的对比。

可重入性问题

线程安全方法有时候也被称为可重入式的,因为在它执行的时候可以被抢占部分线路,在另外的线程调用也不会带来坏效果。从某个意义上讲,术语线程安全可重入式的是同义的或者是贴义的。

不过在自动锁方式上,如果Synchronization的参数可重入式的 为true的话,可重入性会有潜在的问题:

[Synchronization(true)]

同步环境的锁在执行离开上下文时被临时地释放。在之前的例子里,这将能预防死锁的发生;很明显很需要这样的功能。然而一个副作用是,在这期间,任何线程都可以自由的调用在目标对象(“重进入”的同步上下文)的上任何方法,而非常复杂的多线程中试图避免不释放资源是排在首位的。这就是可重入性的问题。

因为[Synchronization(true)]作用于类级别,这特性打开了对于非上下文的方法访问,由于可重入性问题使它们混入类的调用。

虽然可重入性是危险的,但有些时候它是不错的选择。比如:设想一个在其内部实现多线程同步的类,将逻辑工作线程运行在不同的语境中。在没有可重入性问题的情况下,工作线程在它们彼此之间或目标对象之间可能被无理地阻碍。

这凸显了自动同步的一个基本弱点:超过适用的大范围的锁定带来了其它情况没有带来的巨大麻烦。这些困难:死锁,可重入性问题和被阉割的并发,使另一个更简单的方案——手动的锁定变得更为合适。

抱歉!评论已关闭.