.net 线程 和 线程安全

第一节 使用线程和线程处理

启动时创建线程并传递数据

public class ThreadWithState {     private string boilerplate;     private int value;     public ThreadWithState(string text, int number)     {         boilerplate = text;         value = number;     }     public void ThreadProc()     {         Console.WriteLine(boilerplate, value);     } }

Thread t = new Thread(new ThreadStart(tws.ThreadProc)); t.Start(); Console.WriteLine("Main thread does some work, then waits."); t.Join(); //等待线程join结束 才开始当前线程

 
暂停和继续线程

同步线程活动的最常用方法是锁定和释放线程,或者锁定对象或代码区域。

还可以让线程将自身置于休眠状态

调用 System.Threading.Thread.Sleep 及 System.Threading.Thread.Infinite 将使线程休眠,直到被调用 System.Threading.Thread.Interrupt 的另一个线程中断,或被System.Threading.Thread.Abort 终止。System.Threading.Thread.Interrupt 会引发 ThreadInterruptedException,可以中断正在等待的线程,从而使该线程脱离造成阻止的调用

与 System.Threading.Thread.Sleep 不同,System.Threading.Thread.Suspend 不会导致线程立即停止执行。公共语言运行库必须一直等待,直到线程到达安全点之后它才可以将该线程挂起, 直到被System.Threading.Thread.Resume唤醒。如果线程尚未启动或已经停止,则它将不能挂起.  

 

销毁线程

调用Abort中断线程引发ThreadAbortException异常,这是一种可捕获的特殊异常,但在 catch 块的结尾处它将自动被再次引发。引发此异常时,运行库将在结束线程前执行所有 finally 块。由于线程可以在 finally 块中执行未绑定计算,或调用 Thread.ResetAbort来取消中止,所以不能保证线程将完全结束。

如果您希望一直等到被中止的线程结束,可以调用 Thread.Join 方法。Join 是一个模块化调用,它直到线程实际停止执行时才返回。

 

销毁线程

每个线程都具有分配给它的线程优先级。为在公共语言运行库中创建的线程最初分配的优先级为ThreadPriority.Normal。在运行库外创建的线程会保留它们在进入托管环境之前所具有的优先级。您可以使用Thread.Priority 属性获取或设置任何线程的优先级

 

第二节 前台和后台线程

.Net的公用语言运行时能区分两种不同类型的线程:前台线程和后台线程。这两者的区别就是:应用程序必须运行完所有的前台线程(包括正常退出和异常退出)后才可以退出;而对于后台线程,应用程序则可以不考虑其是否已经运行完毕而直接退出,所有的后台线程在应用程序退出时都会自动结束

.net环境使用Thread建立的线程默认情况下是前台线程,即线程属性IsBackground=true,在进程中,只要有一个前台线程未退出,进程就不会终止。主线程就是一个前台线程。而后台线程不管线程是否结束,只要所有的前台线程都退出(包括正常退出和异常退出)后,进程就会自动终止。一般后台线程用于处理时间较短的任务,如在一个Web服务器中可以利用后台线程来处理客户端发过来的请求信息。而前台线程一般用于处理需要长时间等待的任务,如在Web服务器中的监听客户端请求的程序,或是定时对某些系统资源进行扫描的程序。下面的代码演示了前台和后台线程的区别。

 

第三节 为多线程处理同步数据

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

“公共语言基础结构”提供了几种可用来同步对实例和静态成员的访问的策略:

同步代码区域。可以使用 Monitor 类或该类的编译器支持来仅同步需要该类的代码块,从而改善性能。

手动同步。可以使用 .NET Framework 类库提供的同步对象。请参见 同步基元概述,这部分对 Monitor 类进行了讨论。

同步上下文。可以使用 SynchronizationAttribute 为 ContextBoundObject 对象启用简单的自动同步。

Synchronized 属性。Hashtable 和 Queue 等几个类提供了一个可为该类的实例返回线程安全包装的 Synchronized属性。请参见集合和同步(线程安全)。

 
Monitor [独占锁] [线程关联] [跨进程]                          

Monitor对象通过使用 Monitor.Enter、Monitor.TryEnter 和 Monitor.Exit 方法对特定对象获取锁和释放锁来公开同步访问代码区域的能力。在对代码区域获取锁后,就可以使用 Monitor.Wait、Monitor.Pulse 和 Monitor.PulseAll 方法了。如果锁被暂挂,则 Wait 释放该锁并等待通知。当 Wait 接到通知后,它将返回并再次获取该锁。Pulse 和 PulseAll 都会发出信号以便等待队列中的下一个线程继续执行。

