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

.Net学习难点讨论系列14 – 多线程下的进程同步(线程同步问题总结篇)

2012年11月08日 ⁄ 综合 ⁄ 共 2705字 ⁄ 字号 评论关闭

 

       之前写过两篇关于线程同步问题的文章(),这篇中将对相关话题进行总结,本文中也对.NET 4.0中新增的一些同步机制进行了介绍。

  首先需要说明的是为什么需要线程功能同步。MSDN中有这样一段话很好的解释了这个问题:

当多个线程可以调用单个对象的属性和方法时,对这些调用进行同步处理是非常重要的。否则,一个线程可能会中断另一个线程正在执行的任务,使该对象处于一种无效状态。

也就说在默认无同步的情况下,任何线程都可以随时访问任何方法或字段,但一次只能有一个线程访问这些对象。另外,MSDN中也给出定义,成员不受多线程调用中断影响的类即线程安全类。

       CLI提供了几种可用来同步对实例和静态成员的访问的策略(前面两边文章介绍了这其中大部分机制):

Ø  同步代码区域

可以使用Monitor类或(编译器支持的语法,如C#中的lock关键字)来同步需要安全的接受并发请求的代码段,这种方式比其他等效的同步方法有更好的性能。

lock语句通过MonitorEnterExit方法实现代码段同步,使用try catch finally结构确保锁被释放。当线程执行该代码时,会尝试获取锁。如果该锁已由其他线程获取,则在锁变为可用状态之前,该线程一直处于阻塞状态。当线程退出同步代码块时,锁就会被释放,这与线程的退出方式无关。通常情况下同步一小代码块并且不跨越多个方法的最佳选择是lock 语句,Monitor类功能强大但使用不当容易出现孤立锁与死锁,而由于lock是通过MonitorEnterExit实现的,因此在临界区中可以结合Monitor的其它方法一起使用。

另外可以通过[MethodImpl(MethodImplOptions.Synchronized)]特性标记一个方法是需要被同步的,方法可以是实例方法也可以是静态方法。最终实现的效果与使用lock关键字或Monitor相关方法相同。注意不要在此特性标记的方法内使用lock(this)/lock(typeof(this))(注意,单独使用lock时也不应用对象本身或类型作为锁(应为类型或实例可能被其它机制锁定,如被[MethodImpl(MethodImplOptions.Synchronized)]标记),对于实例方法与静态方法最好分别使用声明新的私有成员或静态私有成员作为锁,避免使用公有成员作为锁)。另外不能对字符串加锁。

Ø  手动同步:

.NET Framework中提供一些类用于手动进行线程间的访问同步。这些类主要分为3大类别(但正如下文中会看到的这些类别划分并非绝对,某些同步机制在多个类别之间有交叉):

ü  锁定

ü  通知

ü  连锁操作

1.     锁定

排他锁

独占锁

最常见的形式就是C#lock语句,该语句控制对一个代码块的访问,这个代码块被称作临界区。详见前文xx中对lock的介绍。

Monitor

Monitor类提供了许多附加功能,这些功能可以与lock关键字结合使用(lock的临界区中调用Monitor类的方法)。更多细节见线程同步问题1方法二中的介绍。

Mutex

Mutex的作用也是创建一个临界区以同步对其中对象的访问,方式类似Monitor类,但最大的不同是Mutex支持跨进程的同步。当然其效率也不如Monitor类,在同一进程内通信应首先考虑使用MonitorMutex的介绍详见线程同步问题2方法五中的介绍。

SpinLock

.NET4.0中新增

Monitor 所需的开销会造成性能下降时,可以使用 SpinLock 类。当SpinLock请求进入临界区时,会反复地旋转(执行空循环),直至锁变为可用的。如果请求锁所需时间非常短,则空转可比阻塞提供更好的性能。但是,如果锁保留数十个周期以上,则SpinLock的表现会和Monitor一样,而且将使用更多的CPU周期,降低其他线程或进程的性能。

其它锁

有些时候锁不必独占,可以允许一定数目的线程并发访问某个资源。下面列举的锁即用于这个目的。

ReaderWriterLock

允许多个线程同时读取一个资源,但在向该资源写入时要求线程等待以获得独占锁。更多细节见线程同步问题1方法三中的介绍。

Semaphore

Semaphore类允许指定数目的线程访问某个资源。超过这个数目时,请求该资源的其他线程会一直阻塞,直到某个线程释放信号量。更多细节见线程同步问题2方法七中的介绍。

ReaderWriterLockSlim

.NET4.0中新增

这个类的作用与ReaderWriterLock类完全一致,其拥有更好的性能,在新开发的程序中应当使用ReaderWriterLockSlim而不是ReaderWriterLockReaderWriterLockSlim 具有线程关联。

SemaphoreSlim

.NET4.0中新增

SemaphoreSlim类是用于在单一进程边界内进行同步的轻量信号量。使用方式上与Semaphore一致。

2.     通知

通知机制是等待另一个线程的信号的所有方法的统称。

Join方法

这是等待来自另一个线程信号最简单的方法,解释Join方法最好有一个场景,假如我们有ThreadAThreadB两个线程,假如我们在ThreadB执行的方法中调用ThreadA.Join()方法。这将阻塞B线程的执行直到A线程完成。场景中ThreadB可以是主线程也可以是其它子线程。其中也可以调用多个子线程的Join方法。这样ThreadB将阻塞并等待所有这些线程执行完毕后才继续执行。另外如果ThreadA的方法中调用了其它线程的Join方法,这将形成一个队列形式的线程调用,所有这些线程将一个个排队执行。

    Join也具有两个接受时间间隔的重载,用于设置阻塞线程等待的最长时间。依然用上面的例子来说,我们在B线程方法中调用ThreadA.Join(5000),当在5秒钟内线程A执行完毕了,则Join方法会立刻返回trueThreadB继续执行,如果5秒钟线程A未完成,则Join方法在5秒钟到时返回falseThreadAThreadB进入并行交替执行状态。

等待句柄

等待句柄派生自WaitHandle类,后者又派生自 MarshalByRefObject。从而等待句柄可用于跨应用程序域边界的线程同步。WaitHandle类封装了Win32的同步句柄,用于表示所有允许多个等待操作的同步对象。

通过调用WaitOne实例方法或WaitAllWaitAnySignalAndWait中任一个静态方法方法,可以阻塞当前线程以等待WaitHandle

抱歉!评论已关闭.