C#多线程和线程池

.Net的公用语言运行时(Common Language Runtime,CLR)能区分两种不同类型的线程:前台线程和后台线程。这两者的区别就是:应用程序必须运行完所有的前台线程才可以退出;而对于后台线程,应用程序则可以不考虑其是否已经运行完毕而直接退出,所有的后台线程在应用程序退出时都会自动结束。

前台线程和后台线程的区别和联系:
1、后台线程不会阻止进程的终止。属于某个进程的所有前台线程都终止后,该进程就会被终止。所有剩余的后台线程都会停止且不会完成。
2、可以在任何时候将前台线程修改为后台线程,方式是设置Thread.IsBackground 属性。
3、不管是前台线程还是后台线程,如果线程内出现了异常,都会导致进程的终止。
4、托管线程池中的线程都是后台线程,使用new Thread方式创建的线程默认都是前台线程。

说明:   

        应用程序的主线程以及使用Thread构造的线程都默认为前台线程


通过BeginXXX方法运行的线程都是后台线程。

线程池线程也就是使用 ThreadPool.QueueUserWorkItem()和Task工厂创建的线程都默认为后台线程


前台线程和后台线程适合的场合

       通常,后台线程非常适合于完成后台任务,应该将被动侦听活动的线程设置为后台线程,而将负责发送数据的线程设置为前台线程,这样,在所有的数据发送完毕之前该线程不会被终止。
一般前台线程用于需要长时间等待的任务,比如监听客户端的请求;后台线程一般用于处理时间较短的任务,比如处理客户端发过来的请求信息。

 

1、概念

  1.0 线程的和进程的关系以及优缺点

  windows系统是一个多线程的操作系统。一个程序至少有一个进程,一个进程至少有一个线程。进程是线程的容器,一个C#客户端程序开始于一个单独的线程,CLR(公共语言运行库)为该进程创建了一个线程,该线程称为主线程。例如当我们创建一个C#控制台程序,程序的入口是Main()函数,Main()函数是始于一个主线程的。它的功能主要 是产生新的线程和执行程序。C#是一门支持多线程的编程语言,通过Thread类创建子线程,引入using System.Threading命名空间。 

多线程的优点 

1

2

1、 多线程可以提高CPU的利用率,因为当一个线程处于等待状态的时候,CPU会去执行另外的线程

2、 提高了CPU的利用率,就可以直接提高程序的整体执行速度

多线程的缺点:

 

1

2

3

1、线程开的越多,内存占用越大

2、协调和管理代码的难度加大,需要CPU时间跟踪线程

3、线程之间对资源的共享可能会产生可不遇知的问题

 

     1.1 前台线程和后台线程

     C#中的线程分为前台线程和后台线程,线程创建时不做设置默认是前台线程。即线程属性IsBackground=false。

Thread.IsBackground = false;//false:设置为前台线程,系统默认为前台线程。

 区别以及如何使用:

    这两者的区别就是:应用程序必须运行完所有的前台线程才可以退出;而对于后台线程,应用程序则可以不考虑其是否已经运行完毕而直接退出,所有的后台线程在应用程序退出时都会自动结束。一般后台线程用于处理时间较短的任务,如在一个Web服务器中可以利用后台线程来处理客户端发过来的请求信息。而前台线程一般用于处理需要长时间等待的任务,如在Web服务器中的监听客户端请求的程序。

线程是寄托在进程上的,进程都结束了,线程也就不复存在了!

只要有一个前台线程未退出,进程就不会终止!即说的就是程序不会关闭!(即在资源管理器中可以看到进程未结束。)

     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、线程的基本操作

线程和其它常见的类一样,有着很多属性和方法,参考下表:

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);   //毫秒数

  实例代码: 

复制代码

       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("我是:{0},我循环{1}次", Thread.CurrentThread.Name,i);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接着执行,最后才是主线程执行。

看执行结果:

 

        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类型的。见下图:

直接上代码来看效果:

 View Code

执行结果:

上面的代码中有三个线程,threadA,threadB和主线程,threadA优先级最高,threadB优先级最低。这一点从运行结果中也可以看出,线程B 偶尔会出现在主线程和线程A前面。当有多个线程同时处于可执行状态,系统优先执行优先级较高的线程,但这只意味着优先级较高的线程占有更多的CPU时间,并不意味着一定要先执行完优先级较高的线程,才会执行优先级较低的线程。

