线程同步也称线程管理。
在线程的生命周期中,线程的状态并非一成不变的,它可能经历多种状态。线程的状 态由 Thread 类的 ThreadState 属性表示。
线程同步就是需要我们手动设置线程的不同状态来实现。
在大多数时候,计算机中都会有多个线程并发运行。有些线程之间是没有任何联系的, 它们各自独立运行,互不干扰,这类线程称为无关线程。而有些线程之间则是有联系的, 比如一个线程等待另一个线程的运算结果,两个线程共享一个资源等,这种线程称为相关 线程。为了避免相关线程之间对资源,数据的无序操作。我们需要作为管理员调度他们的操作时机,让他们有序进行。
例如在一个线程中进行写入操作,另外一个线程进行读取操作
我们所期望的状态是Writer放入一个,Reader拿走一个。但不加控制的请款下输出的结果出现了混乱,这是因为有多个线程交替执行,它们被执行的时间是不确定的。可能线程 writer 连 续写入好几次字符,线程 reader 才读取一次;也可能线程 reader 读取了好几次,线程 writer 才写入一次。这样都得不到正确的结果
如何才能保证两个线程同步呢?.NET 框架为我们提供了一系列的同步类,最常用的 包括 Interlocked(互锁)、Monitor(管程)和 Mutex(互斥体)。
互锁(Interlocked类)
private static long numberOfUsedSpace = 0;
//线程:写者
Thread writer = new Thread(delegate(){
//等待,直到缓冲区中的数据被进程 reader 读取为止
while(Interlocked.Read(ref numberOfUsedSpace) == 1){
Thread.Sleep(10);
}
//写入...
Interlocked.Increment(ref numberOfUsedSpace);//读取数据后把缓冲区标记为1(由 0 变为 1)
});
//线程:读者
Thread reader = new Thread(delegate(){
//行等待,直到进程 writer 向缓冲区中写入数据为止
while(Interlocked.Read(ref numberOfUsedSpace) == 0){
Thread.Sleep(10);
}
//从缓冲区中读取信息
Interlocked.Decrement(ref numberOfUsedSpace); //读取数据后把缓冲区标记为空(由 1 变为 0)
});
在上面的例子中我们定义了这么一个变量
private static long numberOfUsedSpace = 0;
通过读取和设置这个变量的计数来判断读写的状态,控制线程的等待和执行时机。
管程(Monitor类)
//用于同步的对象(独占锁)
private static object lockForBuffer = new object();
//线程:写者
Thread writer = new Thread(delegate(){
lock(lockForBuffer){
//向缓冲区写入数据...
//唤醒睡眠在临界资源上的线程
Monitor.Pulse(lockForBuffer);
//让当前线程睡眠在临界资源上
Monitor.Wait(lockForBuffer);
}
});
//线程:读者
Thread reader = new Thread(delegate() {
lock (lockForBuffer){
//从缓冲区读取数据...
//唤醒睡眠在临界资源上的线程
Monitor.Pulse(lockForBuffer);
//让当前线程睡眠在临界资源上
Monitor.Wait(lockForBuffer);
}
}
});
在上面的例子中我们定义了这么一个变量
private static object lockForBuffer = new object();
通过对这个变量的设置我们可以控制当有一个线程在访问资源的时候,会将资源区“上锁”。其它线程只能等待“叫号”。
互斥体(Mutex类)
Thread threadA=new Thread(delegate()
{
//创建互斥体
Mutex fileMutex = new Mutex(false, "MutexForTimeRecordFile");
try{
//请求互斥体的所属权,若成功,则进入临界区,若不成功,则等待
fileMutex.WaitOne();
//写入数据
}
catch (System.Threading.ThreadInterruptedException){
Console.WriteLine("线程 A 被中断。");
}
finally{
fileMutex.ReleaseMutex(); //释放互斥体的所属权
}
Thread.Sleep(1000);
});
Thread threadB = new Thread(delegate()
{
//创建互斥体
Mutex fileMutex = new Mutex(false, "MutexForTimeRecordFile");
try{
//请求互斥体的所属权,若成功,则进入临界区,若不成功,则等待
fileMutex.WaitOne();
//写入
}
catch (System.Threading.ThreadInterruptedException){
Console.WriteLine("线程 B 被中断。");
}
finally{
fileMutex.ReleaseMutex(); //释放互斥体的所属权
}
Thread.Sleep(1000);
});
在上面的例子中我们定义了这么一个变量
Mutex fileMutex = new Mutex(false, "MutexForTimeRecordFile");
看起来两个线程各自建立了一个局部的变量。互斥体有两种类型:局部互斥体和系统互斥体。局部互斥体只能在创建它的程序中使 用,而系统互斥体则能被系统中不同的应用程序共享。如何创建系统互斥体呢?只需在构造函数中为互斥体对象起一个“系统名称”即可,本例中"MutexForTimeRecordFile"即为系统名称。
总结:
介绍了这么多的线程同步的方法,说起来很繁杂,其实概括起来很简单。都是通过设置和获取某个变量的值来设置线程的状态,这和平时我们使用标志位判断fang方法上并没有多差别。