简易阻止:Sleep,阻止给定的时间周期;Join,等待另一个线程完成。
一旦被阻止,线程立刻放弃分配给他的CPU资源,并且ThreadState属性值改为WaitSleepJoin。
停止阻止的方式:(1)操作超时(2)通过Thread的Interrupt中断了(3)通过Thread的Abort放弃了.
Thread.Sleep(Timeout.Infinite);//休眠直到中断
Thread.Sleep(TimeSpan.FromSeconds(1));//休眠一秒
Thread.Sleep(0);//暂时的放弃CPU,时间刚刚够其他时间片里的线程执行。
Join方法也接收一个使用毫秒或用TimeSpan类的超时参数,当Join超时时返回false,如果线程已终止,则返回true。(在阻止是Join保持信息汲取,sleep停止信息汲取)。
锁系统:lock,确保只有一个线程访问某资源,不能跨进程,速度快;
Mutex, ,确保只有一个线程访问某资源,可以跨进程,速度中等;
Semaphore,不超过制定数目的线程访问某资源,可以跨进程,速度中等。
任何对所有有关系的线程都可见的对象都可以作为同步对象,但要服从一个硬性规定:它必须是引用类型。也强烈建议同步对象最好私有在类里面(比如一个私有实例字段)防止无意间从外部锁定相同的对象。服从这些规则,同步对象可以兼对象和保护两种作用。
List<string> list = new List<string>();
void Test()
{
lock (list)
{
list.Add("");
//...
}
}
锁并没有以任何方式阻止对同步对象本身的访问,换言之,x.ToString()不会由于另一个线程调用lock(x) 而被阻止,两者都要调用lock(x) 来完成阻止工作。
Interrupt 和 Abort
static void Main(string[] args)
{
Thread t = new Thread(
delegate()
{
try
{
Thread.Sleep(Timeout.Infinite);
}
catch (ThreadInterruptedException)
{
Console.WriteLine("Interrupt!");
}
Console.WriteLine("finally");
}
);
t.Start();
t.Interrupt();
Console.Read();
}
//Interrupt!
//finally
Interrupt中断一个线程仅仅释放其当前的等待状态,并不结束这个线程,后面该干啥干啥!
被阻止的线程也可以通过Abort方法被强制释放,这与调用Interrupt相似,除了用ThreadAbortException异常代替了ThreadInterruptedException异常,此外,异常将被重新抛出在catch里(在试图以有好方式处理异常的时候),直到Thread.ResetAbort在catch中被调用;在这期间线程的ThreadState为AbortRequested。
在Interrupt 与 Abort 之间最大不同在于它们调用一个非阻止线程所发生的事情。Interrupt继续工作直到下一次阻止发生,Abort在线程当前所执行的位置(可能甚至不在你的代码中)抛出异常。
//当两个或更多线程需要同时访问一个共享资源时,系统需要使用同步机制来确保一次只有一个线程使用该资源。Mutex 是同步基元,它只向一个线程授予对共享资源的独占访问权。如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程释放该互斥体。 //可以使用 WaitHandle.WaitOne 方法请求互斥体的所属权。拥有互斥体的线程可以在对 WaitOne 的重复调用中请求相同的互斥体而不会阻止其执行。但线程必须调用 ReleaseMutex 方法同样多的次数以释放互斥体的所属权。Mutex 类强制线程标识,因此互斥体只能由获得它的线程释放。相反,Semaphore 类不强制线程标识。 //互斥体有两种类型:局部互斥体和已命名的系统互斥体。如果使用接受名称的构造函数创建 Mutex 对象,则该对象与具有该名称的操作系统对象关联。已命名的系统互斥体在整个操作系统中都可见,可用于同步进程活动。您可以创建多个 Mutex 对象来表示同一个已命名的系统互斥体,也可以使用 OpenExisting 方法打开现有的已命名系统互斥体。(msdn) class TreadSafe { private static Mutex mut = new Mutex();//可以在构造函数中命名 private const int nums = 3; private const int arrs = 1; static void Main(string[] args) { for (int i = 0; i < nums; i++) { Thread t = new Thread(Go); t.Name = string.Format("Thread{0}", i); t.Start(); } Console.Read(); } static void Go() { for (int i = 0; i < arrs; i++) { useResource(); } } private static void useResource() { mut.WaitOne(); Console.WriteLine(string.Format("{0}进入", Thread.CurrentThread.Name)); Thread.Sleep(100); Console.WriteLine(string.Format("{0}离开", Thread.CurrentThread.Name)); mut.ReleaseMutex(); } }
/*使用 Semaphore 类可控制对资源池的访问。线程通过调用 WaitOne 方法(从 WaitHandle 类继承)进入信号量,并通过调用 Release 方法释放信号量。 信号量的计数在每次线程进入信号量时减小,在线程释放信号量时增加。当计数为零时,后面的请求将被阻塞,直到有其他线程释放信号量。当所有的线程都已释放信号量时,计数达到创建信号量时所指定的最大值。 被阻止的线程并不一定按特定的顺序(如 FIFO 或 LIFO)进入信号量。 线程可通过重复调用 WaitOne 方法多次进入信号量。为释放这些入口中的部分或全部,线程可多次调用无参数的 Release 方法重载,也可以调用 Release(Int32) 方法重载来指定要释放的入口数。 Semaphore 类不对 WaitOne 或 Release 调用强制线程标识。程序员负责确保线程释放信号量的次数不能太多。例如,假定信号量的最大计数为 2,并且线程 A 和线程 B 同时进入信号量。如果线程 B 中的编程错误导致它两次调用 Release,则两次调用都成功。这样,信号量的计数已满,当线程 A 最终调用 Release 时便会引发 SemaphoreFullException。 信号量分为两种类型:局部信号量和已命名的系统信号量。如果您使用接受名称的构造函数创建 Semaphore 对象,则该对象与具有该名称的操作系统信号量关联。已命名的系统信号量在整个操作系统中都可见,可用于同步进程活动。您可以创建多个 Semaphore 对象来表示同一个已命名的系统信号量,也可以使用 OpenExisting 方法打开现有的已命名系统信号量。 局部信号量仅存在于您的进程内。您的进程中任何引用局部 Semaphore 对象的线程都可以使用它。每个 Semaphore 对象都是一个单独的局部信号量。*/ class Program { private static Semaphore _pool; private static int _padding; static void Main(string[] args) { _pool = new Semaphore(0, 3); for (int i = 1; i <= 5; i++) { Thread t = new Thread(new ParameterizedThreadStart(Go)); t.Start(i); } Thread.Sleep(500); Console.WriteLine("主线程将信号量增加到3"); _pool.Release(3); Console.Read(); } static void Go(object num) { Console.WriteLine("Thread{0}开始,并等待Semaphore", num); _pool.WaitOne(); int padding = Interlocked.Add(ref _padding, 100);//原子操作,两个值相加,把第一个值更新为两值和 Console.WriteLine("Thread{0}进入Semaphore", num); Thread.Sleep(1000 + padding); Console.WriteLine("Thread{0} 离出 semaphore.", num); Console.WriteLine("Thread{0} 前一个信号量数: {1}", num, _pool.Release()); } }
class Program { /*AutoResetEvent 允许线程通过发信号互相通信。 通常,当线程需要独占访问资源时使用该类。 线程通过调用 AutoResetEvent 上的 WaitOne 来等待信号。 如果 AutoResetEvent 为非终止状态,则线程会被阻止,并等待当前控制资源的线程通过调用 Set 来通知资源可用。 调用 Set 向 AutoResetEvent 发信号以释放等待线程。 AutoResetEvent 将保持终止状态,直到一个正在等待的线程被释放,然后自动返回非终止状态。 如果没有任何线程在等待,则状态将无限期地保持为终止状态。 如果当 AutoResetEvent 为终止状态时线程调用 WaitOne,则线程不会被阻止。 AutoResetEvent 将立即释放线程并返回到非终止状态。*/ static AutoResetEvent wh = new AutoResetEvent(false); static void Main() { new Thread(Go).Start(); Thread.Sleep(2000); wh.Set();//释放 Console.Read(); } static void Go() { Console.WriteLine("waitting.........."); wh.WaitOne();//暂停 Console.WriteLine("OK"); } }
//生产者/消费者队列 class ProducerCustomerQueue : IDisposable { AutoResetEvent wh = new AutoResetEvent(false); Thread worker; object locker = new object(); Queue<string> tasks = new Queue<string>(); public ProducerCustomerQueue() { worker = new Thread(work); worker.Start(); } public void EnqueueTask(string task) { lock (locker) { tasks.Enqueue(task); } wh.Set(); } void work() { while (true) { string task = null; lock (locker) { if (tasks.Count > 0) { task = tasks.Dequeue(); if (task == null) //tag { return; } } if (task != null) { Console.WriteLine("执行任务" + task); Thread.Sleep(1000); } else { wh.WaitOne();//没有任务了,等待信号 } } } } #region IDisposable 成员 public void Dispose() { EnqueueTask(null);//goto:tag.告诉消费者退出 worker.Join();//等待消费者线程完成 wh.Close();//释放所有资源 } #endregion } class TreadSafe { static void Main(string[] args) { using (ProducerCustomerQueue q = new ProducerCustomerQueue()) { q.EnqueueTask("hello"); for (int i = 0; i < 10; i++) { q.EnqueueTask("say " + i); } q.EnqueueTask("Bye"); } Console.Read(); } }