优先级越高表示CPU分配给该线程的时间片越多,执行时间就多

优先级越低表示CPU分配给该线程的时间片越少,执行时间就少

   3、线程同步

  什么是线程安全:

  线程安全是指在当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。

   线程有可能和其他线程共享一些资源,比如,内存,文件,数据库等。当多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线程“同步”机制,即各位线程之间要有个先来后到,不能一窝蜂挤上去抢作一团。线程同步的真实意思和字面意思恰好相反。线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。

为什么要实现同步呢,下面的例子我们拿著名的单例模式来说吧。看代码

复制代码

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这个方法,这显然不符合我们线程同步的要求。我们对代码进行修改如下:

 View Code

执行结果:

我们通过添加了 lock(this) {...}代码,查看执行结果实现了我们想要的线程同步需求。但是我们知道this表示当前类实例的本身,那么有这么一种情况,我们把需要访问的方法所在的类型进行两个实例A和B,线程A访问实例A的方法ThreadMethod,线程B访问实例B的方法ThreadMethod,这样的话还能够达到线程同步的需求吗。

 View Code

执行结果:

我们会发现,线程又没有实现同步了!lock(this)对于这种情况是不行的!所以需要我们对代码进行修改!修改后的代码如下: 

 View Code

通过查看执行结果。会发现代码实现了我们的需求。那么 lock(this) 和lock(Obj)有什么区别呢? 

lock(this) 锁定 当前实例对象,如果有多个类实例的话,lock锁定的只是当前类实例,对其它类实例无影响。所有不推荐使用。 
lock(typeof(Model))锁定的是model类的所有实例。 
lock(obj)锁定的对象是全局的私有化静态变量。外部无法对该变量进行访问。 
lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。 
所以,lock的结果好不好,还是关键看锁的谁,如果外边能对这个谁进行修改,lock就失去了作用。所以一般情况下,使用私有的、静态的并且是只读的对象。

总结:

1、lock的是必须是引用类型的对象,string类型除外。

2、lock推荐的做法是使用静态的、只读的、私有的对象。

3、保证lock的对象在外部无法修改才有意义,如果lock的对象在外部改变了,对其他线程就会畅通无阻,失去了lock的意义。

     不能锁定字符串,锁定字符串尤其危险,因为字符串被公共语言运行库 (CLR)“暂留”。 这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。通常,最好避免锁定 public 类型或锁定不受应用程序控制的对象实例。例如,如果该实例可以被公开访问,则 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();}

复制代码

输出结果:

总结:

  第一种情况:

  1. thread_first首先获得同步对象的锁,当执行到 Monitor.Wait(monster);时,thread_first线程释放自己对同步对象的锁,流放自己到等待队列,直到自己再次获得锁,否则一直阻塞。
  2. 而thread_second线程一开始就竞争同步锁所以处于就绪队列中,这时候thread_second直接从就绪队列出来获得了monster对象锁,开始执行到Monitor.PulseAll(monster)时,发送了个Pulse信号。
  3. 这时候thread_first接收到信号进入到就绪状态。然后thread_second继续往下执行到 Monitor.Wait(monster, 1000)时,这是一句非常关键的代码,thread_second将自己流放到等待队列并释放自身对同步锁的独占,该等待设置了1S的超时值,当B线程在1S之内没有再次获取到锁自动添加到就绪队列。
  4. 这时thread_first从Monitor.Wait(monster)的阻塞结束,返回true。开始执行、打印。执行下一行的Monitor.Pulse(monster),这时候thread_second假如1S的时间还没过,thread_second接收到信号,于是将自己添加到就绪队列。
  5. thread_first的同步代码块结束以后,thread_second再次获得执行权, Monitor.Wait(m_smplQueue, 1000)返回true,于是继续从该代码处往下执行、打印。当再次执行到Monitor.Wait(monster, 1000),又开始了步骤3。
  6. 依次循环。。。。

   第二种情况: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();  //释放锁}

复制代码

 2、线程池

      上面介绍了介绍了平时用到的大多数的多线程的例子,但在实际开发中使用的线程往往是大量的和更为复杂的,这时,每次都创建线程、启动线程。从性能上来讲,这样做并不理想(因为每使用一个线程就要创建一个,需要占用系统开销);从操作上来讲,每次都要启动,比较麻烦。为此引入的线程池的概念。

  好处:

  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);} } }

