当一个进程启动了多个线程时,如果需要控制这些线程的推进顺序(比如A线程必须等待B和C线程执行完毕之后才能继续执行),则称这些线程需要进行“线程同步(thread synchronization)”。
线程同步的道理虽然简单,但却是给多线程开发带来复杂性的根源之一。当线程同步不好时,有可能会出现一种特殊的情形——死锁(Dead Lock)。
死锁表示系统进入了一个僵化状态,所有线程都没有执行完毕,但却谁也没法继续执行。究其根源,是因为“进程推进顺序不当”和“资源共享”。如例:
1)进程推进顺序不当造成死锁
using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace JoinLeadToDeadlock { class Program { static Thread mainThread; static void Main(string[] args) { Console.WriteLine("主线程开始运行"); mainThread = Thread.CurrentThread; Thread ta = new Thread(new ThreadStart(ThreadAMethod)); ta.Start(); //线程A开始执行 Console.WriteLine("主线程等待线程A结束……"); ta.Join(); //等待线程A结束 Console.WriteLine("主线程退出"); } static void ThreadAMethod() { for (int i = 0; i < 10; i++) { Console.WriteLine(Convert.ToString(i) + ": 线程A正在执行"); Thread.Sleep(1000); } Console.WriteLine("线程A等待主线程退出……"); mainThread.Join(); //等待主线程结束 } } }在该例中,主线程mainThread先开始执行,然后启动线程ta,线程ta执行结束前又要等待mainThread线程执行结束,这样就出现了“交叉等待”的局面,必然死锁!
2)共享资源造成死锁
所谓“共享资源”,指的是多个线程可以同时访问的数据结构、文件等信息实体。
using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace SharedResourceLeadToDeadlock { class Program { //共享资源 static SharedResource R1 = new SharedResource(); static SharedResource R2 = new SharedResource(); static void Main(string[] args) { Thread th1 = new Thread(UseSharedResource1); Thread th2 = new Thread(UseSharedResource2); th1.Start(); th2.Start(); //等待两线程运行结束 th1.Join(); th2.Join(); } static void UseSharedResource1() { System.Console.WriteLine("线程{0}申请使用资源R1", Thread.CurrentThread.ManagedThreadId); Monitor.Enter(R1); //对R1加锁 System.Console.WriteLine("线程{0}独占使用资源R1", Thread.CurrentThread.ManagedThreadId); Thread.Sleep(1000); System.Console.WriteLine("线程{0}申请使用资源R2", Thread.CurrentThread.ManagedThreadId); Monitor.Enter(R2); //对R2加锁 System.Console.WriteLine("线程{0}独占使用资源R2", Thread.CurrentThread.ManagedThreadId); Thread.Sleep(1000); System.Console.WriteLine("线程{0}资源R2使用完毕,放弃", Thread.CurrentThread.ManagedThreadId); Monitor.Exit(R2); //对R2解锁 System.Console.WriteLine("线程{0}资源R1使用完毕,放弃", Thread.CurrentThread.ManagedThreadId); Monitor.Exit(R1); //对R1解锁 } static void UseSharedResource2() { System.Console.WriteLine("线程{0}申请使用资源R2", Thread.CurrentThread.ManagedThreadId); Monitor.Enter(R2); //对R2加锁 System.Console.WriteLine("线程{0}独占使用资源R2", Thread.CurrentThread.ManagedThreadId); Thread.Sleep(500); System.Console.WriteLine("线程{0}申请使用资源R1", Thread.CurrentThread.ManagedThreadId); Monitor.Enter(R1); //对R1加锁 System.Console.WriteLine("线程{0}独占使用资源R1", Thread.CurrentThread.ManagedThreadId); Thread.Sleep(500); System.Console.WriteLine("线程{0}资源R1使用完毕,放弃", Thread.CurrentThread.ManagedThreadId); Monitor.Exit(R1); //对R1解锁 System.Console.WriteLine("线程{0}资源R2使用完毕,放弃", Thread.CurrentThread.ManagedThreadId); Monitor.Exit(R2); //对R2解锁 } } class SharedResource { } }
在该例中,线程th1执行时先申请使用R1,然后再申请使用R2,而线程th2执行时先申请R2,然后再申请R1,这样对于线程th1和th2,就会造成各自拥有一个对方需要的资源部释放,而又同时申请一个对方已经占有的资源,必然会造成死锁。
using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace SharedResourceLeadToDataError { class Program { static void Main(string[] args) { Thread[] ths = new Thread[4]; for (int i = 0; i < 4; i++) { ths[i]=new Thread(increaseCount); ths[i].Start(); } System.Console.ReadKey(); } static void increaseCount() { Random ran = new Random(); Thread.Sleep(ran.Next(100, 5000)); int beginNum = SharedResource.Count; System.Console.WriteLine("线程 {0} 读到的起始值为 {1} ", Thread.CurrentThread.ManagedThreadId, beginNum ); for (int i = 0; i < 10000; i++) { beginNum ++; } SharedResource.Count = beginNum; System.Console.WriteLine("线程 {0} 结束,SharedResource.Count={1}", Thread.CurrentThread.ManagedThreadId,SharedResource.Count); } } class SharedResource { public static int Count = 0; } }
using System; using System.Collections.Generic; using System.Text; using System.Threading; //展示用Monitor访问共享资源 namespace UseMonitor1 { class Program { static void Main(string[] args) { SharedResource obj = new SharedResource(); Thread[] ths = new Thread[4]; for (int i = 0; i < 4; i++) { ths[i] = new Thread(increaseCount); ths[i].Start(obj); } System.Console.ReadKey(); } static void increaseCount(Object obj) { //访问实例字段 VisitDynamicField(obj); //访问静态字段 VisitStaticField(); } //访问静态字段 private static void VisitStaticField() { //访问静态字段 Monitor.Enter(typeof(SharedResource)); int beginNumber = SharedResource.StaticCount; System.Console.WriteLine("线程 {0} 读到的StaticCount起始值为 {1} ", Thread.CurrentThread.ManagedThreadId, beginNumber); for (int i = 0; i < 10000; i++) { beginNumber++; } SharedResource.StaticCount = beginNumber; System.Console.WriteLine("线程 {0} 结束, SharedResource.StaticCount={1}", Thread.CurrentThread.ManagedThreadId, SharedResource.StaticCount); Monitor.Exit(typeof(SharedResource)); } //访问实例字段 private static void VisitDynamicField(Object obj) { Monitor.Enter(obj); int beginNumber = (obj as SharedResource).DynamicCount; System.Console.WriteLine("线程 {0} 读到的DynamicCount起始值为 {1} ", Thread.CurrentThread.ManagedThreadId, beginNumber); for (int i = 0; i < 10000; i++) { beginNumber++; } (obj as SharedResource).DynamicCount = beginNumber; System.Console.WriteLine("线程 {0} 结束,Obj.DynamicCount={1}", Thread.CurrentThread.ManagedThreadId, (obj as SharedResource).DynamicCount); Monitor.Exit(obj); } } //共享资源类 class SharedResource { public int DynamicCount = 0; //多线程共享的实例字段 public static int StaticCount = 0; //多线程共享的静态字段 } }Monitor类的使用模板:
using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace UseMonitor2 { class Program { static void Main(string[] args) { //创建共享资源 SharedResource obj = new SharedResource(); //创建线程对象并启动 Thread tha = new Thread(ThreadMethodA); Thread thb = new Thread(ThreadMethodB); tha.Start(obj); thb.Start(obj); //程序暂停 System.Console.ReadKey(); } static void ThreadMethodA(Object obj) { Monitor.Enter(obj); (obj as SharedResource).DynamicCount += 100; System.Console.WriteLine("线程A完成工作,obj.DynamicCount={0}", (obj as SharedResource).DynamicCount); Monitor.Pulse(obj); //通知B线程进入准备队列 Monitor.Exit(obj); } static void ThreadMethodB(Object obj) { Monitor.Enter(obj); //A线程还未工作,因为字段保持初始值0 //如果注释掉此条件判断语句,则有可能会发生死锁 if((obj as SharedResource).DynamicCount == 0) Monitor.Wait(obj);//将本线程阻塞,进入阻塞队列等待 (obj as SharedResource).DynamicCount += 100; System.Console.WriteLine("线程B完成工作,obj.DynamicCount={0}", (obj as SharedResource).DynamicCount); Monitor.Exit(obj); } } //共享资源类 class SharedResource { public int DynamicCount = 0; //多线程共享的实例字段 } }
using System.Threading; public class Program { static object ball = new object(); public static void Main() { Thread threadPing = new Thread( ThreadPingProc ); Thread threadPong = new Thread( ThreadPongProc ); threadPing.Start(); threadPong.Start(); } static void ThreadPongProc() { System.Console.WriteLine("ThreadPong: Hello!"); lock ( ball ) for (int i = 0; i < 5; i++) { System.Console.WriteLine("ThreadPong: Pong "); Monitor.Pulse( ball ); Monitor.Wait( ball ); } System.Console.WriteLine("ThreadPong: Bye!"); } static void ThreadPingProc() { System.Console.WriteLine("ThreadPing: Hello!"); lock ( ball ) for(int i=0; i< 5; i++) { System.Console.WriteLine("ThreadPing: Ping "); Monitor.Pulse( ball ); Monitor.Wait( ball ); } System.Console.WriteLine("ThreadPing: Bye!"); } }
ThreadPing: Hello! ThreadPing: Ping ThreadPong: Hello! ThreadPong: Pong ThreadPing: Ping ThreadPong: Pong ThreadPing: Ping ThreadPong: Pong ThreadPing: Ping ThreadPong: Pong ThreadPing: Ping ThreadPong: Pong ThreadPing: Bye!
//A线程执行的代码 lock(obj) { //访问共享资源obj Monitor.Pulse(obj); //通知B 线程可以访问共享资源obj了 } --------------------------------------------------------------- //B线程执行的代码 lock(obj) { Monitor.Wait(obj); //等待A 线程完成 //访问共享资源obj }
using System; using System.Collections.Generic; using System.Text; using System.Threading; //展示用Monitor访问共享资源 namespace UseMonitor1 { class Program { static void Main(string[] args) { SharedResource obj = new SharedResource(); Thread[] ths = new Thread[4]; for (int i = 0; i < 4; i++) { ths[i] = new Thread(increaseCount); ths[i].Start(obj); } System.Console.ReadKey(); } static void increaseCount(Object obj) { //访问实例字段 VisitDynamicField(obj); //访问静态字段 VisitStaticField(); } //访问静态字段 private static void VisitStaticField() { //访问静态字段 lock (typeof(SharedResource)) { int beginNumber = SharedResource.StaticCount; System.Console.WriteLine("线程 {0} 读到的StaticCount起始值为 {1} ", Thread.CurrentThread.ManagedThreadId, beginNumber); for (int i = 0; i < 10000; i++) { beginNumber++; } SharedResource.StaticCount = beginNumber; System.Console.WriteLine("线程 {0} 结束, SharedResource.StaticCount={1}", Thread.CurrentThread.ManagedThreadId, SharedResource.StaticCount); } } //访问实例字段 private static void VisitDynamicField(Object obj) { lock (obj) { int beginNumber = (obj as SharedResource).DynamicCount; System.Console.WriteLine("线程 {0} 读到的DynamicCount起始值为 {1} ", Thread.CurrentThread.ManagedThreadId, beginNumber); for (int i = 0; i < 10000; i++) { beginNumber++; } (obj as SharedResource).DynamicCount = beginNumber; System.Console.WriteLine("线程 {0} 结束,Obj.DynamicCount={1}", Thread.CurrentThread.ManagedThreadId, (obj as SharedResource).DynamicCount); } } } //共享资源类 class SharedResource { public int DynamicCount = 0; //多线程共享的实例字段 public static int StaticCount = 0; //多线程共享的静态字段 } }
public class MyType { //创建自旋锁对象 private SpinLock _spinLock = new SpinLock(); //将被多线程执行的代码, //由于使用了自旋锁,可以保证被“锁定”代码一次只会被一个线程执行 public void DoWork() { bool lockTaken = false; try { _spinLock.Enter(ref lockTaken); //申请获取“锁” // 获得了锁,在此书写工作代码,这些工作代码不会同时被两个线程执行 } finally { //工作完毕,或者发生异常时,检查一下当前线程是否占有了锁 //如果占有了锁,释放它,以避免出现死锁的情况。 if (lockTaken) _spinLock.Exit(); } } }
// Interlocked.cs // Interlocked示例 using System; using System.Threading; class Test { private long bufferEmpty = 0; private string buffer = null; static void Main() { Test t = new Test(); // 进行测试 t.Go(); } public void Go() { Thread t1 = new Thread(new ThreadStart(Producer)); t1.Name = "生产者线程"; t1.Start(); Thread t2 = new Thread(new ThreadStart(Consumer)); t2.Name = "消费者线程"; t2.Start(); // 等待两个线程结束 t1.Join(); t2.Join(); } // 生产者方法 public void Producer() { Console.WriteLine("{0}:开始执行", Thread.CurrentThread.Name); try { for (int j = 0; j < 16; ++j) { // 等待共享缓冲区为空 while (Interlocked.Read(ref bufferEmpty) != 0) Thread.Sleep(100); // 构造共享缓冲区 Random r = new Random(); int bufSize = r.Next() % 64; char[] s = new char[bufSize]; for (int i = 0; i < bufSize; ++i) { s[i] = (char)((int)'A' + r.Next() % 26); } buffer = new string(s); Console.WriteLine("{0}:{1}", Thread.CurrentThread.Name, buffer); // 互锁加一,成为1,标志共享缓冲区已满 Interlocked.Increment(ref bufferEmpty); // 休眠,将时间片让给消费者 Thread.Sleep(10); } Console.WriteLine("{0}:执行完毕", Thread.CurrentThread.Name); } catch (System.Threading.ThreadInterruptedException) { Console.WriteLine("{0}:被终止", Thread.CurrentThread.Name); } } // 消费者方法 public void Consumer() { Console.WriteLine("{0}:开始执行", Thread.CurrentThread.Name); try { for (int j = 0; j < 16; ++j) { while (Interlocked.Read(ref bufferEmpty) == 0) Thread.Sleep(100); // 打印共享缓冲区 Console.WriteLine("{0}:{1}", Thread.CurrentThread.Name, buffer); // 互锁减一,成为0,标志共享缓冲区已空 Interlocked.Decrement(ref bufferEmpty); // 休眠,将时间片让给生产者 Thread.Sleep(10); } Console.WriteLine("{0}:执行完毕", Thread.CurrentThread.Name); } catch (System.Threading.ThreadInterruptedException) { Console.WriteLine("{0}:被终止", Thread.CurrentThread.Name); } } }
using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace UseATM { class Program { static ATM OneATM=new ATM(); //共享资源 static void Main(string[] args) { //向公共帐号存款2万 Console.Write("输入公司公共帐户的金额:"); int PublicAcountMoney =Convert.ToInt32(Console.ReadLine()); OneATM.Deposit(PublicAcountMoney); Console.Write("输入ATM中的现金额:"); int ATMLeftMoney = Convert.ToInt32(Console.ReadLine()); OneATM.SetATMLeftMoney(ATMLeftMoney); System.Console.WriteLine("\n敲任意键从公共帐户中取钱,ESC键退出……\n"); while (System.Console.ReadKey(true).Key !=ConsoleKey.Escape) { System.Console.WriteLine(""); Thread One = new Thread(WithDrawMoney); Thread Two = new Thread(WithDrawMoney); Thread Three = new Thread(WithDrawMoney); //随机生成一个要提款的数额,最少100元,最高5000元 Random ran = new Random(); One.Start(ran.Next(100, 5000)); Two.Start(ran.Next(100, 5000)); Three.Start(ran.Next(100, 5000)); //等三人取完钱 One.Join(); Two.Join(); Three.Join(); System.Console.WriteLine("公共账号剩余{0}元,ATM中可提现金:{1}", OneATM.QueryPublicAccount(),OneATM.QueryATMLeftAccount()); } } //线程函数 static void WithDrawMoney(object amount) { switch(OneATM.WithDraw((int)amount)) { case WithDrawState.Succeed: System.Console.WriteLine("成功取出{0}元。",amount ); break; case WithDrawState.ATMHasNotEnoughCash: System.Console.WriteLine("ATM中现金不足,无法支取{0}元。", amount); break ; case WithDrawState.AccountHasNotEnoughMoney: System.Console.WriteLine("帐户中没钱了!无法取出{0}元",amount); break ; } } } //自助取款机 class ATM { private int PublicAcountLeftMoney;//帐户剩余的钱 private int ATMLeftMoney;//提款机剩余的钱 //同步信息号量 private Mutex m = new Mutex(); //取钱 public WithDrawState WithDraw(int amount) { m.WaitOne(); //公共帐号钱不够 if (PublicAcountLeftMoney < amount) { m.ReleaseMutex(); return WithDrawState.AccountHasNotEnoughMoney; } //ATM现金不够 if (ATMLeftMoney < amount) { m.ReleaseMutex(); return WithDrawState.ATMHasNotEnoughCash; } //用户可以提取现金 ATMLeftMoney -= amount; PublicAcountLeftMoney -= amount; m.ReleaseMutex(); return WithDrawState.Succeed; } //存钱 public void Deposit(int amount) { m.WaitOne(); PublicAcountLeftMoney += amount; m.ReleaseMutex(); } /// <summary> /// 设置ATM的现金金额 /// </summary> /// <param name="amount"></param> public void SetATMLeftMoney(int amount) { Interlocked.Exchange(ref ATMLeftMoney, amount); } //获取还剩余多少钱 public int QueryPublicAccount() { return PublicAcountLeftMoney; } /// <summary> /// 查询ATM剩余多少钱 /// </summary> /// <returns></returns> public int QueryATMLeftAccount() { return ATMLeftMoney; } } //取款状态 public enum WithDrawState { Succeed, //取钱成功 AccountHasNotEnoughMoney, //账号中没钱了 ATMHasNotEnoughCash //ATM中没有足够的现金 } }可能的运行结果:
输入公司公共帐户的金额:200000 输入ATM中的现金额:6000000 敲任意键从公共帐户中取钱,ESC键退出…… 成功取出1249元。 成功取出643元。 成功取出4958元。 公共账号剩余193150元,ATM中可提现金:5993150 成功取出1168元。 成功取出3650元。 成功取出2707元。 公共账号剩余185625元,ATM中可提现金:5985625 成功取出3866元。 成功取出402元。 成功取出2397元。 公共账号剩余178960元,ATM中可提现金:5978960 成功取出4485元。 成功取出1701元。 成功取出3354元。 公共账号剩余169420元,ATM中可提现金:5969420
// Mutex1.cs // Mutex1示例 using System; using System.IO; using System.Threading; using System.Diagnostics; class Test { static void Main() { Test t = new Test(); // 进行测试 t.Go(); } public void Go() { // 创建并启动线程 Thread t1 = new Thread(new ThreadStart(Producer)); t1.Name = "生产者线程"; t1.Start(); // 等待线程结束 t1.Join(); Console.WriteLine("按Enter键退出..."); Console.Read(); } // 生产者方法 public void Producer() { Console.WriteLine("{0}:开始执行", Thread.CurrentThread.Name); // 创建互斥体 Mutex mutex = new Mutex(false, "CSharp_Mutex_test"); // 启动消费者进程 Process.Start("Mutex2.exe"); for (int j = 0; j < 16; ++j) { try { // 进入互斥体 mutex.WaitOne(); FileStream fs = new FileStream(@"d:\text.txt", FileMode.OpenOrCreate, FileAccess.Write); StreamWriter sw = new StreamWriter(fs); // 构造字符串 Random r = new Random(); int bufSize = r.Next() % 64; char[] s = new char[bufSize]; for (int i = 0; i < bufSize; ++i) { s[i] = (char)((int)'A' + r.Next() % 26); } string str = new string(s); // 将字符串写入文件 sw.WriteLine(str); sw.Close(); Console.WriteLine("{0}:{1}", Thread.CurrentThread.Name, str); } catch (System.Threading.ThreadInterruptedException) { Console.WriteLine("{0}:被终止", Thread.CurrentThread.Name); break; } finally { // 退出互斥体 mutex.ReleaseMutex(); } // 休眠,将时间片让给消费者 Thread.Sleep(1000); } // 关闭互斥体 mutex.Close(); Console.WriteLine("{0}:执行完毕", Thread.CurrentThread.Name); } }消费者线程所在应用程序代码:
// Mutex2.cs // Mutex2示例 using System; using System.IO; using System.Threading; class Test { static void Main() { Test t = new Test(); // 进行测试 t.Go(); } public void Go() { // 创建并启动线程 Thread t2 = new Thread(new ThreadStart(Consumer)); t2.Name = "消费者线程"; t2.Start(); // 等待线程结束 t2.Join(); Console.WriteLine("按Enter键退出..."); Console.Read(); } // 消费者方法 public void Consumer() { Console.WriteLine("{0}:开始执行", Thread.CurrentThread.Name); // 创建互斥体 Mutex mutex = new Mutex(false, "CSharp_Mutex_test"); for (int j = 0; j < 16; ++j) { try { // 进入互斥体 mutex.WaitOne(); StreamReader sr = new StreamReader(@"d:\text.txt"); string s = sr.ReadLine(); sr.Close(); // 显示字符串的值 Console.WriteLine("{0}:{1}", Thread.CurrentThread.Name, s); } catch (System.Threading.ThreadInterruptedException) { Console.WriteLine("{0}:被终止", Thread.CurrentThread.Name); break; } finally { // 退出互斥体 mutex.ReleaseMutex(); } // 休眠,将时间片让给消费者 Thread.Sleep(1000); } // 关闭互斥体 mutex.Close(); Console.WriteLine("{0}:执行完毕", Thread.CurrentThread.Name); } }
using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace UseLibraryComputer { class Program { //图书馆拥有的公用计算机 private const int ComputerNum = 3; private static Computer[] LibraryComputers; //同步信号量 public static Semaphore sp = new Semaphore( ComputerNum, ComputerNum); static void Main(string[] args) { //图书馆拥有ComputerNum台电脑 LibraryComputers = new Computer[ComputerNum]; for (int i = 0; i <ComputerNum; i++) LibraryComputers[i] = new Computer("Computer"+(i+1).ToString()); int peopleNum = 0; Random ran=new Random(); Thread user; System.Console.WriteLine("敲任意键模拟一批批的人排队使用{0}台计算机,ESC键结束模拟……" ,ComputerNum); //每次创建若干个线程,模拟人排队使用计算机 while (System.Console.ReadKey().Key != ConsoleKey.Escape) { peopleNum = ran.Next(0, 10); System.Console.WriteLine("\n有{0}人在等待使用计算机。",peopleNum ); for (int i = 1; i <= peopleNum; i++) { user = new Thread(UseComputer); user.Start("User" + i.ToString()); } } } //线程函数 static void UseComputer(Object UserName) { sp.WaitOne();//等待计算机可用 //查找可用的计算机 Computer cp=null; for (int i = 0; i < ComputerNum; i++) if (LibraryComputers[i].IsOccupied == false) { cp = LibraryComputers[i]; break; } //使用计算机工作 cp.Use(UserName.ToString()); //不再使用计算机,让出来给其他人使用 sp.Release(); } } class Computer { public readonly string ComputerName = ""; public Computer(string Name) { ComputerName = Name; } //是否被占用 public bool IsOccupied = false; //人在使用计算机 public void Use(String userName) { System.Console.WriteLine("{0}开始使用计算机{1}", userName,ComputerName); IsOccupied = true; Thread.Sleep(new Random().Next(1, 2000)); //随机休眠,以模拟人使用计算机 System.Console.WriteLine("{0}结束使用计算机{1}", userName,ComputerName); IsOccupied = false; } } }可能的运行结果: