raindust

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

线程同步与异步的区别(个人理解)
异步线程与同步线程模型最大的区别就在于共享数据的访问。
异步线程线程之间数据并不冲突,也就是说不存在共享数据,因此在访问时线程之间不需要了解彼此资源的状态。异步操作的重点主要在于如何在异步线程完成任务后第一时间执行其他任务(释放资源也是一种任务)。
而同步线程之间需要协调线程间共享资源的使用和释放,因此锁机制显得尤为重要。

线程安全
如果是在构建一个可重用的类,需要确保所有类型的静态成员都是线程安全的,从而确保多个线程同时调用类型的静态方法时,类型的静态状态不会被破坏。为了实现这一点,必须确保静态方法是线程安全的,因为类的用户不可能编写正确的代码来实现这一点。
另一方面,可重用的类库不必确保所有类型的实例方法都是线程安全的。因为对方法增加线程安全会使方法变得较慢,而且大多数对象都是被一个线程使用(即不存在多个线程同时访问对象的情况,因此也就不必考虑线程安全)。
另外,当代码创建对象时,没有其他代码可以访问这个对象,除非该对象的引用以某种方式传递给周围的其他代码。任何分发对象引用的代码同样也应分发一个线程同步构造,以便于其他线程执行的代码可以以线程安全的方法访问对象。

Micorsoft确保所有FrameWork类库FCL中的类型的静态方法都是线程安全的,但不保证类型的实例方法是线程安全的。

易失
由于执行一次内存写入操作时,修改的是CPU的高速缓存中的值,然后在未来某个时间刷新内存中的值。这对于单CPU以及超线程都是没有影响的,但如果在多核CPU的电脑上,则要考虑高速缓存的影响。因为每一个CPU都有自己的高速缓存,它们与内存之间某个时刻对于同一数据不一致的情况还是有可能的。

对于上述情况可以被称作“易失”。
System.Threading.Thread类提供了若干版本的VolatileRead和VolatileWrite版本,可以用于从高速缓存向内存刷新。
也可以通过volatile关键字来实现,该关键字支持除double之外的大部分值类型以及由这些类型实现的枚举类型,以及IntPtr结构等。
不过不推荐使用上述两种方式,而应该尽量使用CLR提供的锁机制或者其他较高层次的线程同步构造。

互锁方法
当多个线程访问共享数据时,必须以线程安全的方式访问该数据。
到目前为止,以线程安全的方式操作数据的最快方法是互锁方法系列。这些方法相较于线程同步构造相当快,且这些方法非常容易使用。
这些方法的不利方面就是他们实现的对象有限。

线程同步的实现方法:
线程的异步特性意味着必须协调对资源(如文件句柄、网络连接和内存)的访问。否则,两个或更多的线程可能在同一时间访问相同的资源,而每个线程都不知道其他线程的操作。结果将产生不可预知的数据损坏。
1.对于整数数据类型的简单操作,可以用 Interlocked 类的成员来实现线程同步。
Interlocked 为多个线程共享的变量提供原子操作。在现代处理器中,Interlocked 类的方法经常可以由单个指令来实现。因此,它们提供性能非常高的同步。
另外,该类还有一个泛型版本,他接受的参数被限制为class(任意的引用类型)。
2.lock 关键字
lock 关键字可以用来确保代码块完成运行,而不会被其他线程中断。这是通过在代码块运行期间为给定对象获取互斥锁来实现的。
提供给 lock 关键字的参数必须为基于引用类型的对象,该对象用来定义锁的范围。
严格地说,提供给 lock 的对象只是用来唯一地标识由多个线程共享的资源,所以它可以是任意类实例。然而,实际上,此对象通常表示需要进行线程同步的资源。
通常,最好避免锁定 public 类型或锁定不受应用程序控制的对象实例。因为不受控制的代码也可能会锁定该对象,这可能导致死锁。
3.监视器
Enter 方法允许一个且仅一个线程继续执行后面的语句;其他所有线程都将被阻止,直到执行语句的线程调用 Exit。
lock 关键字就是用 Monitor 类来实现的。
使用 lock 关键字通常比直接使用 Monitor 类更可取,一方面是因为 lock 更简洁,另一方面是因为 lock 确保了即使受保护的代码引发异常,也可以释放基础监视器。
4.同步事件和等待句柄
使用锁或监视器对于防止同时执行区分线程的代码块很有用,但是这些构造不允许一个线程向另一个线程传达事件。通过这种方式实现同步事件的实现。
让线程等待非终止的同步事件可以将线程挂起,将事件状态更改为终止可以将线程激活。如果线程试图等待已经终止的事件,则线程将继续执行,而不会延迟。
同步事件有两种:AutoResetEvent 和 ManualResetEvent。它们之间唯一的不同在于,无论何时,只要 AutoResetEvent 激活线程,它的状态将自动从终止变为非终止。相反,ManualResetEvent 允许它的终止状态激活任意多个线程,只有当它的 Reset 方法被调用时才还原到非终止状态。
5.Mutex 对象
mutex 与监视器类似;然而与监视器不同的是,mutex 可以用来使跨进程的线程同步。
当用于进程间同步时,mutex 称为“命名 mutex”,因为它将用于另一个应用程序,因此它不能通过全局变量或静态变量共享。必须给它指定一个名称,才能使两个应用程序访问同一个 mutex 对象。
尽管 mutex 可以用于进程内的线程同步,但是使用 Monitor 通常更为可取,因为监视器是专门为 .NET Framework 而设计的,因而它可以更好地利用资源。相比之下,Mutex 类是 Win32 构造的包装。尽管 mutex 比监视器更为强大,但是相对于 Monitor 类,它所需要的互操作转换更消耗计算资源。


