多线程编程笔记
操作线程
1、操作实例
using System;
using System.Threading;
namespace AppThreadDemo
{
class App
{
static public void threadMethod()
{
Console.WriteLine("\n执行ThreadMethod的线程的代
码为:{0}",Thread.CurrentThread.GetHashCode().ToString());
Thread.Sleep(1000); //休眠1秒
}
static public void ThreadCreate()
{
Console.WriteLine("\n执行ThreadCreate的线程的代
码为:{0}",Thread.CurrentThread.GetHashCode().ToString());
//用线程体实例化Thread 代理类
ThreadStart entry = new ThreadStart(App.threadMethod);
Thread thread = new Thread(entry);
thread.Start();
thread.Join();//等待线程结束
}
static public void Main()
{
ThreadCreate();
}
}
}
注意:
A、多个线程可以执行同一个线程体:
Thread thread = new Thread(entry);
Thread thread1 = new Thread(entry);
thread.Start();
thread1.Start();
thread.Join();
thread2.Join();
B、一个线程体执行结束后,不能调用其Start再次启动,试图通过对已终止的线程调用 Start 来重新启动已中止的线程将引发 ThreadStateException。
C、一个启动了的线程不能再次启动。试图通过对已运行的线程调用 Start 来重新启动已中止的线程将引发 ThreadStateException。
(对于B和C,《.net核心技术--原理与架构》中描述为SecurityException异常,用编译器是ThreadStateException异常。应该是笔误吧!)
2、结束线程
A、Abort
调用此方法后,在当前的线程上引发 ThreadAbortException,该异常终止此线程的过程。调用此方法通常会终止线程。
如果对尚未启动的线程调用 Abort,则在线程调用 Start 后再调用Abort 将该线程将中止。例如:
ThreadStart entry = new ThreadStart(App.threadMethod);
Thread thread = new Thread(entry);
Console.WriteLine("终止线程");
thread.Abort();
Console.WriteLine("启动线程");
thread.Start();
thread.Join();
Console.WriteLine("结束线程");
输出为:
终止线程
启动线程
结束线程
可以看出,执行这段代码后,Thread刚启动就马上死亡。 如果对已挂起的线程调用 Abort,则线程被重新开始后再执行Abort将该线程将中止。如果对被阻塞或正在休眠的线程调用 Abort,则该线程被中断然后中止。
下面这段代码没有进行异常处理,所以异常向外传递,直到最后由公共运行库捕获。
using System;
using System.Threading;
namespace AppThreadDemo
{
class App
{
static public void threadMethod()
{
Console.WriteLine("\n执行ThreadMethod的线程的代
码为:{0}",Thread.CurrentThread.GetHashCode().ToString());
Thread.Sleep(1000);
Console.WriteLine("threadMethod线程结束");
}
static public void ThreadCreate()
{
Console.WriteLine("\n执行ThreadCreate的线程的代
码为:{0}",Thread.CurrentThread.GetHashCode().ToString());
ThreadStart entry = new ThreadStart(App.threadMethod);
Thread thread = new Thread(entry);
Console.WriteLine("终止线程");
Console.WriteLine("启动线程");
thread.Start();
Thread.Sleep(500);//主线程休眠0.5秒,等待子线程运行
thread.Abort();
thread.Join();
Console.WriteLine("主线程结束");
}
static public void Main()
{
ThreadCreate();
}
}
}
输出为:
执行ThreadCreate的线程的代码为:1
终止线程
启动线程
执行ThreadMethod的线程的代码为:2
主线程结束
在threadMethod中,启动子线程后,主线程休眠0.5等待子线程执行,子线程执行threadMethod的第1句代码后,休眠1秒等待主线程运行。此当主线程休眠并调用Abort方法结束子线程的时候,子线程仍处于休眠状态。着时候子线程被打断,并结束。所以threadMethod的最后没机会执行。
若将threadMethod进行异常处理,
static public void threadMethod()
{
try
{
Console.WriteLine("\n执行ThreadMethod的线程的代
码为:{0}",Thread.CurrentThread.GetHashCode().ToString());
Thread.Sleep(1000);
}
catch(Exception e)
{
Console.WriteLine("捕获到异常:{0}",e.ToString());
}
finally
{
Console.WriteLine("threadMethod执行了finally块");
}
//这句在调用Abort是不会执行到的;Interrupt会执行到的
Console.WriteLine("threadMethod线程结束"); }
线程体就会捕获到了一个ThreadAbortException异常,而且finally块也执行了。如果在执行非托管代码时线程忽略 ThreadAbortException,则当线程开始执行托管代码时,系统将再次引发 ThreadAbortException。
B、Interrupt
该方法打断一个处于WaitSleepJoin状态的线程。在当前的线程调用该方法后产生ThreadInterruptedException异常。与Abort结束线程不同,调用Abort结束线程的方法将使该线程执行完Finally语句就结束,而调用Interrupt方法则允许线程执行完整个线程体后结束。如果线程体不采取异常处理措施,则线程在被打断的时候立刻结束。如果线程不处于WaitSleepJoin状态,则线程下次处于WaitSleepJoin时被打断。
线程池
用线程池编写多线程应用一般步骤:
1、编写工作任务:将所有的工作任务泛放在一个符合WaitCallback接口的函数中
public static void MyAsyncOperation(object state)
{
//...
//要完成的工作任务
}
一般通过state传入一个同步对象
2、往线程池中添加工作项
ThreadPool.QueueUserWorkItem(new WaitCallback(MyAsyncOperation),asyncOpIsDone);
此时将第2个参数asyncOpIsDone作为MyAsyncOperation的state参数执行该委托实例。
3、和工作线程通信
比较典型的做法是主线程传入一个同步对象到工作线程,工作线程在完成任务后利用传入的同步对象通知主线程。
线程池中的线程是按需创建达到。公共运行库根据线程池中工作任务的多少创建合适的线程来处理工作任务。
注意:为了减轻系统的负担,应尽量使每个工作任务尽快的结束。对于那些需要大量CPU时间的工作任务,可利用Thread类创建自由线程来执行。
例程:
using System;
using System.Threading;
namespace AsyncMethod
{
class App
{
public static void MyAsyncOperation(object state)
{
Console.WriteLine("工作任务");
//....
Console.WriteLine("睡眠5秒");
Thread.Sleep(5000);
((AutoResetEvent)state).Set();//将指定事件的状态设置为终止。指示工作认为已完成
Console.WriteLine("工作任务结束");
}
public static void Main()
{
Console.WriteLine("主线程进行异步调用");
AutoResetEvent asyncOpIsDone = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(new WaitCallback(MyAsyncOperation),asyncOpIsDone);
Console.WriteLine("主线程执行其他任务");
//....
Console.WriteLine("主线程等待工作任务结束");
asyncOpIsDone.WaitOne();//阻塞当前线程,直到当前的 WaitHandle 收到信号。
}
}
}
//下面程序,工作线程和主线程均在同一个应用域中,公共运行库创建了2个工作线程来完成工作线程池中的2个任务。
using System;
using System.Threading;
namespace demoThreadPool
{
class App
{
public static void MyWorkItem(object state)
{
Console.WriteLine("\n当前线程的代码:{0}",Thread.CurrentThread.GetHashCode().ToString());
Console.WriteLine("当前应用域的名称为:{0}",AppDomain.CurrentDomain.FriendlyName);
Thread.Sleep(1000);
((AutoResetEvent)state).Set();
}
public static void demoThreadPool()
{
Console.WriteLine("当前线程的代码:{0}",Thread.CurrentThread.GetHashCode().ToString());
Console.WriteLine("当前应用域的名称为:{0}",AppDomain.CurrentDomain.FriendlyName);
AutoResetEvent asyncOpIsDone = new AutoResetEvent(false);
AutoResetEvent asyncOpIsDone2 = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(new WaitCallback(MyWorkItem),asyncOpIsDone);
ThreadPool.QueueUserWorkItem(new WaitCallback(MyWorkItem),asyncOpIsDone2);
WaitHandle[] handles= new WaitHandle[2];
handles[0] = asyncOpIsDone;
handles[1] = asyncOpIsDone2;
WaitHandle.WaitAll(handles);//此语句也可以用下面语句代替
//handles[0].WaitOne();
//handles[1].WaitOne();
}
public static void Main()
{
demoThreadPool();
}
}
}
方法说明:
public AutoResetEvent(
bool initialState
);
//用一个指示是否将初始状态设置为终止的布尔值初始化 AutoResetEvent 类的新实例。
//initialState
//如果为 true,则将初始状态设置为终止;如果为 false,则将初始状态设置为非终止。
//使用 AutoResetEvent 类可以使某个线程等待,直到某个事件通过调用 AutoResetEvent.Set 将其置于已发信号的状态为止。与 ManualResetEvent 不同,AutoResetEvent 在单个等待线程被释放后由系统自动重置为未发信号的状态。如果没有任何线程正在等待,则该事件对象的状态将保持已发信号的状态。AutoResetEvent 与 Win32 CreateEvent 调用相对应,并将 bManualReset 参数指定为 false。
//还要注意WaitOne,WaitAll,WaitAny的用
互斥
1、Monitor.Enter和Monitor.Exit
Monitor 类通过向单个线程授予对象锁来控制对对象的访问。对象锁提供限制访问代码块(通常称为临界区)的能力。当一个线程拥有对象的锁时,其他任何线程都不能获取该锁。还可以使用 Monitor 来确保不会允许其他任何线程访问正在由锁的所有者执行的应用程序代码节,除非另一个线程正在使用其他的锁定对象执行该代码。
注意:使用 Monitor 锁定对象(即引用类型)而不是值类型。
Monitor 具有以下功能:
它根据需要与某个对象相关联。
它是未绑定的,也就是说可以直接从任何上下文调用它。
不能创建 Monitor 类的实例。
将为每个同步对象来维护以下信息:
对当前持有锁的线程的引用。
对就绪队列的引用,它包含准备获取锁的线程。
对等待队列的引用,它包含正在等待锁定对象状态变化通知的线程。
使用 Enter 和 Exit 方法标记临界区的开头和结尾。
如果临界区是一个连续指令集,则由 Enter 方法获取的锁将保证只有一个线程可以使用锁定对象执行所包含的代码。在这种情况下,建议您将这些指令放在 try 块中,并将 Exit 指令放在 finally 块中。此功能通常用于同步对类的静态或实例方法的访问。
如果实例方法需要同步线程访问,则它将使用当前实例作为要锁定的对象调用 Enter 和对应的 Exit 方法。由于只能有一个线程持有当前实例上的锁,因此该方法一次只能由一个线程来执行。
静态方法是使用当前实例的 Type 作为锁定对象以类似的方式来保护的。Enter 和 Exit 方法提供的功能与 C# lock 语句提供的功能相同。
如果临界区跨越整个方法,则可以通过将 System.Runtime.CompilerServices.MethodImplAttribute 放置在方法上并在 MethodImplAttribute 的构造函数中指定 Synchronized 值来实现上述锁定功能。使用该属性后就不需要 Enter 和 Exit 语句了。请注意,该属性将使当前线程持有锁,直到方法返回;如果可以更早释放锁,则使用 Monitor 类或 C# lock 语句而不是该属性。
尽管锁定和释放给定对象的 Enter 和 Exit 语句可以跨越成员或类的边界或同时跨越两者的边界,但并不推荐这样做。
当选择要同步的对象时,应只锁定私有或内部对象。锁定外部对象可能导致死锁,这是因为不相关的代码可能会出于不同的目的而选择锁定相同的对象。
//实例方法同步线程访问
public class Account
{
int val;
public void Deposit(int x)
{
Monitor.Enter(this);
try
{
val += x;
}
finally
{
Monitor.Exit(this);
}
}
public void WithDraw(int x)
{
Monitor.Exiter(this);
try
{
val -= x;
}
finally
{
Monitor.Exit(this);
}
}
}
//静态方法同步线程访问
public class DemoStatic
{
public static int count =0;
}
public class DemoStaticLock
{
public static void Demo()
{
try
{
Monitor.Enter(typeof(DemoStatic));
DemoStatic.count++;
}
catch(Exception e)
{
Console.WriteLine("捕获异常{0}",e.ToString());
}
finally
{
Monitor.Exit(typeof(DemoStatic));
}
}
}
2、Lock/SyncLock语句
Lock/SyncLock关键字将某个语句标志为临界区。
//实例方法同步线程访问
public class Account
{
int val;
public void Deposit(int x)
{
lock(this)
{
val += x;
}
}
public void WithDraw(int x)
{
lock(this)
{
val -= x;
}
}
}
3、ReaderWriterLock
ReaderWriterLock 用于同步对资源的访问。在任一特定时刻,它允许多个线程同时进行读访问,或者允许单个线程进行写访问。在资源不经常发生更改的情况下,ReaderWriterLock 所提供的吞吐量比简单的一次只允许一个线程的锁(如 Monitor)更高。
在多数访问为读访问,而写访问频率较低、持续时间也比较短的情况下,ReaderWriterLock 的性能最好。多个读线程与单个写线程交替进行操作,所以读线程和写线程都不会长时间阻塞。
注意:长时间持有读线程锁或写线程锁会使其他线程发生饥饿 (starve)。为了得到最好的性能,需要考虑重新构造应用程序以将写访问的持续时间减少到最小。
一个线程可以持有读线程锁或写线程锁,但是不能同时持有两者。若要获取写线程锁,请使用 UpgradeToWriterLock 和 DowngradeFromWriterLock,而不要通过释放读线程锁的方式获取。
递归锁请求会增加锁上的锁计数。
读线程和写线程将分别排入各自的队列。当线程释放写线程锁时,此刻读线程队列中的所有等待线程都将被授予读线程锁;当已释放所有读线程锁时,写线程队列中处于等待状态的下一个线程(如果存在)将被授予写线程锁,依此类推。换句话说,ReaderWriterLock 在一组读线程和一个写线程之间交替进行操作。
当写线程队列中有一个线程在等待活动读线程锁被释放时,请求新的读线程锁的线程会排入读线程队列。即使它们能和现有的读线程锁持有者同时一起访问,也不会授予它们的请求;这可以防止写线程被读线程无限期阻塞。
大多数在 ReaderWriterLock 上获取锁的方法都采用超时值。使用超时可以避免应用程序中出现死锁。例如,某个线程可能获取了一个资源上的写线程锁,然后请求第二个资源上的读线程锁;同时,另一个线程获取了第二个资源上的写线程锁,并请求第一个资源上的读线程锁。如果不使用超时,这两个线程将出现死锁。
如果超时间隔过期并且没有授予锁请求,则此方法通过引发 ApplicationException 将控制返回给调用线程。线程可以捕捉此异常并确定下一步要进行的操作。
超时用毫秒表示。如果使用 System.TimeSpan 指定超时,则所用的值是 TimeSpan 所表示的毫秒整数的总和。
下面显示用毫秒表示的有效超时值。
值 说明
-1 Infinite.
0 无超时。
> 0 要等待的毫秒数。
除了 -1 以外,不允许使用负的超时值。如果要使用 -1 以外的负整数来指定超时,系统将使用零(无超时)。如果指定的 TimeSpan 表示的是 -1 以外的负毫秒数,将引发 ArgumentOutOfRangeException。
使用ReaderWriterLock实现互斥:首先实例化ReaderWriterLock;然后在读取临界资源前调用AcquireReaderLock方法,读过程结束后,调用ReleaseReaderLock释放读锁定;在修改临界资源之前,调用AcquireWriterLock方法请求写锁定,在写过程结束后,调用ReleaseWriterLock方法释放写锁定。
//例子
public class Account
{
int val ;
ReaderWriterLock rwl = new ReaderWriterLock();
public int Read()
{
rwl.AcquireReaderLock(Timeout.Infinite);
int iRet = 0;
try
{
iRet = val;
}
finally
{
rwl.ReleaseReaderLock();
}
return iRet;
}
public void Deposit(int x)
{
rwl.AcquireWriterLock(Timeout.Infinite);
try
{
val += x;
}
finally
{
rwl.ReleaseWriterLock();
}
}
}
4、互斥体Mutex
当两个或更多线程需要同时访问一个共享资源时,系统需要使用同步机制来确保一次只有一个线程使用该资源。Mutex 是同步基元,它只向一个线程授予对共享资源的独占访问权。如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程释放该互斥体。
可以使用 WaitHandle.WaitOne 请求互斥体的所属权。拥有互斥体的线程可以在对 Wait 的重复调用中请求相同的互斥体而不会阻塞其执行。但线程必须调用 ReleaseMutex 方法同样多的次数以释放互斥体的所属权。如果线程在拥有互斥体期间正常终止,则互斥体状态设置为终止,并且下一个等待线程获得所属权。如果没有线程拥有互斥体,则互斥体状态为终止。
public class Account
{
int val = 100;
Mutex m = new Mutex();
public void Deposite(int x)
{
m.WaitOne() ; //请求获得互斥对象
try
{
val += x;
}
finally
{
m.ReleaseMutex(); //释放互斥对象
}
}
public void Withdraw(int x)
{
m.WaitOne() ;
try
{
val -= x;
}
finally
{
m.ReleaseMutex();
}
}
}
可以使用Mutex对象在线程之间跨进程进行同步。虽然Mutex不具备Monitor类的所有等待和脉冲功能,但它的确提供了创建可在进程之间使用的命名的互斥的功能。如下代码:
using System;
using System.Threading;
namespace DemoSyncAcrossProc
{
public class App
{
static public void DemoMutex()
{
Mutex mutex = new Mutex(false, "Demo");
Console.WriteLine("创建名为Demo的互斥体");
if(mutex.WaitOne())
Console.WriteLine("得到互斥体");
else
Console.WriteLine("没有得到互斥体");
Console.WriteLine("按任意键退出");
Console.ReadLine();
}
static int Main()
{
DemoMutex();
return 0;
}
}
}
编译后,开启一个命令窗口,运行该程序,不按任何键,程序显示如下:
创建名为Demo的互斥体
得到互斥体
按任意键退出
再开启第2个命令窗口执行该程序,程序显示如下:
创建名为Demo的互斥体
此时,第2个实例被阻塞(因为第1个应用实例还没释放名为Demo的互斥体)。
接着切换到第1个命令行窗口,按任意键结束第1个应用实例,再切换到第2个命令行窗口,则会观测到程序继续执行输出“得到互斥体”。
5、InterLocked
此类的方法可以防止可能在下列情况发生的错误:线程正在更新可由其他线程访问的变量时,计划程序切换上下文;或者两个线程在不同的处理器上同时执行。此类的成员不引发异常。
Increment 和 Decrement 方法递增或递减变量并将结果值存储在单个操作中。在大多数计算机上,增加变量操作不是一个原子操作,需要执行下列步骤:
(1).将实例变量中的值加载到寄存器中。
(2).增加或减少该值。
(3).在实例变量中存储该值。
如果不使用 Increment 和 Decrement,线程会在执行完前两个步骤后被抢先。然后由另一个线程执行所有三个步骤。当第一个线程重新开始执行时,它改写实例变量中的值,造成第二个线程执行增减操作的结果丢失。
Exchange 方法自动交换指定变量的值。CompareExchange 方法组合了两个操作:比较两个值以及根据比较的结果将第三个值存储在其中一个变量中。比较和交换操作按原子操作执行。
有InterLocked公开的Exchange和CompareExchange方法采用可以存储引用的Object类型的参数。但是,类型安全要求将所有参数严格类型化为Object;不能对其中一个方法的调用中简单地将对象强制转换为Object,换言之,必须创建Object类型变量,将自定义对象赋给该变量,然后传递该变量。例如:
public class DemoInterLocked
{
Object _x;
public Object X
{
set
{
Object ovalue = value;
InterLocked.CompareExchange(ref _x , ovalue , null);
}
get
{
return _x;
}
}
}
//下面用Interlocked实现互斥的例子
using System ;
using System.Threading;
namespace DemoSyncResource
{
class Resource
{
ReaderWriterLock rwl = new ReaderWriterLock();
public void Read(Int32 threadNum)
{
rwl.AcquireReaderLock(Timeout.Infinite);
try
{
Console.WriteLine("开始读资源(Thread={0})", threadNum);
Thread.Sleep(250);
Console.WriteLine("读取资源结束(Thread={0})", threadNum);
}
finally
{
rwl.ReleaseReaderLock();
}
}
public void Write(Int32 threadNum)
{
rwl.AcquireWriterLock(Timeout.Infinite);
try
{
Console.WriteLine("开始写资源 (Thread={0})", threadNum);
Thread.Sleep(750);
Console.WriteLine("写资源结束 (Thread={0})", threadNum);
}
finally
{
rwl.ReleaseWriterLock();
}
}
}
class App
{
//临界资源
static Int32 numAsyncOps = 4;
//同步对象
static AutoResetEvent asyncAreDone = new AutoResetEvent(false);
//临界资源
static Resource res = new Resource();
public static void Main()
{
for (Int32 threadNum = 0 ;threadNum < 4 ; threadNum++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(UpdateResource),threadNum);
}
asyncAreDone.WaitOne();
Console.WriteLine("所有操作都结束");
}
static void UpdateResource(object state)
{
Int32 threadNum = (Int32)state;
if ((threadNum % 2)!=0)
res.Read(threadNum);
else
res.Write(threadNum);
//利用Interlocked.Decrement互斥的修改临界资源
//每执行一个线程都将numAsyncOps减1
//如果numAsyncOps变为0,说明4个线程都执行完了
if(Interlocked.Decrement(ref numAsyncOps) == 0)
asyncAreDone.Set();
}
}
}
这5种实现互斥方法的比较:
A.Monitor.Enter/Monitor.Exit和Lock(obj)/SyncLock都是基于引用对象琐技术。锁跟临界资源捆绑在一起。这2种方法的粒度较粗。
B.Mutex是基于自身的锁。通过将一个临界资源跟一个Mutex实例相关,要求所有的请求该临界资源的线程首先获得跟它相关的Mutex锁。这种方式的锁定粒度可以自由控制,可以是一个对象、一段代码、甚至整个过程。
C.Interlocked提供了基于粒度最细的锁,它不依赖于锁定,而基于原子操作的不可分割性,它使增、减、交换、比较等动作成为一个不可分割的原子操作实现互斥。
同步
实现同步的3种方法:
1、Thread.Join()
2、WaitHandle
//使用自动事件
AutoResetEvent asyncOpIsDone = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(new WaitCallback(MyAsyncOperation),asyncOpIsDone);
asyncOpIsDone.WaitOne();//asyncOpIsDone自动被复位
ThreadPool.QueueUserWorkItem(new WaitCallback(MyAsyncOperation),asyncOpIsDone);
asyncOpIsDone.WaitOne();//asyncOpIsDone自动被复位
//使用手工代码
ManualResetEvent masyncOpIsDone = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(new WaitCallback(MyAsyncOperation),masyncOpIsDone);
masyncOpIsDone.WaitOne();//asyncOpIsDone自动被复位
//masyncOpIsDone仍处于有信号状态
//必须手工复位
masyncOpIsDone.Reset(); //如果该代码注释掉,则主线程不会等待第2个子线程结束。
ThreadPool.QueueUserWorkItem(new WaitCallback(MyAsyncOperation),masyncOpIsDone);
masyncOpIsDone.WaitOne();//asyncOpIsDone自动被复位
3、Monitor
//下面实现同步的例子
using System;
using System.Threading;
namespace AppThreadDemo
{
public class Buffer
{
const int size = 1;
char[] buffer = new char[size];//临界资源
//n缓冲区的字符数
//head队列头
//tail队列尾
int n=0,head = 0, tail =0 ;
public void Put(char ch)
{
Console.WriteLine("Put 开始");
lock(this)
{
n++;//放一个字符
while(n> size){//如果n>size说明缓冲区满,故期待读线程取数据
Console.WriteLine("Put Monitor.Wait");
Monitor.Wait(this);
}
buffer[tail]=ch;
tail = (tail+1) % size ;
Console.WriteLine("Put tail={0}\tbuffer={1}\tn={2}",tail,buffer[tail],n);
if (n <=0) //如果缓冲区为空,则通知所有等待线程
{
Console.WriteLine("Put 通知等待的所有线程");
Monitor.PulseAll(this);
}
}
}
public char Get()
{
char ch;
Console.WriteLine("Get 开始");
lock(this)
{
n--;//先取一个字符
while(n<0)//如果缓冲区为空,则等待写线程写入数据
{
Console.WriteLine("Get Monitor.Wait");
Monitor.Wait(this);
}
ch = buffer[head];
head = (head + 1) % size;
Console.WriteLine("Get tail={0}\tbuffer={1}\tn={2}",tail,buffer[tail],n);
if(n>=size)//如果缓冲区满了,则通知所有等待线程
{
Console.WriteLine("Get 通知等待的所有线程");
Monitor.PulseAll(this);
}
return ch;
}
}
}
class App
{
static public void bufferRead()
{
Object o = AppDomain.CurrentDomain.GetData("Buffer");
if(o!=null)
{
Buffer buffer = (Buffer)o;
for(int i=0;i<8;i++)
{
Console.WriteLine("读线程\t{0}读到字符\t{1}",Thread.CurrentThread.GetHashCode(),buffer.Get());
}
}
Console.WriteLine("读取结束");
}
static public void bufferWrite()
{
Object o = AppDomain.CurrentDomain.GetData("Buffer");
char[] msg ={'A','B','C','D','E','F','G','H','I','J','1','2'};
if(o!=null)
{
Buffer buffer = (Buffer)o;
for(int i=0;i<msg.GetLength(0);i++)
{
Console.WriteLine("写线程\t{0}写字符\t{1}",Thread.CurrentThread.GetHashCode(),msg[i]);
buffer.Put(msg[i]);
}
}
Console.WriteLine("写结束");
}
static public void demoBuffer()
{
Buffer buffer = new Buffer();
AppDomain.CurrentDomain.SetData("Buffer",buffer);
Thread threadReader = new Thread(new ThreadStart(App.bufferRead));
Thread threadWriter = new Thread(new ThreadStart(App.bufferWrite));
threadReader.Start();
threadWriter.Start();
threadWriter.Join();
threadReader.Join();
}
static int Main(string[] args)
{
demoBuffer();
return 0;
}
}
}
3种方法的总结:
A、Thread.Join用以等待特定的线程实例thread结束,常用以主线程和子线程之间的同步。
B、AutoResetEvent和ManualResetEvent用事件信号量的方式实现多个线程之间的同步。
C、Monitor要和Lock/SyncLock语句配合才能实现同步。