复制代码

 

 

有关线程池的解释请参考:

http://www.cnblogs.com/JeffreyZhao/archive/2009/07/22/thread-pool-1-the-goal-and-the-clr-thread-pool.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/439428.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GitHub.com上的那些东西你都知道什么意思吗?

GitHub初学入门者的图谱&#xff0c;介绍Github网站每个功能的意思 一、键盘快捷键 在GitHub中&#xff0c;很多页面都可以使用键盘快捷键。在各个页面按下“shift /”都可以打开键盘快捷键一览表&#xff0c;如下图&#xff1a; 快捷键 二、工具栏 工具栏 LOGO 点击GitHub…

【Python学习】 - sklearn学习 - 数据集分割方法 - 随机划分与K折交叉划分与StratifiedKFold与StratifiedShuffleSplit

一、随机划分 import numpy as np from sklearn import datasetsiris datasets.load_iris() X iris.data y iris.target# 1&#xff09;归一化前&#xff0c;将原始数据分割 from sklearn.model_selection import train_test_split X_train,X_test,y_train,y_test train_t…

【Python学习】 - sklearn - 用于生成数据的make_blobs模块

函数原型&#xff1a; sklearn.datasets.make_blobs(n_samples100, n_features2, centers3, cluster_std1.0, center_box(-10.0, 10.0), shuffleTrue, random_stateNone) 参数含义&#xff1a; n_samples: int, optional (default100) The total number of points equally di…

微服务架构及幂等性

微服务架构 微服务架构是一种架构概念&#xff0c;旨在通过将功能分解到各个离散的服务中以实现对解决方案的解耦。它的主要作用是将功能分解到离散的各个服务当中&#xff0c;从而降低系统的耦合性&#xff0c;并提供更加灵活的服务支持。 和 微服务 相对应的&#xff0c;这…

【Python学习】 - Matplotlib二维绘图 - plt.matshow()和plt.imshow()区别对比

给定一个8*8的数据&#xff0c;用两种方式分别进行输出。 xx np.zeros((8,8),dtype np.uint8) xx[0,0] 13im Image.fromarray(xx) plt.imshow(im)plt.matshow(xx) plt.show() 输出&#xff1a; 得出结论&#xff1a; 首先我不知道为啥两个窗口是不一样大的。 其次发现图…

【机器学习】 - 数据预处理之数据归一化(标准化)与实战分析,正则化

一、为什么要进行数据归一化 定义&#xff1a;把所有数据的特征都归到 [0,1] 之间 或 均值0方差1 的过程。原则&#xff1a;样本的所有特征&#xff0c;在特征空间中&#xff0c;对样本的距离产生的影响是同级的&#xff1b;问题&#xff1a;特征数字化后&#xff0c;由于取值…

【基于Python】 - 人工智能机器学习深度学习数据分析 - 常见问题,常用的套路与操作(持续更新)

20200221&#xff1b; 1.做分类问题的时候&#xff0c;给定你标签&#xff0c;你想知道每一类标签的出现频数&#xff0c;可以使用这个函数&#xff1a;np.bincount()。 如果想分析一下数据样本是否均衡的时候&#xff0c;可以考虑这种操作&#xff0c;代码十分简明。 2. 当…

Entity Framework 简介

转贴&#xff1a;链接https://www.cnblogs.com/davidzhou/p/5348637.html 侵删&#xff0c;谢谢 第一篇&#xff1a;Entity Framework 简介 先从ORM说起吧&#xff0c;很多年前&#xff0c;由于.NET的开源组件不像现在这样发达&#xff0c;更别说一个开源的ORM框架&#xff0…

【Python学习】 - pyecharts包 - 地图可视化

安装&#xff1a; https://pan.baidu.com/s/1vAlSjVbHt0EDJY6C_38oEA 提取码&#xff1a;t9be 在这个链接中下载对应的.whl文件&#xff0c;放到下图所示的目录中。 然后打开anaconda prompt 找到对应的目录&#xff0c;输入&#xff1a; pip install pyecharts-0.1.9.4-py…

