一、概念html
1.0 线程的和进程的关系以及优缺点数据库
windows系统是一个多线程的操做系统。一个程序至少有一个进程,一个进程至少有一个线程。进程是线程的容器,一个C#客户端程序开始于一个单独的线程,CLR(公共语言运行库)为该进程建立了一个线程,该线程称为主线程。例如当咱们建立一个C#控制台程序,程序的入口是Main()函数,Main()函数是始于一个主线程的。它的功能主要 是产生新的线程和执行程序。C#是一门支持多线程的编程语言,经过Thread类建立子线程,引入using System.Threading命名空间。 编程
多线程的优势: windows
1
2
|
一、 多线程能够提升CPU的利用率,由于当一个线程处于等待状态的时候,CPU会去执行另外的线程
二、 提升了CPU的利用率,就能够直接提升程序的总体执行速度
|
多线程的缺点:安全
1
2
3
|
一、线程开的越多,内存占用越大
二、协调和管理代码的难度加大,须要CPU时间跟踪线程
三、线程之间对资源的共享可能会产生可不遇知的问题
|
1.1 前台线程和后台线程服务器
C#中的线程分为前台线程和后台线程,线程建立时不作设置默认是前台线程。即线程属性IsBackground=false。多线程
Thread.IsBackground = false;//false:设置为前台线程,系统默认为前台线程。
区别以及如何使用:编程语言
这二者的区别就是:应用程序必须运行完全部的前台线程才能够退出;而对于后台线程,应用程序则能够不考虑其是否已经运行完毕而直接退出,全部的后台线程在应用程序退出时都会自动结束。通常后台线程用于处理时间较短的任务,如在一个Web服务器中能够利用后台线程来处理客户端发过来的请求信息。而前台线程通常用于处理须要长时间等待的任务,如在Web服务器中的监听客户端请求的程序。ide
线程是寄托在进程上的,进程都结束了,线程也就不复存在了!函数
只要有一个前台线程未退出,进程就不会终止!即说的就是程序不会关闭!(即在资源管理器中能够看到进程未结束。)
1.3 多线程的建立
下面的代码建立了一个子线程,做为程序的入口mian()函数所在的线程即为主线程,咱们经过Thread类来建立子线程,Thread类有 ThreadStart 和 ParameterizedThreadStart类型的委托参数,咱们也能够直接写方法的名字。线程执行的方法能够传递参数(可选),参数的类型为object,写在Start()里。
class Program { //咱们的控制台程序入口是main函数。它所在的线程便是主线程 static void Main(string[] args) { Thread thread = new Thread(ThreadMethod); //执行的必须是无返回值的方法 thread.Name = "子线程"; //thread.Start("王建"); //在此方法内传递参数,类型为object,发送和接收涉及到拆装箱操做 thread.Start(); Console.ReadKey(); } public static void ThreadMethod(object parameter) //方法内能够有参数,也能够没有参数 { Console.WriteLine("{0}开始执行。", Thread.CurrentThread.Name); } }
首先使用new Thread()建立出新的线程,而后调用Start方法使得线程进入就绪状态,获得系统资源后就执行,在执行过程当中可能有等待、休眠、死亡和阻塞四种状态。正常执行结束时间片后返回到就绪状态。若是调用Suspend方法会进入等待状态,调用Sleep或者遇到进程同步使用的锁机制而休眠等待。具体过程以下图所示:
二、线程的基本操做
线程和其它常见的类同样,有着不少属性和方法,参考下表:
2.1 线程的相关属性
咱们能够经过上面表中的属性获取线程的一些相关信息,下面是代码展现和输出结果:
static void Main(string[] args) { Thread thread = new Thread(ThreadMethod); //执行的必须是无返回值的方法 thread.Name = "子线程"; thread.Start(); StringBuilder threadInfo = new StringBuilder(); threadInfo.Append(" 线程当前的执行状态: " + thread.IsAlive); threadInfo.Append("\n 线程当前的名字: " + thread.Name); threadInfo.Append("\n 线程当前的优先级: " + thread.Priority); threadInfo.Append("\n 线程当前的状态: " + thread.ThreadState); Console.Write(threadInfo); Console.ReadKey(); } public static void ThreadMethod(object parameter) { Console.WriteLine("{0}开始执行。", Thread.CurrentThread.Name); }
输输出结果:
2.2 线程的相关操做
2.2.1 Abort()方法
Abort()方法用来终止线程,调用此方法强制中止正在执行的线程,它会抛出一个ThreadAbortException异常从而致使目标线程的终止。下面代码演示:
static void Main(string[] args) { Thread thread = new Thread(ThreadMethod); //执行的必须是无返回值的方法 thread.Name = "小A"; thread.Start(); Console.ReadKey(); } public static void ThreadMethod(object parameter) { Console.WriteLine("我是:{0},我要终止了", Thread.CurrentThread.Name); //开始终止线程 Thread.CurrentThread.Abort(); //下面的代码不会执行 for (int i = 0; i < 10; i++) { Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name,i); } }
执行结果:和咱们想象的同样,下面的循环没有被执行
2.2.2 ResetAbort()方法
Abort方法能够经过跑出ThreadAbortException异常停止线程,而使用ResetAbort方法能够取消停止线程的操做,下面经过代码演示使用 ResetAbort方法。
static void Main(string[] args) { Thread thread = new Thread(ThreadMethod); //执行的必须是无返回值的方法 thread.Name = "小A"; thread.Start(); Console.ReadKey(); } public static void ThreadMethod(object parameter) { try { Console.WriteLine("我是:{0},我要终止了", Thread.CurrentThread.Name);
//开始终止线程 Thread.CurrentThread.Abort(); } catch(ThreadAbortException ex) { Console.WriteLine("我是:{0},我又恢复了", Thread.CurrentThread.Name);
//恢复被终止的线程 Thread.ResetAbort(); } for (int i = 0; i < 10; i++) { Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name,i); } }
执行结果:
2.2.3 Sleep()方法
Sleep()方法调已阻塞线程,是当前线程进入休眠状态,在休眠过程当中占用系统内存可是不占用系统时间,当休眠期事后,继续执行,声明以下:
public static void Sleep(TimeSpan timeout); //时间段 public static void Sleep(int millisecondsTimeout); //毫秒数
实例代码:
using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace ThreadTest { class Program { static void Main(string[] args) { Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法 threadA.Name = "小A"; threadA.Start(); Console.ReadKey(); } public static void ThreadMethod(object parameter) { for (int i = 0; i < 10; i++) { Console.WriteLine("[{2}] 我是:{0},我循环{1}次", Thread.CurrentThread.Name, i, DateTime.Now.ToString("HH:mm:ss.fff")); Thread.Sleep(300); //休眠300毫秒 } } } }
将上面的代码执行之后,能够清楚的看到每次循环之间相差300毫秒的时间。
2.2.4 join()方法
Join方法主要是用来阻塞调用线程,直到某个线程终止或通过了指定时间为止。官方的解释比较乏味,通俗的说就是建立一个子线程,给它加了这个方法,其它线程就会暂停执行,直到这个线程执行完为止才去执行(包括主线程)。她的方法声明以下:
public void Join(); public bool Join(int millisecondsTimeout); //毫秒数 public bool Join(TimeSpan timeout); //时间段
为了验证上面所说的,咱们首先看一段代码:
static void Main(string[] args) { Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法 threadA.Name = "小A"; Thread threadB = new Thread(ThreadMethod); //执行的必须是无返回值的方法 threadB.Name = "小B"; threadA.Start();
//threadA.Join(); threadB.Start();
//threadB.Join(); for (int i = 0; i < 10; i++) { Console.WriteLine("我是:主线程,我循环{1}次", Thread.CurrentThread.Name, i); Thread.Sleep(300); //休眠300毫秒 } Console.ReadKey(); } public static void ThreadMethod(object parameter) { for (int i = 0; i < 10; i++) { Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name,i); Thread.Sleep(300); //休眠300毫秒 } }
由于线程之间的执行是随机的,全部执行结果和咱们想象的同样,杂乱无章!可是说明他们是同时执行的。以下图
如今咱们把代码中的 ThreadA.join()方法注释取消,在看看执行的效果吧!
首先程序中有三个线程,ThreadA、ThreadB、主线程,首先咱们看到主线程和ThreadB线程阻塞,ThreadA先执行,而主线程和ThreadB线程则同时执行了。
那么咱们把代码中的 ThreadA.join()方法和ThreadB.join()方法注释都取消,在看看执行的效果吧!
从运行结果能够看到,首先程序中有三个线程,ThreadA、ThreadB和主线程,首先主线程先阻塞,而后线程ThreadB阻塞,ThreadA先执行,执行完毕之后ThreadB接着执行,最后才是主线程执行。
看执行结果:
2.2.5 Suspent()和Resume()方法
其实在C# 2.0之后, Suspent()和Resume()方法已通过时了。suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这以前得到的锁定。此时,其余任何线程都不能访问锁定的资源,除非被”挂起”的线程恢复运行。对任何线程来讲,若是它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会形成死锁。因此不该该使用suspend()。
static void Main(string[] args) { Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法 threadA.Name = "小A"; threadA.Start(); Thread.Sleep(3000); //休眠3000毫秒 threadA.Resume(); //继续执行已经挂起的线程 Console.ReadKey(); } public static void ThreadMethod(object parameter) { Thread.CurrentThread.Suspend(); //挂起当前线程 for (int i = 0; i < 10; i++) { Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name, i); } }
执行上面的代码。窗口并无立刻执行 ThreadMethod方法输出循环数字,而是等待了三秒钟以后才输出,由于线程开始执行的时候执行了Suspend()方法挂起。而后主线程休眠了3秒钟之后又经过Resume()方法恢复了线程threadA。
2.2.6 线程的优先级
若是在应用程序中有多个线程在运行,但一些线程比另外一些线程重要,这种状况下能够在一个进程中为不一样的线程指定不一样的优先级。线程的优先级能够经过Thread类Priority属性设置,Priority属性是一个ThreadPriority型枚举,列举了5个优先等级:AboveNormal、BelowNormal、Highest、Lowest、Normal。公共语言运行库默认是Normal类型的。见下图:
直接上代码来看效果:
static void Main(string[] args) { Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法 threadA.Name = "A"; Thread threadB = new Thread(ThreadMethod); //执行的必须是无返回值的方法 threadB.Name = "B"; threadA.Priority = ThreadPriority.Highest; threadB.Priority = ThreadPriority.BelowNormal; threadB.Start(); threadA.Start(); Thread.CurrentThread.Name = "C"; ThreadMethod(new object()); Console.ReadKey(); } public static void ThreadMethod(object parameter) { for (int i = 0; i < 500; i++) { Console.Write(Thread.CurrentThread.Name); } }
执行结果:
上面的代码中有三个线程,threadA,threadB和主线程,threadA优先级最高,threadB优先级最低。这一点从运行结果中也能够看出,线程B 偶尔会出如今主线程和线程A前面。当有多个线程同时处于可执行状态,系统优先执行优先级较高的线程,但这只意味着优先级较高的线程占有更多的CPU时间,并不意味着必定要先执行完优先级较高的线程,才会执行优先级较低的线程。
优先级越高表示CPU分配给该线程的时间片越多,执行时间就多
优先级越低表示CPU分配给该线程的时间片越少,执行时间就少
三、线程同步
什么是线程安全:
线程安全是指在当一个线程访问该类的某个数据时,进行保护,其余线程不能进行访问直到该线程读取完,其余线程才可以使用。不会出现数据不一致或者数据污染。
线程有可能和其余线程共享一些资源,好比,内存,文件,数据库等。当多个线程同时读写同一份共享资源的时候,可能会引发冲突。这时候,咱们须要引入线程“同步”机制,即各位线程之间要有个先来后到,不能一窝蜂挤上去抢做一团。线程同步的真实意思和字面意思刚好相反。线程同步的真实意思,实际上是“排队”:几个线程之间要排队,一个一个对共享资源进行操做,而不是同时进行操做。
为何要实现同步呢,下面的例子咱们拿著名的单例模式来讲吧。看代码
public class Singleton { private static Singleton instance; private Singleton() //私有函数,防止实例 { } public static Singleton GetInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
单例模式就是保证在整个应用程序的生命周期中,在任什么时候刻,被指定的类只有一个实例,并为客户程序提供一个获取该实例的全局访问点。但上面代码有一个明显的问题,那就是假如两个线程同时去获取这个对象实例,那。。。。。。。。
咱们队代码进行修改:
public class Singleton { private static Singleton instance; private static object obj=new object(); private Singleton() //私有化构造函数 { } public static Singleton GetInstance() { if(instance==null) { lock(obj) //经过Lock关键字实现同步 { if(instance==null) { instance=new Singleton(); } } } return instance; } }
通过修改后的代码。加了一个 lock(obj)代码块。这样就可以实现同步了,假如不是很明白的话,我们看后面继续讲解~
3.0 使用Lock关键字实现线程同步
首先建立两个线程,两个线程执行同一个方法,参考下面的代码:
static void Main(string[] args) { Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法 threadA.Name = "王文建"; Thread threadB = new Thread(ThreadMethod); //执行的必须是无返回值的方法 threadB.Name = "生旭鹏"; threadA.Start(); threadB.Start(); Console.ReadKey(); } public static void ThreadMethod(object parameter) { for (int i = 0; i < 10; i++) { Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name, i); Thread.Sleep(300); } }
执行结果:
经过上面的执行结果,能够很清楚的看到,两个线程是在同时执行ThreadMethod这个方法,这显然不符合咱们线程同步的要求。咱们对代码进行修改以下:
static void Main(string[] args) { Program pro = new Program(); Thread threadA = new Thread(pro.ThreadMethod); //执行的必须是无返回值的方法 threadA.Name = "王文建"; Thread threadB = new Thread(pro.ThreadMethod); //执行的必须是无返回值的方法 threadB.Name = "生旭鹏"; threadA.Start(); threadB.Start(); Console.ReadKey(); } public void ThreadMethod(object parameter) { lock (this) //添加lock关键字 { for (int i = 0; i < 10; i++) { Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name, i); Thread.Sleep(300); } } }
执行结果:
咱们经过添加了 lock(this) {...}代码,查看执行结果实现了咱们想要的线程同步需求。可是咱们知道this表示当前类实例的自己,那么有这么一种状况,咱们把须要访问的方法所在的类型进行两个实例A和B,线程A访问实例A的方法ThreadMethod,线程B访问实例B的方法ThreadMethod,这样的话还可以达到线程同步的需求吗。
static void Main(string[] args) { Program pro1 = new Program(); Program pro2 = new Program(); Thread threadA = new Thread(pro1.ThreadMethod); //执行的必须是无返回值的方法 threadA.Name = "王文建"; Thread threadB = new Thread(pro2.ThreadMethod); //执行的必须是无返回值的方法 threadB.Name = "生旭鹏"; threadA.Start(); threadB.Start(); Console.ReadKey(); } public void ThreadMethod(object parameter) { lock (this) { for (int i = 0; i < 10; i++) { Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name, i); Thread.Sleep(300); } } }
执行结果:
咱们会发现,线程又没有实现同步了!lock(this)对于这种状况是不行的!因此须要咱们对代码进行修改!修改后的代码以下:
private static object obj = new object(); static void Main(string[] args) { Program pro1 = new Program(); Program pro2 = new Program(); Thread threadA = new Thread(pro1.ThreadMethod); //执行的必须是无返回值的方法 threadA.Name = "王文建"; Thread threadB = new Thread(pro2.ThreadMethod); //执行的必须是无返回值的方法 threadB.Name = "生旭鹏"; threadA.Start(); threadB.Start(); Console.ReadKey(); } public void ThreadMethod(object parameter) { lock (obj) { for (int i = 0; i < 10; i++) { Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name, i); Thread.Sleep(300); } } }
经过查看执行结果。会发现代码实现了咱们的需求,两个线程按顺序执行了。那么 lock(this) 和lock(Obj)有什么区别呢? 咱们再看一个示例代码:
static void Main(string[] args) { Class1 pro1 = new Class1(); Class1 pro2 = new Class1(); Thread threadA = new Thread(pro1.ThreadMethod); //执行的必须是无返回值的方法 threadA.Name = "王文建"; Thread threadB = new Thread(pro2.ThreadMethod); //执行的必须是无返回值的方法 threadB.Name = "生旭鹏"; threadA.Start(); threadB.Start(); Console.ReadKey(); } //另外新建一个类 public class Class1 { private static object obj = new object(); public void ThreadMethod(object parameter) { lock (obj) // 也可使用 lock (typeof(Class1)) 方法来锁定 { for (int i = 0; i < 10; i++) { Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name, i); Thread.Sleep(300); } } } }
lock(this) 锁定 当前实例对象,若是有多个类实例的话,lock锁定的只是当前类实例,对其它类实例无影响。全部不推荐使用。
lock(typeof(Model))锁定的是model类的全部实例。 这里的Model是指某个类名。
lock(obj)锁定的对象是全局的私有化静态变量。外部没法对该变量进行访问。
lock 确保当一个线程位于代码的临界区时,另外一个线程不进入临界区。若是其余线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
因此,lock的结果好很差,仍是关键看锁的谁,若是外边能对这个谁进行修改,lock就失去了做用。
因此,通常状况下,使用私有的、静态的而且是只读的对象。
总结:
一、lock的是必须是引用类型的对象,string类型除外。
二、lock推荐的作法是使用静态的、只读的、私有的对象。
三、保证lock的对象在外部没法修改才有意义,若是lock的对象在外部改变了,对其余线程就会畅通无阻,失去了lock的意义。
不能锁定字符串,锁定字符串尤为危险,由于字符串被公共语言运行库 (CLR)“暂留”。 这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了全部运行的应用程序域的全部线程中的该文本。所以,只要在应用程序进程中的任何位置处具备相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的全部实例。一般,最好避免锁定 public 类型或锁定不受应用程序lock块内控制的对象实例。例如,若是该实例能够被公开访问,则 lock(this) 可能会有问题,由于不受控制的代码也可能会锁定该对象。这可能致使死锁,即两个或更多个线程等待释放同一对象。出于一样的缘由,锁定公共数据类型(相比于对象)也可能致使问题。并且lock(this)只对当前对象有效,若是多个对象之间就达不到同步的效果。lock(typeof(Class))与锁定字符串同样,锁定的对象的做用域的范围太广了。
3.1 使用Monitor类实现线程同步
Lock关键字是Monitor的一种替换用法,lock在IL代码中会被翻译成Monitor.
lock(obj)
{
//代码段
}
就等同于
Monitor.Enter(obj);
//代码段
Monitor.Exit(obj);
Monitor的经常使用属性和方法:
Enter(Object) 在指定对象上获取排他锁。
Exit(Object) 释放指定对象上的排他锁。
Pulse 通知等待队列中的线程锁定对象状态的更改。
PulseAll 通知全部的等待线程对象状态的更改。
TryEnter(Object) 试图获取指定对象的排他锁。
TryEnter(Object, Boolean) 尝试获取指定对象上的排他锁,并自动设置一个值,指示是否获得了该锁。
Wait(Object) 释放对象上的锁并阻止当前线程,直到它从新获取该锁。
经常使用的方法有两个,Monitor.Enter(object)方法是获取锁,Monitor.Exit(object)方法是释放锁,这就是Monitor最经常使用的两个方法,在使用过程当中为了不获取锁以后由于异常,致锁没法释放,因此须要在try{} catch(){}以后的finally{}结构体中释放锁(Monitor.Exit())。
Enter(Object)的用法很简单,看代码
static void Main(string[] args) { Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法 threadA.Name = "A"; Thread threadB = new Thread(ThreadMethod); //执行的必须是无返回值的方法 threadB.Name = "B"; threadA.Start(); threadB.Start(); Thread.CurrentThread.Name = "C"; ThreadMethod(); Console.ReadKey(); } static object obj = new object(); public static void ThreadMethod() { Monitor.Enter(obj); //Monitor.Enter(obj) 锁定对象 try { for (int i = 0; i < 500; i++) { Console.Write(Thread.CurrentThread.Name); } } catch(Exception ex){ } finally { Monitor.Exit(obj); //释放对象 } }
TryEnter(Object)和TryEnter() 方法在尝试获取一个对象上的显式锁方面和 Enter() 方法相似。然而,它不像Enter()方法那样会阻塞执行。若是线程成功进入关键区域那么TryEnter()方法会返回true. 和试图获取指定对象的排他锁。看下面代码演示:
咱们能够经过Monitor.TryEnter(monster, 1000),该方法也可以避免死锁的发生,咱们下面的例子用到的是该方法的重载,Monitor.TryEnter(Object,Int32),。
static void Main(string[] args) { Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法 threadA.Name = "A"; Thread threadB = new Thread(ThreadMethod); //执行的必须是无返回值的方法 threadB.Name = "B"; threadA.Start(); threadB.Start(); Thread.CurrentThread.Name = "C"; ThreadMethod(); Console.ReadKey(); } static object obj = new object(); public static void ThreadMethod() { bool flag = Monitor.TryEnter(obj, 1000); //设置1S的超时时间,若是在1S以内没有得到同步锁,则返回false
//上面的代码设置了锁定超时时间为1秒,也就是说,在1秒中后,
//lockObj还未被解锁,TryEntry方法就会返回false,若是在1秒以内,lockObj被解锁,TryEntry返回true。咱们可使用这种方法来避免死锁 try { if (flag) { for (int i = 0; i < 500; i++) { Console.Write(Thread.CurrentThread.Name); } } } catch(Exception ex) { } finally { if (flag) Monitor.Exit(obj); } }
Monitor.Wait和Monitor()Pause()
Wait(object)方法:释放对象上的锁并阻止当前线程,直到它从新获取该锁,该线程进入等待队列。
Pulse方法:只有锁的当前全部者可使用 Pulse 向等待对象发出信号,当前拥有指定对象上的锁的线程调用此方法以便向队列中的下一个线程发出锁的信号。接收到脉冲后,等待线程就被移动到就绪队列中。在调用 Pulse 的线程释放锁后,就绪队列中的下一个线程(不必定是接收到脉冲的线程)将得到该锁。
另外:
Wait 和 Pulse 方法必须写在 Monitor.Enter 和Moniter.Exit 之间。
上面是MSDN的解释。不明白看代码:
首先咱们定义一个怪物类,被攻击类,
/// <summary> /// 怪物类 /// </summary> internal class Monster { public int Blood { get; set; } public Monster(int blood) { this.Blood = blood; Console.WriteLine("我是怪物,我有{0}滴血",blood); } }
而后在定义一个玩家类,攻击类
/// <summary> /// 玩家类 /// </summary> internal class Play { /// <summary> /// 攻击者名字 /// </summary> public string Name { get; set; } /// <summary> /// 攻击力 /// </summary> public int Power{ get; set; } /// <summary> /// 法术攻击 /// </summary> public void magicExecute(object monster) { Monster m = monster as Monster; Monitor.Enter(monster); while (m.Blood>0) { Monitor.Wait(monster); Console.WriteLine("当前英雄:{0},正在使用法术攻击打击怪物", this.Name); if(m.Blood>= Power) { m.Blood -= Power; } else { m.Blood = 0; } Thread.Sleep(300); Console.WriteLine("怪物的血量还剩下{0}", m.Blood); Monitor.PulseAll(monster); } Monitor.Exit(monster); } /// <summary> /// 物理攻击 /// </summary> /// <param name="monster"></param> public void physicsExecute(object monster) { Monster m = monster as Monster; Monitor.Enter(monster); while (m.Blood > 0) { Monitor.PulseAll(monster); if (Monitor.Wait(monster, 1000)) //很是关键的一句代码 { Console.WriteLine("当前英雄:{0},正在使用物理攻击打击怪物", this.Name); if (m.Blood >= Power) { m.Blood -= Power; } else { m.Blood = 0; } Thread.Sleep(300); Console.WriteLine("怪物的血量还剩下{0}", m.Blood); } } Monitor.Exit(monster); } }
执行代码:
static void Main(string[] args) { //怪物类 Monster monster = new Monster(1000); //物理攻击类 Play play1 = new Play() { Name = "无敌剑圣", Power = 100 }; //魔法攻击类 Play play2 = new Play() { Name = "流浪法师", Power = 120 }; Thread thread_first = new Thread(play1.physicsExecute); //物理攻击线程 Thread thread_second = new Thread(play2.magicExecute); //魔法攻击线程 thread_first.Start(monster); thread_second.Start(monster); Console.ReadKey(); }
输出结果:
总结:
第一种状况:
第二种状况:thread_second首先得到同步锁对象,首先执行到Monitor.PulseAll(monster),由于程序中没有须要等待信号进入就绪状态的线程,因此这一句代码没有意义,当执行到 Monitor.Wait(monster, 1000),自动将本身流放到等待队列并在这里阻塞,1S 时间事后thread_second自动添加到就绪队列,线程thread_first得到monster对象锁,执行到Monitor.Wait(monster);时发生阻塞释放同步对象锁,线程thread_second执行,执行Monitor.PulseAll(monster)时通知thread_first。因而又开始第一种状况...
Monitor.Wait是让当前进程睡眠在临界资源上并释放独占锁,它只是等待,并不退出,当等待结束,就要继续执行剩下的代码。
3.0 使用Mutex类实现线程同步
Mutex的突出特色是能够跨应用程序域边界对资源进行独占访问,便可以用于同步不一样进程中的线程,这种功能固然这是以牺牲更多的系统资源为代价的。
主要经常使用的两个方法:
public virtual bool WaitOne() 阻止当前线程,直到当前 System.Threading.WaitHandle 收到信号获取互斥锁。
public void ReleaseMutex() 释放 System.Threading.Mutex 一次。
使用实例:
static void Main(string[] args) { Thread[] thread = new Thread[3]; for (int i = 0; i < 3; i++) { thread[i] = new Thread(ThreadMethod1); thread[i].Name = i.ToString(); } for (int i = 0; i < 3; i++) { thread[i].Start(); } Console.ReadKey(); } public static void ThreadMethod1(object val) { mutet.WaitOne(); //获取锁 for (int i = 0; i < 500; i++) { Console.Write(Thread.CurrentThread.Name); } mutet.ReleaseMutex(); //释放锁 }
二、线程池
上面介绍了介绍了平时用到的大多数的多线程的例子,但在实际开发中使用的线程每每是大量的和更为复杂的,这时,每次都建立线程、启动线程。从性能上来说,这样作并不理想(由于每使用一个线程就要建立一个,须要占用系统开销);从操做上来说,每次都要启动,比较麻烦。为此引入的线程池的概念。
好处:
1.减小在建立和销毁线程上所花的时间以及系统资源的开销
2.如不使用线程池,有可能形成系统建立大量线程而致使消耗完系统内存以及”过分切换”。
在什么状况下使用线程池?
1.单个任务处理的时间比较短
2.须要处理的任务的数量大
线程池最多管理线程数量=“处理器数 * 250”。也就是说,若是您的机器为2个2核CPU,那么CLR线程池的容量默认上限即是1000
经过线程池建立的线程默认为后台线程,优先级默认为Normal。
代码示例:
static void Main(string[] args) { ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadMethod1), new object()); //参数可选 Console.ReadKey(); } public static void ThreadMethod1(object val) { for (int i = 0; i <= 500000000; i++) { if (i % 1000000 == 0) { Console.Write(Thread.CurrentThread.Name); } } }
有关线程池的解释请参考:
======