补充:Montor类与同步块
设计CLR的过程中,对于对象状态的同步访问设计的基本思想:
堆中的每个对象都有一个数据结构(与Win32的结构相似)与其关联,该数据结构可以用作线程同步锁。
在Win32中,采用该设计思想的非托管C++类的形式如下:

Code

然而在堆中的每个字段都关联一个CRITICAL_SECTION字段非常浪费,32位系统上需要大约24字节的空间,然而大多数对象在操作时都需要线程安全的访问。
了了减少内存的使用,CLR使用一个更有效的方法来提供该功能,它的工作原理是:
当CLR初始化时,CLR分配一个同步块(sync block)数组。一个同步块是一个可以与对象关联的内存链,每个同步块都包含与Win32的CRITICAL_SECTION相同的字段。
而无论何时,当一个对象被创建在堆中时,它将另外拥有两个额外的开销字段(overhead field)与其关联。第一个开销字段是类型对象指针,他包含类型的类型对象的内存地址。第二个就是同步块索引(sync block index),它包含一个同步块数组的整数索引。
对象被构建时,对象的同步块索引被初始化为一个负值,它表示没有任何同步块。然后当方法被调用进入对象的同步块时,CLR在数组中查找一个空闲的同步块,然后将对象的同步块索引设为引用所找到的同步块。当所有的线程都释放了对象的同步块之后,对象的同步块索引被重新设置为负值。

使用System.Threading.Monitor类中的Enter方法对对象的同步块加锁,某一个线程使用Enter方法后若没有其他线程占有则会拥有同步块,若其他线程使用Enter方法调用时发现已经有线程拥有这个同步块,则调用线程会挂起直至拥有这个同步块的线程释放了这个同步块。
不过使用Monitor类中的TryEnter()方法能达到更好的安全维护作用,因为该函数若不能拥有同步块则会返回一个false的bool值。

 

CLR线程同步锁存在的缺陷

使用Monitor和lock关键字时,常用的参数为this。如下面的语句:

            Monitor.Enter(this);
            
            
//do something

            Monitor.Exit(
this);

 

            lock (this)
            {
                
//do something
            }

但是这样做的缺点是,this指针往往是公共的(public)。这就好比在C++中将CRITICAL_SECTION设置为public。典型的例子,若将一个string类型加锁,而字符串统一存储在字符串池里,具有两个具有相同值的对象引用其实引用的是同一个对象,这样若对这两个引用同时加锁可能会出错。

.NET Remoting中的代理类也可能会出现只对代理类加锁,而根本没有印象到真正对象的情况。

下面的例子更是会出现死锁的情况,继而导致CLR的终结线程停止——该线程中将不再有对象被终结,因此托管内存中就不会再有可终结的对象被回收内存。

Code

为了避开公共锁的问题,可以将一个私有字段System.Object定义为类型的一个成员,并构建对象,然后以私有Object的引用为参数使用c#的lock语句。如下例所示:

Code

 

如果觉得专门建立一个对象用于加锁有些浪费,可以用其他的私有字段代替,但是为了安全,建议这样做。

双检锁技巧

使用惰性初始化(lazy initialization)时,若使用线程同步则需要使用双检锁技巧,在C#中较java容易实现。

在C#中实现双检锁技巧:

Code

其实,当没有必要惰性初始化时,下面的实现更方便:

Code

 

Window内核对象

 

posted on 2009-04-01 21:49  ymz  阅读(423)  评论(0编辑  收藏  举报