当一个进程启动了多个线程时,如果需要控制这些线程的推进顺序(比如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();
}
///
/// 设置ATM的现金金额
///
///
public void SetATMLeftMoney(int amount)
{
Interlocked.Exchange(ref ATMLeftMoney, amount);
}
//获取还剩余多少钱
public int QueryPublicAccount()
{
return PublicAcountLeftMoney;
}
///
/// 查询ATM剩余多少钱
///
///
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
可能的运行结果: