C#线程学习笔记五:线程同步--事件构造
本笔记摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/23/event_constructor.html,记录一下学习过程以备后续查用。
前面讲的线程同步主要是用户模式的(clr via c# 一书中是这么定义的,书中说到线程同步分两种:一、用户模式构造 二、内核模式构造),对于内核模式构造(指的的是构造操作系内核对象),我们使用.net framework中的类
如autoresetevent、semaphore中方法来实现线程同步,其实其内部是调用操作系统中的内核对象来实现的线程同步,此时就会将线程从托管代码转为内核代码。而用户模式构造,因为没有调用操作系统内核对象,所以线程只会在用
户的托管代码上执行。
一、waithandle基类介绍
system.threading命名空间中提供了一个waithandle 的抽象基类,此类就是包装了一个windows内核对象的句柄(句柄可以理解为标示了对象实例的一个数字,
具体大家可以查资料深入理解下,在这里只是提出理解句柄也是很重要的)。
在.net framework中提供了从waithandle类派生的类,它们的继承关系为:
waithandle
eventwaithandle
autoresetevent
manualresetevent
semaphore
mutex
当我们用构造函数来实例化autoresetevent、manualresetevent、semaphore、mutex这些类的对象时,其内部都调用了win32 createevent或者createevent函数
或者createsemaphore或者createmutex函数,这些函数调用返回的句柄值都保存在waithandle基类定义的safewaithandle字段中。
二、事件(event)类实现线程同步
2.1 autoresetevent (自动重置事件)
2.1.1先讲讲autoresetevent类的构造函数,其定义为:
public autoresetevent(bool initialstate);
构造函数中用一个bool 类型的初始状态来设置autoresetevent对象的状态。如果要将autoresetevent对象的初始状态设置为终止,则传入bool值为true;
若要设置为非终止,则传入bool值为false。
2.2.2waitone方法定义:
public virtual bool waitone(int millisecondstimeout);
该方法用来阻塞线程,当在指定的时间间隔还没有收到一个信号时,将返回false。
调用set方法发信号来释放等待线程。
在使用过程中waitone方法和set方法都是成对出现的:
一个用于阻塞线程,等待信号;
一个用来释放等待线程(就是说调用set方法来发送一个信号,此时waitone接受到信号,就释放阻塞的线程,线程就可以继续运行。)
线程通过调用autoresetevent的waitone方法来等待信号,如果autoresetevent对象为非终止状态,则线程被阻止,直到线程调用set方法来恢复线程执行;
如果autoresetevent为终止状态时,则线程不会被阻止,此时autoresetevent将立即释放线程并返回为非终止状态(指出有线程在使用资源的一种状态)。
下面代码演示autoresetevent的使用:
class program { //创建对象 public static autoresetevent autoresetevent = new autoresetevent(false); static void main(string[] args) { #region 线程同步:autoresetevent的使用 console.writeline("main thread start run at: " + datetime.now.tolongtimestring()); thread thread = new thread(waitonemethod); thread.start(); //阻塞主线程3秒钟 thread.sleep(3000); //释放线程 autoresetevent.set(); console.read(); #endregion } /// <summary> /// waitone方法 /// </summary> public static void waitonemethod() { autoresetevent.waitone(); console.writeline("method restart run at: " + datetime.now.tolongtimestring()); } }
运行结果如下:
若创建对象时,把它改为public static autoresetevent autoresetevent = new autoresetevent(true); ,看到的输出结果的时间就是一样的了。因为设置为true时,表示此时
已经为终止状态了。因此,autoresetevent.set()可以理解为将autoresetevent的状态设置为终止状态,因而释放线程。
上面用到的是没带参数的waitone方法,该方法表示无限制阻塞线程,直到收到一个事件为止(通过set方法来发送一个信号)。
通过bool waitone(int millisecondstimeout),当超时时,线程即使没收到set发来的信号,也将不再阻塞线程而让它继续运行,只是waitone方法返回的值不一样:
当收到set信号时返回值为true,否则返回值为false。
下面代码演示waitone(millisecondstimeout)的使用:
class program { //创建对象 public static autoresetevent autoresetevent = new autoresetevent(false); static void main(string[] args) { #region 线程同步:waitone(millisecondstimeout)的使用 console.writeline("main thread start run at: " + datetime.now.tolongtimestring()); thread thread = new thread(waitonetimeoutmethod); thread.start(); //阻塞主线程3秒钟 thread.sleep(3000); //释放线程 autoresetevent.set(); console.read(); #endregion } /// <summary> /// waitonetimeout方法 /// </summary> public static void waitonetimeoutmethod() { if (autoresetevent.waitone(2000)) { console.writeline("get signal to work."); console.writeline("method restart run at: " + datetime.now.tolongtimestring()); } else { console.writeline("time out to work."); console.writeline("method restart run at: " + datetime.now.tolongtimestring()); } } }
运行结果如下:
若把thread.sleep(3000);设为thread.sleep(1000);,此时线程将收到set发过来的信号,得到的结果将是get signal to work,时间相差就只有1秒了。
2.2 manualresetevent(手动重置事件)
manualresetevent和autoresetevent的使用方法很类似,因为他们都是从eventwaithandle类派生的,不过他们还是有些区别:
2.2.1autoresetevent 为终止状态(true)时,如果线程调用waitone方法的话线程是不会被阻止的。此时autoresetevent将立即释放线程并返回到非终止状态(false),
在这之后如果线程再次调用waitone方法的话,线程将会被阻止。(注:调用waitone方法自动改变状态,仅对初始状态为终止状态时有效。)
2.2.2manualresetevent为终止状态(true)时,如果线程调用waitone方法的话线程也是不会被阻止的。此manualresetevent将立即释放线程但不会返回到非终止状态(false),
除非我们手动将状态改为终止状态(false),否则在这之后如果线程再次调用waitone方法的话,线程不会被阻止。
下面代码演示两者的区别:
class program { //创建对象 public static autoresetevent autoresetevent = new autoresetevent(false); public static manualresetevent manualresetevent = new manualresetevent(false); static void main(string[] args) { #region 线程同步:manualresetevent的使用 console.writeline("main thread start run at: " + datetime.now.tolongtimestring()); thread threadauto = new thread(autoreseteventmethod); threadauto.start(); autoresetevent.set(); threadauto = new thread(autoreseteventmethod); threadauto.start(); thread threadmanual = new thread(manualreseteventmethod); threadmanual.start(); manualresetevent.set(); threadmanual = new thread(manualreseteventmethod); threadmanual.start(); console.read(); #endregion } /// <summary> /// autoresetevent方法 /// </summary> public static void autoreseteventmethod() { autoresetevent.waitone(); console.writeline("autoresetevent method restart run at: " + datetime.now.tolongtimestring()); } /// <summary> /// manualresetevent方法 /// </summary> public static void manualreseteventmethod() { manualresetevent.waitone(); console.writeline("manualresetevent method restart run at: " + datetime.now.tolongtimestring()); } }
运行结果如下:
2.3 跨进程之间同步
内核模式构造可实现同一台机器上的不同进程中的线程进行同步,因此可以使用autoresetevent来实现此功能。此时需要对autoresetevent进行命名,但是autoresetevent只提供了带一个参数的构造函数,该如何实现呢?
办法还是有的,因为autoresetevent是继承自eventwaithandle类,而eventwaithandle类有多个构造函数。
除了之前的方法创建autoresetevent对象外,还可以通过eventwaithandle autoevent = new eventwaithandle (false, eventresetmode.auto,"my");这样的方式来构造autoresetevent对象,此方式指定了名称。
下面代码演示跨进程之间的线程同步:
第一个进程代码:
class program { //创建对象 public static eventwaithandle autoeventfirst = new eventwaithandle(false, eventresetmode.autoreset, "first"); public static eventwaithandle autoeventsecond = new eventwaithandle(false, eventresetmode.autoreset, "second"); static void main(string[] args) { #region 线程同步:跨进程之间的线程同步 console.writeline("first main thread start run at: " + datetime.now.tolongtimestring()); thread thread = new thread(eventwaithandlemethod); thread.start(); //为了有时间去启动另外一个进程 thread.sleep(15000); autoeventfirst.set(); autoeventsecond.set(); console.read(); #endregion } /// <summary> /// eventwaithandle方法 /// </summary> public static void eventwaithandlemethod() { autoeventfirst.waitone(); console.writeline("first method start at:" + datetime.now.tolongtimestring()); } }
第二个进程代码:
class program { //创建对象 public static eventwaithandle autoeventsecond = new eventwaithandle(false, eventresetmode.autoreset, "second"); static void main(string[] args) { console.writeline("second main thread start run at: " + datetime.now.tolongtimestring()); thread thread = new thread(eventwaithandlemethod); thread.start(); console.read(); } /// <summary> /// eventwaithandle方法 /// </summary> public static void eventwaithandlemethod() { autoeventsecond.waitone(); console.writeline("second method start at:" + datetime.now.tolongtimestring()); } }
运行结果如下:
从结果可以看出,第一个进程的autoeventsecond.set();信号发出后,第二个进程可以收到并释放线程。