目录
3.4 线程同步事件类 —— EventWaitHandle
线程同步技术主要包括两个方面的内容:
一、多个线程推进顺序的控制问题
二、访问共享资源的问题
1 死锁与数据存取错误
1.1 多线程程序中的 “死锁” 现象
Join 方法如果使用不当,就会在线程之间形成死锁状态。以下代码为典型的死锁实例:
static Thread mainThread;
static void Main(string[] args)
{
mainThread = Thread.CurrentThread;
Thread m_objThread = new Thread(__PlayThread);
m_objThread.Start();
m_objThread.Join();
Console.WriteLine("mainThread");
}
static void __PlayThread()
{
mainThread.Join();
Console.WriteLine("__PlayThread");
}
注意突出显示的两句代码:主线程在等待线程 A 运行结束,而线程 A 在运行过程中又等待主线程的运行结束。当死锁发生时,两个或多个线程相互等待,所有线程都无法再前进一步,程序会失去响应。
1. 线程推进循序不当造成线程间的循环等待。
2. 多个线程访问共享的资源。
1.2 多线程引发的数据存取错误
单 CPU 时,多线程访问 Info 类对象,一个线程在访问 Info.Result 属性时,在判断 if (num2 != 0)为true 后,可能在 NO.1 之后和 NO.2 之前处出现中断(线程挂起),此时另一个线程通过 DoSomething 方法修改 num2 的值为 0,中断恢复后,程序报错。双 CPU中,多线程访问 Info 类对象,情况更糟,一个线程访问 Info.Result 属性时,不管在 NO.1 之后和 NO.2 之前会不会中断,另一个线程都有可能通过 DoSomething 方法修改 num2 的值为0,程序报错。
class Info
{
public int num1;
public int num2;
public int Result
{
get
{
if (num2 != 0) // NO.1
{
return num1 / num2; // NO.2
}
else
{
return 0;
}
}
}
public void DoSomething(int a1,int a2)
{
num1 = a1;
num2 = a2;
}
}
2 锁
访问共享资源,常用的方法是给资源加 “锁”,加上锁的共享资源一次只允许一个线程访问。从而避免多线程同时存取共享资源所带来的数据存取错误问题,付出的代价是程序性能的下降。
2.1 锁定共享资源 —— Monitor
由于多个线程同时访问共享数据时会造成数据存取错误,解决这一问题的方法是给共享的资源加上一把 “锁”,一次只允许一个线程访问共享资源。
Monitor 类可用于给共享资源加 “锁”。
如下代码所示,定义一个共享资源类:
class SharedResource
{
public int InstanceCount = 0;
public static int StaticCount = 0;
}
访问多线程共享的实例字段
private static void __PlayThread(object obj)
{
// 访问实例字段
Monitor.Enter(obj); // 加锁
int beginNumber = (obj as SharedResource).InstanceCount;
Monitor.Exit(obj); // 解锁
}
线程函数可能会被多个线程同时执行,在代码开头,使用 Monitor 类的 Enter 方法申请对共享对象 obj 的 “独家使用权” (即 “对象锁” ),之后就可以放心地访问它,访问完后,再调用 Monitor 类的 Exit 方法放弃对共享对象 obj 的 “独家使用权”。
在调用 Monitor 类的 Enter 方法申请对共享对象 obj 的 “独家使用权” 时,如果此对象已被其它线程所使用,即 “对象锁” 为另一线程所拥有,则申请锁的线程必须等待对方放弃 “对象锁” 之后(通过调用 Monitor 类的 Exit 方法实现),才可以继续运行。
由于共享对象加锁的功能非常常用,所以 C# 甚至在语言级别就提供了于 Monitor 类的 Enter 和 Eixt 方法等价的关键字 —— lock。
其格式为:
lock (obj)
{
// 访问共享资源的代码
}
它完全等同于以下代码段:
try
{
Monitor.Enter(obj);
// 访问共享资源的代码......
}
finally
{
Monitor.Exit(obj);
}
因此,在开发中推荐使用 lock 关键字而不是直接使用 Monitor 类。
访问多线程共享的静态字段
private static void __PlayThread(object obj)
{
// 访问静态字段
Monitor.Enter(typeof(SharedResource));
int beginNumber = SharedResource.StaticCount;
Monitor.Exit(typeof(SharedResource));
}
使用 Monitor 控制线程的推进顺序
如果访问共享资源的多个线程间有着顺序关系,比如要求 A 线程先访问,之后 B 线程才可以访问,则可以使用 Monitor 类的 Wait 和 Pulse 方法。其代码框架如下:
// A线程执行的代码
lock(ob