Monitor 将锁定对象(即引用类型),而非值类型。尽管可以向 Enter 和 Exit 传递值类型,但对于每次调用它都是分别装箱的。因为每次调用都创建一个独立的对象,所以 这样做的结果就是Enter 永远不会阻止,而且它要保护的代码并没有真正同步。另外,传递给 Exit 的对象不同于传递给 Enter 的对象,所以 Monitor 将引发 SynchronizationLockException,并显示以下消息:“从不同步的代码块中调用了对象同步方法。”下面的示例演示这些问题。

意到 Monitor 和 WaitHandle 对象在使用上的区别是非常重要的。Monitor 对象是完全托管、完全可移植的,并且在操作系统资源要求方面可能更为有效。WaitHandle 对象表示操作系统可等待对象,对于在托管和非托管代码之间进行同步非常有用,并公开一些高级操作系统功能(如同时等待许多对象的能力)。


WaitHandle [信号量和其他等待句柄的抽象基类] [跨进程]

等同于C#中的lock(object)语法

WaitHandle 类本身是抽象类。除派生类之外,它还具有许多对多个事件启用等待的静态方法。从 WaitHandle派生的类包括Mutex 类, EventWaitHandle 类及其派生类、AutoResetEvent 和 ManualResetEvent, Semaphore 类.

由于 WaitHandle 类派生自 MarshalByRefObject,所以这些类可用于跨应用程序域边界同步线程的活动。

线程可以通过调用实例方法 WaitOne 在单个等待句柄上阻塞。此外,WaitHandle 类重载了静态方法,以等待所有指定的等待句柄集都已收到信号 (WaitAll),或等待某一指定的等待句柄集收到信号 (WaitAny)。这些方法的重载提供了放弃等待的超时间隔、在进入等待之前退出同步上下文的机会,并允许其他线程使用同步上下文。

在 .NET Framework 2.0 版中,等待句柄也具有静态 SignalAndWait方法,该方法允许线程发送一个等待句柄信号,然后立即等待另一个等待句柄,如同原子操作一样。

WaitHandle 的派生类具有不同的线程关联。事件等待句柄(EventWaitHandle、AutoResetEvent 和 ManualResetEvent)以及信号量没有线程关联任何线程都可以发送事件等待句柄或信号量的信号。另一方面,mutex 具有线程关联。拥有 mutex 的线程必须将其释放;

 

Mutex 互斥锁  [独占锁] [线程关联] [跨进程]

Mutex 类比 Monitor 类使用更多系统资源,但是它可以跨应用程序域边界进行封送处理,可用于多个等待,并且可用于同步不同进程中的线程. 线程调用 mutex 的 WaitOne 方法请求所有权。该调用会一直阻塞到 mutex 可用,或直至达到可选的超时间隔。如果没有任何线程拥有它,则 Mutex 的状态为已发信号的状态。线程通过调用其 ReleaseMutex 方法释放 mutex。mutex 具有线程关联;即 mutex 只能由拥有它的线程释放,否则会在该线程中引发 ApplicationException。

由于 Mutex 类从 WaitHandle 派生,所以您还可以结合其他等待句柄调用 WaitHandle 的静态 WaitAll 或 WaitAny 方法请求 Mutex 的所有权。 如果某个线程拥有 Mutex,则该线程就可以在重复的等待-请求调用中指定同一个 Mutex,而不必阻止其执行;但是,它必须释放 Mutex,次数与释放所属权的次数相同

Mutex 分两种类型:本地 mutex 和命名系统 mutex。如果使用接受名称的构造函数创建了 Mutex 对象,那么该对象将与具有该名称的操作系统对象相关联。命名的系统 mutex 在整个操作系统中都可见,并且可用于同步进程活动。您可以创建多个 Mutex 对象来表示同一命名系统 mutex,而且您可以使用 OpenExisting 方法打开现有的命名系统 mutex。本地 mutex 仅存在于进程当中。进程中引用本地 Mutex 对象的任意线程都可以使用本地 mutex。 

 

EventWaitHandle、AutoResetEvent 和 ManualResetEvent [非独占锁] [非线程关联] [跨进程]

        事件等待句柄允许线程通过彼此发送信号和等待彼此的信号来同步活动。这些同步事件是基于 Win32 等待句柄的,可分为两种类型:一种收到信号时自动重置;另一种需手动重置。 事件等待句柄在与 Monitor 类相同的许多同步情况下十分有用。事件等待句柄通常比使用 System.Threading.Monitor.Wait和 System.Threading.Monitor.Pulse(System.Object)方法更简单,并且可以对信号发送提供更多控制。命名事件等待句柄也可用于跨应用程序域和进程同步活动,而监视器对于应用程序域是本地的。

  

