C#中的线程 -- 同步基础(线程状态,同步上下文)

来源:互联网 发布:vidconvert mac注册机 编辑:程序博客网 时间:2024/05/16 11:02

内容预告:

  • 线程入门(线程概念,创建线程)
  • 同步基础(同步本质,线程安全,线程中断,线程状态,同步上下文)
  • 使用线程(后台任务,线程池,读写锁,异步代理,定时器,本地存储)
  • 高级话题(非阻塞线程,扶起和恢复)

线程状态:可以通过ThreadState查看线程的状态。

关于线程的状态,有三层意思:

  •  运行、阻塞、终止。如上图所示
  •  前台、后台(ThreadState.Background)
  •  暂停、申请暂停(ThreadState.SuspendRequested 和 ThreadState.Suspended)

所以线程的状态可以是这三层中各占其一,比如:SuspendRequested, Background, WaitSleepJoin

 

等待句柄:

Win32 API有很多线程同步的结构,在.NET里是通过EventWaitHandle, Mutex 和Semaphore暴露的。这些类都是WaitHandle的派生类。通过命名线程,可以跨进程的工作,而不是只在线程内工作。

EventWaitHandle有两个子类,AutoResetEvent 和ManualResetEvent,两者的不同在于构造函数的参数不同。

AutoResetEvent 就像一个门禁,刷一次门卡进一个人。Auto的意思是当有刷卡的时候自动开门,人进去以后自动关门。可以通过调用WaitOne函数让一个线程在门口等,可以通过Set函数插入门卡。如果有很多线程在门外等,就排成一个队列。WaitOne可以接收一个超时参数,如果时间结束会返回一个false。

