下面是一个展示如何使用
lock
关键字和
Monitor
类来实现线程的同步和通讯的例子,也是一个典型的生产者与消费者问题。这个例程中,生产者线程和消费者线程是交替进行的,生产者写入一个数,消费者立即读取并且显示,我将在注释中介绍该程序的精要所在。用到的系统命名空间如下:
using System;
using System.Threading;
|
首先,我们定义一个被操作的对象的类
Cell
,在这个类里,有两个方法:
ReadFromCell()
和
WriteToCell
。消费者线程将调用
ReadFromCell()
读取
cellContents
的内容并且显示出来,生产者进程将调用
WriteToCell()
方法向
cellContents
写入数据。
public class Cell
{
int cellContents; // Cell
对象里边的内容
bool readerFlag = false; //
状态标志,为
true
时可以读取,为
false
则正在写入
public int ReadFromCell( )
{
lock(this) // Lock
关键字保证了什么,请大家看前面对
lock
的介绍
{
if (!readerFlag)//
如果现在不可读取
{
try
{
file://
等待
WriteToCell
方法中调用
Monitor.Pulse()
方法
Monitor.Wait(this);
}
catch (SynchronizationLockException e)
{
Console.WriteLine(e);
}
catch (ThreadInterruptedException e)
{
Console.WriteLine(e);
}
}
Console.WriteLine("Consume: {0}",cellContents);
readerFlag = false; file://
重置
readerFlag
标志,表示消费行为已经完成
Monitor.Pulse(this); file://
通知
WriteToCell()
方法(该方法在另外一个线程中执行,等待中)
}
return cellContents;
}
public void WriteToCell(int n)
{
lock(this)
{
if (readerFlag)
{
try
{
Monitor.Wait(this);
}
catch (SynchronizationLockException e)
{
file://
当同步方法(指
Monitor
类除
Enter
之外的方法)在非同步的代码区被调用
Console.WriteLine(e);
}
catch (ThreadInterruptedException e)
{
file://
当线程在等待状态的时候中止
Console.WriteLine(e);
}
}
cellContents = n;
Console.WriteLine("Produce: {0}",cellContents);
readerFlag = true;
Monitor.Pulse(this); file://
通知另外一个线程中正在等待的
ReadFromCell()
方法
}
} }
|
下面定义生产者
CellProd
和消费者类
CellCons
,它们都只有一个方法
ThreadRun()
,以便在
Main()
函数中提供给线程的
ThreadStart
代理对象,作为线程的入口。
public class CellProd
{
Cell cell; //
被操作的
Cell
对象
int quantity = 1; //
生产者生产次数,初始化为
1
public CellProd(Cell box, int request)
{
//
构造函数
cell = box;
quantity = request;
}
public void ThreadRun( )
{
for(int looper=1; looper<=quantity; looper++)
cell.WriteToCell(looper); file://
生产者向操作对象写入信息
} }
public class CellCons
{
Cell cell;
int quantity = 1;
public CellCons(Cell box, int request)
{
cell = box;
quantity = request;
}
public void ThreadRun( )
{
int valReturned;
for(int looper=1; looper<=quantity; looper++)
valReturned=cell.ReadFromCell( );//
消费者从操作对象中读取信息
} }
|
然后在下面这个类
MonitorSample
的
Main()
函数中我们要做的就是创建两个线程分别作为生产者和消费者,使用
CellProd.ThreadRun()
方法和
CellCons.ThreadRun()
方法对同一个
Cell
对象进行操作。
public class MonitorSample
{
public static void Main(String[] args)
{
int result = 0; file://
一个标志位,如果是
0
表示程序没有出错,如果是
1
表明有错误发生
Cell cell = new Cell( );
//
下面使用
cell
初始化
CellProd
和
CellCons
两个类,生产和消费次数均为
20
次
CellProd prod = new CellProd(cell, 20);
CellCons cons = new CellCons(cell, 20);
Thread producer = new Thread(new ThreadStart(prod.ThreadRun));
Thread consumer = new Thread(new ThreadStart(cons.ThreadRun));
//
生产者线程和消费者线程都已经被创建,但是没有开始执行
try
{
producer.Start( );
consumer.Start( );
producer.Join( );
consumer.Join( );
Console.ReadLine();
}
catch (ThreadStateException e)
{
file://
当线程因为所处状态的原因而不能执行被请求的操作
Console.WriteLine(e);
result = 1;
}
catch (ThreadInterruptedException e)
{
file://
当线程在等待状态的时候中止
Console.WriteLine(e);
result = 1;
}
//
尽管
Main()
函数没有返回值,但下面这条语句可以向父进程返回执行结果
Environment.ExitCode = result;
} }
|
大家可以看到,在上面的例程中,同步是通过等待
Monitor.Pulse()
来完成的。首先生产者生产了一个值,而同一时刻消费者处于等待状态,直到收到生产者的
“
脉冲
(Pulse)”
通知它生产已经完成,此后消费者进入消费状态,而生产者开始等待消费者完成操作后将调用
Monitor.Pulese()
发出的
“
脉冲
”
。它的执行结果很简单:
Produce: 1
Consume: 1
Produce: 2
Consume: 2
Produce: 3
Consume: 3
...
...
Produce: 20
Consume: 20
|
事实上,这个简单的例子已经帮助我们解决了多线程应用程序中可能出现的大问题,只要领悟了解决线程间冲突的基本方法,很容易把它应用到比较复杂的程序中去