Interlocked 互锁操作 [联锁] [高性能] [可跨进程]

        Interlocked 类提供这样一些方法,即同步对多个线程共享的变量的访问的方法。如果该变量位于共享内存中,则不同进程的线程就可以使用该机制。互锁操作是原子的 — 即整个操作是不能由相同变量上的另一个互锁操作所中断的单元。这在抢先多线程操作系统中是很重要的,在这样的操作系统中,线程可以在从某个内存地址加载值之后但是在有机会更改和存储该值之前被挂起。在现代处理器中,Interlocked 类的方法经常可以由单个指令来实现。因此,它们提供性能非常高的同步,并且可用于构建更高级的同步机制,例如自旋锁。

Interlocked 类提供了以下操作:

Add方法向变量添加一个整数值并返回该变量的新值。

Read方法作为一个原子操作读取一个 64 位整数值。

Increment 和 Decrement方法递增或递减某个变量并返回结果值。

Exchange 方法执行指定的变量中的值的原子交换,返回该值并将其替换为新值。可以重载在引用类型的变量上执行此交换。

CompareExchange 方法也交换两个值,但是视比较的结果而定。


ReaderWriterLock [读写锁] [线程关联]

允许多个线程同时读取一个资源,但在向该资源写入时要求线程等待获得独占锁。 在应用程序中,可以使用 ReaderWriterLock 在访问一个共享资源的线程之间提供协调同步。在这种情况下,获得的锁是针对 ReaderWriterLock 本身的。与任何线程同步机制相同,您必须确保任何线程都不会跳过 ReaderWriterLock。或者,也可以设计一个封装资源的类。此类可以使用 ReaderWriterLock 实现其针对资源的锁定方案。ReaderWriterLock 使用了一种高效的设计,可用来同步单个对象。设计您应用程序的结构,让读取和写入的时间尽可能最短。因为写入锁定是排他的,所以长时间的写入会直接降低吞吐量。长时间的读取会阻止处于等待的编写器,并且,如果至少有一个线程在等待写入锁,那么请求新的读取器锁的线程也将被阻止。

 

信号量 [生产者消费者锁] [非线程关联] [跨进程]

Semaphore 类表示一个命名信号量(系统范围)或本地信号量。Windows 信号量是计数信号量,可用于控制对资源池的访问。线程通过调用 WaitOne 方法来进入信号量,此方法是从 WaitHandle类派生的。当调用返回时,信号量的计数将减少。当一个线程请求项而计数为零时,该线程会被阻止。当线程通过调用 Release 方法释放信号量时,将允许被阻止的线程进入。针对让被阻止的线程进入信号量,不存在保证的顺序(例如 FIFO 或 LIFO)。线程可以通过重复调用 WaitOne 方法来多次进入信号量。若要释放信号量,线程可以调用 Release 方法重载相同的次数,也可以调用 Release 方法重载并指定要释放的项数

Windows 操作系统允许信号量具有名称。命名信号量在整个系统范围都有效。即,创建命名信号量后,所有进程中的所有线程都是可见的。因此,命名信号量可用于同步进程的活动以及线程的活动。

信号量的一个常用方案包括一个生产者线程和一个使用者线程,其中一个线程总是增加信号量计数,而另一个线程总是减少信号量计数。因此他不是线程关联的

 

SpinLock 自旋锁 [独占锁] [线程关联]

尝试获取该锁的线程持续不断的check是否可以获得。此时线程仍然是激活状态,只是在空转,浪费cpu而已。单核是不建议使用自旋的. 但是spinlock避免了线程调度和上下文切换,如果锁的时间极短的话,使用该锁反而效率会高。

而lock[Monitor]是线程被block了。这将引起线程调度和上下文切换等行为。

Spinlock在自旋极短的时间内是可以采取的。

不能调用Enter两次在同一个spinlock上面。

SpinLock允许你查询是否锁已经被其他线程占用,通过IsHeld属性。


 线程关联定义: 进入Lock的线程必须通过自身调用 Exit 或 Wait 才能退出。

 

线程安全

在线程中使用共享资源时,能够保证共享资源在任何时候都是原子的、一致的,这样的线程就是线程安全的线程, 一个类要成为线程安全的类,就是在该类被多个线程访问时,不管运行环境中执行这些线程有什么样的时序安排或者交错,它仍然执行正确行为,并且在调用的代码中没有任何额外的同步。

当一个类的实例为singleton的时候,你就要考虑该实例在调用的时候是否是线程安全的


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值