【机器学习】 - 关于图像质量评价IQA(Image Quality Assessment)

图像质量评价&#xff08;Image Quality Assessment,IQA&#xff09;是图像处理中的基本技术之一&#xff0c;主要通过对图像进行特性分析研究&#xff0c;然后评估出图像优劣&#xff08;图像失真程度&#xff09;。 主要的目的是使用合适的评价指标&#xff0c;使得评价结果…

【机器学习】 - CNN

什么是卷积神经网络&#xff0c;它为何重要&#xff1f; 卷积神经网络&#xff08;也称作 ConvNets 或 CNN&#xff09;是神经网络的一种&#xff0c;它在图像识别和分类等领域已被证明非常有效。 卷积神经网络除了为机器人和自动驾驶汽车的视觉助力之外&#xff0c;还可以成功…

Asp.Net中WebForm与MVC,Web API模式对比

webform&#xff0c;web mvc和web api都是asp.net官方的三套框架&#xff0c;想对比下三者的关系&#xff0c;查了下资料&#xff0c;web api跟web mvc基本同属一脉&#xff0c;只是mvc多了一个视图渲染&#xff0c;网上有些博客介绍了webform和mvc底层源码实现的不同&#xff…

【机器学习】 - Keras学习 - TensorBoard模块 - 可视化模型训练过程神器

运行环境&#xff1a;Win10 anaconda3。 TensorFlow版本&#xff1a;2.0.0 import numpy as np import tensorflow as tf import tensorflow.keras from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense import matplotlib.pyplot as…

无废话SharePoint入门教程一[SharePoint概述]

一、前言 听说SharePoint也有一段时间了&#xff0c;可一直处在门外。最近被调到SharePoint实施项目小组&#xff0c;就随着工作一起学习了一下实施与开发。但苦于网上SharePoint入门的东西实在太少&#xff0c;导致自学入门很难&#xff0c;不知道SharePoint这东西到底能做什么…

SharePoint 站点结构及概念

简单的记录一下Sharepoint的结构与基本概念 一、服务器场 服务器场,即主机的集群.简单点说就是两台机器互相备份&#xff0c;两个或几台机器之间有心跳线&#xff0c;定时检测对端设备的情况&#xff0c;如果对端设备出现故障&#xff0c;一台机器就会接管出问题机器的受保护…

【Python学习】 - sklearn学习 - 自带数据集sklearn.datasets.x

sklearn 的数据集有好多个种 自带的小数据集&#xff08;packaged dataset&#xff09;&#xff1a;sklearn.datasets.load_可在线下载的数据集&#xff08;Downloaded Dataset&#xff09;&#xff1a;sklearn.datasets.fetch_计算机生成的数据集&#xff08;Generated Datas…

sharepoint 概念及认证方式介绍

3.SharePoint Web 应用程序 我个人的理解&#xff0c;SharePoint Web 应用程序&#xff08;SharePoint Web Application&#xff09;代表的是 SharePoint 网站&#xff08;集&#xff09;的物理容器。 SharePoint Web 应用程序需要指定内容数据库、宿主 IIS 应用程序池、应用…

我们可以用SharePoint做什么

前言 不知不觉作为一个SharePoint的开发人员若干年了&#xff0c;从SharePoint api 开始学习&#xff0c;到了解SharePoint的结构&#xff0c;逐渐一点点了解sharepoint的体系&#xff1b;从SharePoint 的2007到2010到2013到SharePoint Online都接触了一些。本文会从个人的视角…

SharePoint REST API - 确定REST端点URL

SharePoint REST端点URI的结构 在你能够通过REST访问SharePoint资源之前&#xff0c;首先你要做的就是找出对应的URI端点&#xff0c;如果你对Client API熟悉&#xff0c;有些时候也可以参考Client API去猜测构建&#xff0c;例如。 客户端对象模型的方法&#xff1a; List.G…

【机器学习】 - 各种人脸数据集下载地址及说明汇总

1. Olivetti Faces人脸数据集 由40个人组成&#xff0c;共计400张人脸&#xff1b; 每人的人脸图片为10张&#xff0c;包含正脸、侧脸以及不同的表情&#xff1b; 整个数据集就是一张大的人脸组合图片&#xff0c;下载地址&#xff1a;https://cs.nyu.edu/~roweis/data/olivet…