复制代码
class BasicWaitHandle {static EventWaitHandle wh = new AutoResetEvent (false);static void Main() {new Thread (Waiter).Start();Thread.Sleep (1000); // Wait for some time...wh.Set(); // OK - wake it up}static void Waiter() {Console.WriteLine ("Waiting...");wh.WaitOne(); // Wait for notificationConsole.WriteLine ("Notified");}}
复制代码

输出:Waiting... (pause) Notified.

创建一个跨进程的EventWaitHandle

EventWaitHandle wh = new EventWaitHandle (false, EventResetMode.Auto,"MyCompany.MyApp.SomeName");

生产者、消费者模型:

另一个常见的场景是:一个队列中有一个后台工作进程任务。这个队列叫做生产者/消费者队列,生产者将任务加入队,消费者将任务移出队。
生产者消费者队列可以扩展的,可以创建多个消费者,每个消费者都在一个单独的线程上服务于同一个队列。下面的例子中,单个AutoResetEvent用来当任务运行完成时给工作线程发信号:

复制代码
using System;using System.Threading;using System.Collections.Generic;class ProducerConsumerQueue : IDisposable {    EventWaitHandle wh = new AutoResetEvent (false);Thread worker;object locker = new object();Queue<string> tasks = new Queue<string>();public ProducerConsumerQueue() {worker = new Thread (Work);worker.Start();}public void EnqueueTask (string task) {lock (locker) tasks.Enqueue (task);wh.Set();}public void Dispose() {EnqueueTask (null); // Signal the consumer to exit.worker.Join(); // Wait for the consumer's thread to finish.wh.Close(); // Release any OS resources.}void Work() {while (true) {string task = null;lock (locker)if (tasks.Count > 0) {task = tasks.Dequeue();if (task == null) return;}if (task != null) {Console.WriteLine ("Performing task: " + task);Thread.Sleep (1000); // simulate work...}elsewh.WaitOne(); // No more tasks - wait for a signal}}}Here's a main method to test the queue:class Test {static void Main() {using (ProducerConsumerQueue q = new ProducerConsumerQueue()) {q.EnqueueTask ("Hello");for (int i = 0; i < 10; i++) q.EnqueueTask ("Say " + i);q.EnqueueTask ("Goodbye!");}// Exiting the using statement calls q's Dispose method, which// enqueues a null task and waits until the consumer finishes.}}
复制代码

输出结果是:

复制代码
Performing task: HelloPerforming task: Say 1Performing task: Say 2Performing task: Say 3......Performing task: Say 9Goodbye!
复制代码

注意:我们在这个例子中当ProducerConsumerQueue析构时显式地关闭了等待句柄,因为我们可以在程序的生命周期里创建和销毁很多这个类的实例。

ManualResetEvent:是AutoResetEvent的变种,区别在于在线程在WaitOne后被允许通过门禁后不能自动重置。要手动Set开门,Reset关门。

Mutex:和lock的功能一样,只是可以跨进程。一个常用的功能是保证程序同时只有一个实例在运行:

复制代码
class OneAtATimePlease {// Use a name unique to the application (eg include your company URL)static Mutex mutex = new Mutex (false, "oreilly.com OneAtATimeDemo");static void Main() {// Wait 5 seconds if contended – in case another instance// of the program is in the process of shutting down.if (!mutex.WaitOne (TimeSpan.FromSeconds (5), false)) {Console.WriteLine ("Another instance of the app is running. Bye!");return;}try {Console.WriteLine ("Running - press Enter to exit");Console.ReadLine();}finally { mutex.ReleaseMutex(); }}}
复制代码

Mutex有一个还不错的功能是程序在终止时如果没有调用ReleaseMutex,CLR会自动释放Mutex。

Semaphore(信号量):Semaphore就像一个夜总会,有一个保镖保护着,当里面满的时候,外面就得排除进去,直到有空位置出来。构造函数有两个参数,一个是夜总会当前的空位数量和容限(总位置数)。
一个带容限的Semaphore和Mutex以及lock都很像,除了Semaphore没有宿主,任何线程都可以释放一个信号量,在Mutex和lock上只有获得资源的线程才能释放信号量。下面的例子里,10个线程执行一个带Sleep的循环语句,一个信号量确保不超过3个线程可以同时执行Sleep:

复制代码
class SemaphoreTest {static Semaphore s = new Semaphore (3, 3); // Available=3; Capacity=3static void Main() {for (int i = 0; i < 10; i++) new Thread (Go).Start();}static void Go() {while (true) {s.WaitOne();Thread.Sleep (100); // Only 3 threads can get here at onces.Release();}}}
复制代码

WaitAny, WaitAll and SignalAndWait

除了Set和WaitOne之外,WaitHandle类还有几个静态函数来解决复杂的同步场景,WaitAny, WaitAll 和 SignalAndWait 函数有助于等待多个句柄(可以是多个类型的)。SignalAndWait也许最有用,当在另一个WaitHandle调用Set时,它在WaitHandle上调用WaitOne,这是一个原子操作。可以用在一对EventWaitHandles上来建立两个线程,AutoResetEvent和ManualResetEvent都有这个麻烦,第一个线程:

WaitHandle.SignalAndWait (wh1, wh2);

第二个线程:

WaitHandle.SignalAndWait (wh1, wh2);

WaitHandle.WaitAny等待每一个等待句柄数组中的句柄。WaitHandle.WaitAll等待所有给定的句柄。用门禁的例子来比喻的话,这些函数就像在门禁前排队。


同步上下文:

相比手动加锁,我们也可以通过代码自动加锁,可以从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); // We can't be preempted hereConsole.WriteLine ("end"); // thanks to automatic locking!}}public class Test {public static void Main() {AutoLock safeInstance = new AutoLock();new Thread (safeInstance.Demo).Start(); // Call the Demonew Thread (safeInstance.Demo).Start(); // method 3 timessafeInstance.Demo(); // concurrently.}}
复制代码

输出:

Start... endStart... endStart... end

CLR保证同时只有一个线程执行safeInstance代码,它通过创建一个同步对象,锁定safeInstance的所有函数和字段,这个safeInstance对象叫做同步上下文。

它是如何运转的?一个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();}}
复制代码
原创粉丝点击