多线程编程系列,掌握多线程编程

图片 16

多线程编程系列,掌握多线程编程

目录

你必需调节的多线程编制程序,驾驭多线程编制程序

1、八线程编制程序必备知识

    1.1 进程与线程的概念

       
 当大家开垦贰个应用程序后,操作系统就能为该应用程序分配三个进度ID,比方展开QQ,你将要职责微处理器的经过选项卡见到QQ.exe进度,如下图:

         图片 1

         
进度能够通晓为一块包含了好几能源的内部存款和储蓄器区域,操作系统通过进度这一艺术把它的干活划分为区别的单元。一个应用程序能够对应于多个进度。

         
线程是经过中的独立推行单元,对于操作系统来讲,它通过调解线程来使应用程序专门的学业,多个历程中起码含有八个线程,大家把该线程成为主线程。线程与经过之间的涉及足以掌握为:线程是经过的举行单元,操作系统通过调整线程来使应用程序专门的工作;而经过则是线程的容器,它由操作系统创制,又在现实的实施进程中开创了线程。

 

    1.2 线程的调节

       
 在操作系统的书中经常有提过,“Windows是抢占式八线程操作系统”。之所以那样说它是抢占式的,是因为线程能够在大肆时间里被侵夺,来调整另二个线程。操作系统为每一个线程分配了0-31中的某一流优先级,况且会把前期级高的线程优分给CPU实施。

         
Windows帮助7个相对线程优先级:Idle、Lowest、BelowNormal、Normal、AboveNormal、Highest和Time-Critical。在那之中,Normal是默许的线程优先级。程序能够通过安装Thread的Priority属性来退换线程的优先级,该属性的类型为ThreadPriority枚举类型,其成员包含Lowest、BelowNormal、Normal、Above诺玛l和Highest。CL凯雷德为和煦保留了Idle和Time-Critical多个先行级。

 

    1.3 线程也分前后台

         
线程有前台线程和后台线程之分。在三个进度中,当全体前台线程甘休运转后,CL悍马H2会强制截至全体仍在运作的后台线程,这一个后台线程被直接终止,却不会抛出其余极度。主线程将一向是前台线程。大家能够动用Tread类来创立前台线程。

 1 using System;
 2 using System.Threading;
 3 
 4 namespace 多线程1
 5 {
 6     internal class Program
 7     {
 8         private static void Main(string[] args)
 9         {
10             var backThread = new Thread(Worker);
11             backThread.IsBackground = true;
12             backThread.Start();
13             Console.WriteLine("从主线程退出");
14             Console.ReadKey();
15         }
16 
17         private static void Worker()
18         {
19             Thread.Sleep(1000);
20             Console.WriteLine("从后台线程退出");
21         }
22     }
23 }

   
以上代码先通过Thread类创造了一个线程对象,然后经过设置IsBackground属性来指明该线程为后台线程。假如不安装那天性情,则默感觉前台线程。接着调用了Start的艺术,那时候后台线程会施行Worker函数的代码。所以在这里个顺序中有两个线程,多个是运作Main函数的主线程,一个是运营Worker线程的后台线程。由于前台线程施行达成后CLLX570会无条件地平息后台线程的运行,所以在前边的代码中,若运营了后台线程,则主线程将会一而再接二连三运转。主线程实施完后,CL兰德瑞虎发掘主线程甘休,会告生机勃勃段落后台线程,然后使整个应用程序截至运行,所以Worker函数中的Console语句将不会实践。所以地点代码的结果是不会运转Worker函数中的Console语句的。

   
 能够行使Join函数的点子,确定保障主线程会在后台线程实施达成后才起头运转。

 1 using System;
 2 using System.Threading;
 3 
 4 namespace 多线程1
 5 {
 6     internal class Program
 7     {
 8         private static void Main(string[] args)
 9         {
10             var backThread = new Thread(Worker);
11             backThread.IsBackground = true;
12             backThread.Start();
13             backThread.Join();
14             Console.WriteLine("从主线程退出");
15             Console.ReadKey();
16         }
17 
18         private static void Worker()
19         {
20             Thread.Sleep(1000);
21             Console.WriteLine("从后台线程退出");
22         }
23     }
24 }

    以上代码调用Join函数来确认保障主线程会在后台线程结束后再运行。

    纵然您线程试行的法子必要参数,则就须求使用new
Thread的重载构造函数Thread(ParameterizedThreadStart).

 1 using System;
 2 using System.Threading;
 3 
 4 namespace 多线程1
 5 {
 6     internal class Program
 7     {
 8         private static void Main(string[] args)
 9         {
10             var backThread = new Thread(new ParameterizedThreadStart(Worker));
11             backThread.IsBackground = true;
12             backThread.Start("Helius");
13             backThread.Join();
14             Console.WriteLine("从主线程退出");
15             Console.ReadKey();
16         }
17 
18         private static void Worker(object data)
19         {
20             Thread.Sleep(1000);
21             Console.WriteLine($"传入的参数为{data.ToString()}");
22         }
23     }
24 }

   
实践结果为:图片 2

 

2、线程的器皿——线程池

   
前边大家都以通过Thead类来手动创造线程的,可是线程的创导和销毁会损耗大批量时辰,那样的手动操作将促成质量损失。由此,为了防止因经过Thread手动创设线程而引致的损失,.NET引进了线程池机制。

    2.1 线程池

       
 线程池是指用来贮存在应用程序中要利用的线程集结,能够将它精晓为三个存放线程之处,这种集聚寄存的方法有利对线程举行保管。

       
 CL福特Explorer起初化时,线程池中是还没线程的。在里面,线程池维护了二个操作央浼队列,当应用程序想要施行四个异步操作时,须要调用QueueUserWorkItem方法来将相应的天职增添到线程池的央求队列中。线程池完成的代码会从队列中领到,并将其委派给线程池中的线程去施行。若是线程池未有空余的线程,则线程池也会创建叁个新线程去执行提取的义务。而当线程池线程完结某些职责时,线程不会被销毁,而是回到到线程池中,等待响应另一个倡议。由于线程不会被销毁,所以也就幸免了品质损失。记住,线程池里的线程皆现在台线程,暗中认可等级是Normal。

 

    2.2 通过线程池来兑现三二十四线程

         
要使用线程池的线程,须求调用静态方法ThreadPool.QueueUserWorkItem,以钦定线程要调用的方法,该静态方法有七个重载版本:

          public static bool QueueUserWorkItem(WaitCallback callBack);

          public static bool QueueUserWorkItem(WaitCallback
callback,Object state)

         
那四个办法用于向线程池队列增加三个行事先以致三个可选的图景数据。然后,那四个主意就能够马上重临。下边通超过实际例来演示怎样使用线程池来达成十二线程编制程序。

 1 using System;
 2 using System.Threading;
 3 
 4 namespace 多线程2
 5 {
 6     class Program
 7     {
 8         static void Main(string[] args)
 9         {
10             Console.WriteLine($"主线程ID={Thread.CurrentThread.ManagedThreadId}");
11             ThreadPool.QueueUserWorkItem(CallBackWorkItem);
12             ThreadPool.QueueUserWorkItem(CallBackWorkItem,"work");
13             Thread.Sleep(3000);
14             Console.WriteLine("主线程退出");
15             Console.ReadKey();
16         }
17 
18         private static void CallBackWorkItem(object state)
19         {
20             Console.WriteLine("线程池线程开始执行");
21             if (state != null)
22             {
23                 Console.WriteLine($"线程池线程ID={Thread.CurrentThread.ManagedThreadId},传入的参数为{state.ToString()}");
24             }
25             else
26             {
27                 Console.WriteLine($"线程池线程ID={Thread.CurrentThread.ManagedThreadId}");
28             }
29         }
30     }
31 }

结果为:图片 3

 

    2.3 合营式裁撤线程池线程

         .NET
Framework提供了撤回操作的格局,这一个格局是同盟式的。为了撤除叁个操作,必得创制一个System.Threading.CancellationTokenSource对象。上面依然选择代码来演示一下:

using System;
using System.Threading;

namespace 多线程3
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Console.WriteLine("主线程运行");
            var cts = new CancellationTokenSource();
            ThreadPool.QueueUserWorkItem(Callback, cts.Token);
            Console.WriteLine("按下回车键来取消操作");
            Console.Read();
            cts.Cancel();
            Console.ReadKey();
        }

        private static void Callback(object state)
        {
            var token = (CancellationToken) state;
            Console.WriteLine("开始计数");
            Count(token, 1000);
        }

        private static void Count(CancellationToken token, int count)
        {
            for (var i = 0; i < count; i++)
            {
                if (token.IsCancellationRequested)
                {
                    Console.WriteLine("计数取消");
                    return;
                }
                Console.WriteLine($"计数为:{i}");
                Thread.Sleep(300);
            }
            Console.WriteLine("计数完成");
        }
    }
}

结果为:图片 4

 

3、线程同步

   
线程同步计数是指多线程程序中,为了保证前面一个线程,独有等待前面贰个线程实现未来手艺继续推行。那就好比活着中排队买票,在后面包车型大巴人没买到票以前,后边的人总得等待。

    3.1 三十二线程程序中留存的隐患

         
十六线程恐怕还要去拜谒一个分享能源,那将损坏财富中所保存的数额。这种状态下,只可以使用线程同步手艺。

    3.2 使用监视器对象达成线程同步

         
监视器对象(Monitor卡塔 尔(英语:State of Qatar)能够保险线程具有对分享财富的排外访谈权,C#经过lock关键字来提供简化的语法。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading;
 6 using System.Threading.Tasks;
 7 
 8 namespace 线程同步
 9 {
10     class Program
11     {
12         private static int tickets = 100;
13         static object globalObj=new object();
14         static void Main(string[] args)
15         {
16             Thread thread1=new Thread(SaleTicketThread1);
17             Thread thread2=new Thread(SaleTicketThread2);
18             thread1.Start();
19             thread2.Start();
20             Console.ReadKey();
21         }
22 
23         private static void SaleTicketThread2()
24         {
25             while (true)
26             {
27                 try
28                 {
29                     Monitor.Enter(globalObj);
30                     Thread.Sleep(1);
31                     if (tickets > 0)
32                     {
33                         Console.WriteLine($"线程2出票:{tickets--}");
34                     }
35                     else
36                     {
37                         break;
38                     }
39                 }
40                 catch (Exception)
41                 {
42                     throw;
43                 }
44                 finally
45                 {
46                     Monitor.Exit(globalObj);
47                 }
48             }
49         }
50 
51         private static void SaleTicketThread1()
52         {
53             while (true)
54             {
55                 try
56                 {
57                     Monitor.Enter(globalObj);
58                     Thread.Sleep(1);
59                     if (tickets > 0)
60                     {
61                         Console.WriteLine($"线程1出票:{tickets--}");
62                     }
63                     else
64                     {
65                         break;
66                     }
67                 }
68                 catch (Exception)
69                 {
70                     throw;
71                 }
72                 finally
73                 {
74                     Monitor.Exit(globalObj);
75                 }
76             }
77         }
78     }
79 }

   
在以上代码中,首先额外定义了二个静态全局变量globalObj,并将其用作参数字传送递给Enter方法。使用了Monitor锁定的指标急需为援引类型,而无法为值类型。因为在将值类型传递给Enter时,它将被先装箱为三个独立的毒香,之后再传递给Enter方法;而在将变量传递给Exit方法时,也会创立二个单身的引用对象。那个时候,传递给Enter方法的对象和传递给Exit方法的指标区别,Monitor将会抓住SynchronizationLockException极度。

  

    3.3 线程同步手艺存在的标题

       
 (1卡塔尔使用比较冗杂。要用额外的代码把多个线程同不常候做客的数额包围起来,还并无法遗漏。

       
 (2卡塔 尔(阿拉伯语:قطر‎使用线程同步会影响程序品质。因为获取和自由同步锁是亟需时间的;何况决定非常线程先得到锁的时候,CPU也要拓宽谐和。这一个额外的干活都会对质量形成影响。

       
 (3卡塔尔线程同步每一遍只同意二个线程访谈财富,那会促成线程拥塞。继而系统会创立越多的线程,CPU也就要各负其责更艰辛的调治职业。这些历程会对品质变成影响。

           上边就由代码来解释一下质量的异样:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Diagnostics;
 4 using System.Linq;
 5 using System.Text;
 6 using System.Threading;
 7 using System.Threading.Tasks;
 8 
 9 namespace 线程同步2
10 {
11     class Program
12     {
13         static void Main(string[] args)
14         {
15             int x = 0;
16             const int iterationNumber = 5000000;
17             Stopwatch stopwatch=Stopwatch.StartNew();
18             for (int i = 0; i < iterationNumber; i++)
19             {
20                 x++;
21             }
22             Console.WriteLine($"不使用锁的情况下花费的时间:{stopwatch.ElapsedMilliseconds}ms");
23             stopwatch.Restart();
24             for (int i = 0; i < iterationNumber; i++)
25             {
26                 Interlocked.Increment(ref x);
27             }
28             Console.WriteLine($"使用锁的情况下花费的时间:{stopwatch.ElapsedMilliseconds}ms");
29             Console.ReadKey();
30         }
31     }
32 }

   
试行结果:图片 5

    实施出结论。

1、八线程编制程序必备知识 1.1 进度与线程的概念
当大家开拓三个应用程序后,操作系统就能够为该…

  • C#四十八线程编制程序种类(二卡塔尔-
    线程根基

    • 1.1
      简介
    • 1.2
      创立线程
    • 1.3
      暂停线程
    • 1.4
      线程等待
    • 1.5
      终止线程
    • 1.6
      检查评定线程状态
    • 1.7
      线程优先级
    • 1.8
      前台线程和后台线程
    • 1.9
      向线程传递参数
    • 1.10 C#
      Lock关键字的应用
    • 1.11
      使用Monitor类锁定资源
    • 1.12
      八线程中拍卖极其
  • 仿照效法书籍
  • 作者水平有限,即便不当款待各位商议指正!

C#八线程编程连串(二卡塔 尔(英语:State of Qatar)- 线程底蕴


1.1 简介

线程基本功首要满含线程创造、挂起、等待和终止线程。关于更加多的线程的最底层达成,CPU时间片轮转等等的文化,能够参见《深入理解计算机系统》风流罗曼蒂克书中关于进度和线程的章节,本文不过多废话。

1.2 创制线程

在C#语言中,创制线程是后生可畏件特别简单的事体;它只须要用到
System.Threading命名空间,个中首要使用Thread类来创建线程。

事必躬亲代码如下所示:

using System;
using System.Threading; // 创建线程需要用到的命名空间
namespace Recipe1
{
    class Program
    {
        static void Main(string[] args)
        {
            // 1.创建一个线程 PrintNumbers为该线程所需要执行的方法
            Thread t = new Thread(PrintNumbers);
            // 2.启动线程
            t.Start();

            // 主线程也运行PrintNumbers方法,方便对照
            PrintNumbers();
            // 暂停一下
            Console.ReadKey();
        }

        static void PrintNumbers()
        {
            // 使用Thread.CurrentThread.ManagedThreadId 可以获取当前运行线程的唯一标识,通过它来区别线程
            Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 开始打印...");
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 打印:{i}");
            }
        }
    }
}

运营结果如下图所示,大家能够通过运营结果得知上边的代码创造了三个线程,然后主线程和创办的线程交叉输出结果,那表达PrintNumbers办法同不时候运营在主线程和别的二个线程中。

图片 6

1.3 暂停线程

停顿线程这里运用的方式是因而Thread.Sleep措施,假如线程实行Thread.Sleep方法,那么操作系统就要钦命的日子内不为该线程分配任几时间片。若是Sleep时间100ms那么操作系统将最少让该线程睡眠100ms只怕越来越长日子,所以Thread.Sleep方法不能用作高精度的电磁照管计时器使用。

示范代码如下所示:

using System;
using System.Threading; // 创建线程需要用到的命名空间
namespace Recipe2
{
    class Program
    {
        static void Main(string[] args)
        {
            // 1.创建一个线程 PrintNumbers为该线程所需要执行的方法
            Thread t = new Thread(PrintNumbersWithDelay);
            // 2.启动线程
            t.Start();

            // 暂停一下
            Console.ReadKey();
        }

        static void PrintNumbersWithDelay()
        {
            Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 开始打印... 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}");
            for (int i = 0; i < 10; i++)
            {
                //3. 使用Thread.Sleep方法来使当前线程睡眠,TimeSpan.FromSeconds(2)表示时间为 2秒
                Thread.Sleep(TimeSpan.FromSeconds(2));
                Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 打印:{i} 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}");
            }
        }
    }
}

运维结果如下图所示,通过下图能够鲜明上边的代码是卓有功效的,通过Thread.Sleep方法,使线程休眠了2秒左右,不过并非特意纯粹的2秒。验证了地点的传教,它的睡觉是最少让线程睡眠多久,而不是必然多久。

图片 7

1.4 线程等待

在本章中,线程等待使用的是Join方法,该方法将中断实践当前线程,直到所等待的另一个线程终止。在简约的线程同步中会使用到,但它比较简单,不作过多介绍。

演示代码如下所示:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine($"-------开始执行 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}-------");

        // 1.创建一个线程 PrintNumbersWithDelay为该线程所需要执行的方法
        Thread t = new Thread(PrintNumbersWithDelay);
        // 2.启动线程
        t.Start();
        // 3.等待线程结束
        t.Join();

        Console.WriteLine($"-------执行完毕 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}-------");
        // 暂停一下
        Console.ReadKey();
    }

    static void PrintNumbersWithDelay()
    {
        Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 开始打印... 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}");
        for (int i = 0; i < 10; i++)
        {
            Thread.Sleep(TimeSpan.FromSeconds(2));
            Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 打印:{i} 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}");
        }
    }
}

运维结果如下图所示,伊始奉行和奉行完成两条音信由主线程打字与印刷;依照其出口的顺序可以预知主线程是等待此外的线程截至后才输出实践达成那条消息。

图片 8

1.5 终止线程

悬停线程使用的艺术是Abort主意,当该措施被施行时,将尝试销毁该线程。通过抓住ThreadAbortException卓殊使线程被消逝。但貌似不推荐使用该办法,原因有以下几点。

  1. 使用Abort方式只是尝试行出售毁该线程,但不必然能终止线程。
  2. 黄金时代旦被截至的线程在实施lock内的代码,那么终止线程会以致线程不安全。
  3. 线程终止时,CLWrangler会保障自个儿之中的数据结构不会损坏,可是BCL不可能确认保证。

遵照以上原因不引入应用Abort措施,在实质上项目中日常选取CancellationToken来终止线程。

亲自过问代码如下所示:

static void Main(string[] args)
{
    Console.WriteLine($"-------开始执行 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}-------");

    // 1.创建一个线程 PrintNumbersWithDelay为该线程所需要执行的方法
    Thread t = new Thread(PrintNumbersWithDelay);
    // 2.启动线程
    t.Start();
    // 3.主线程休眠6秒
    Thread.Sleep(TimeSpan.FromSeconds(6));
    // 4.终止线程
    t.Abort();

    Console.WriteLine($"-------执行完毕 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}-------");
    // 暂停一下
    Console.ReadKey();
}

static void PrintNumbersWithDelay()
{
    Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 开始打印... 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}");
    for (int i = 0; i < 10; i++)
    {
        Thread.Sleep(TimeSpan.FromSeconds(2));
        Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 打印:{i} 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}");
    }
}

运维结果如下图所示,运维所成立的线程3后,6分钟主线程调用了Abort措施,线程3从未继续施行便甘休了;与预期的结果黄金时代律。

图片 9

1.6 检查测量试验线程状态

线程的景色可经过访谈ThreadState属性来检查测验,ThreadState是三个枚举类型,生龙活虎共有10种情景,状态具体意思如下表所示。

成员名称 说明
Aborted 线程处于 Stopped 状态中。
AbortRequested 已对线程调用了 Thread.Abort 方法,但线程尚未收到试图终止它的挂起的 System.Threading.ThreadAbortException
Background 线程正作为后台线程执行(相对于前台线程而言)。此状态可以通过设置 Thread.IsBackground 属性来控制。
Running 线程已启动,它未被阻塞,并且没有挂起的 ThreadAbortException
Stopped 线程已停止。
StopRequested 正在请求线程停止。这仅用于内部。
Suspended 线程已挂起。
SuspendRequested 正在请求线程挂起。
Unstarted 尚未对线程调用 Thread.Start 方法。
WaitSleepJoin 由于调用 WaitSleepJoin,线程已被阻止。

下表列出引致情状修改的操作。

操作 ThreadState
在公共语言运行库中创建线程。 Unstarted
线程调用 Start Unstarted
线程开始运行。 Running
线程调用 Sleep WaitSleepJoin
线程对其他对象调用 Wait WaitSleepJoin
线程对其他线程调用 Join WaitSleepJoin
另一个线程调用 Interrupt Running
另一个线程调用 Suspend SuspendRequested
线程响应 Suspend 请求。 Suspended
另一个线程调用 Resume Running
另一个线程调用 Abort AbortRequested
线程响应 Abort 请求。 Stopped
线程被终止。 Stopped

亲自过问代码如下所示:

static void Main(string[] args)
{
    Console.WriteLine("开始执行...");

    Thread t = new Thread(PrintNumbersWithStatus);
    Thread t2 = new Thread(DoNothing);

    // 使用ThreadState查看线程状态 此时线程未启动,应为Unstarted
    Console.WriteLine($"Check 1 :{t.ThreadState}");

    t2.Start();
    t.Start();

    // 线程启动, 状态应为 Running
    Console.WriteLine($"Check 2 :{t.ThreadState}");

    // 由于PrintNumberWithStatus方法开始执行,状态为Running
    // 但是经接着会执行Thread.Sleep方法 状态会转为 WaitSleepJoin
    for (int i = 1; i < 30; i++)
    {
        Console.WriteLine($"Check 3 : {t.ThreadState}");
    }

    // 延时一段时间,方便查看状态
    Thread.Sleep(TimeSpan.FromSeconds(6));

    // 终止线程
    t.Abort();

    Console.WriteLine("t线程被终止");

    // 由于该线程是被Abort方法终止 所以状态为 Aborted或AbortRequested
    Console.WriteLine($"Check 4 : {t.ThreadState}");
    // 该线程正常执行结束 所以状态为Stopped
    Console.WriteLine($"Check 5 : {t2.ThreadState}");

    Console.ReadKey();
}

static void DoNothing()
{
    Thread.Sleep(TimeSpan.FromSeconds(2));
}

static void PrintNumbersWithStatus()
{
    Console.WriteLine("t线程开始执行...");

    // 在线程内部,可通过Thread.CurrentThread拿到当前线程Thread对象
    Console.WriteLine($"Check 6 : {Thread.CurrentThread.ThreadState}");
    for (int i = 1; i < 10; i++)
    {
        Thread.Sleep(TimeSpan.FromSeconds(2));
        Console.WriteLine($"t线程输出 :{i}");
    }
}

运行结果如下图所示,与预期的结果黄金时代致。

图片 10

1.7 线程优先级

Windows操作系统为抢占式二十四线程(Preemptive
multithreaded)操作系统,是因为线程可在任曾几何时刻停止(被枪占卡塔尔并调整另三个线程。

Windows操作系统中线程有0(最低) ~ 31(最高)的优先级,而优先级越高所能占用的CPU时间就越来越多,分明有些线程所处的先行级须求考虑进度优先级对峙线程优先级多个优先级。

  1. 进程优先级:Windows扶植6个进程优先级,分别是Idle、Below Normal、Normal、Above normal、High 和Realtime。默认为Normal
  2. 相对线程优先级:相对线程优先级是对峙于经过优先级的,因为经过包蕴了线程。Windows扶持7个相对线程优先级,分别是Idle、Lowest、Below Normal、Normal、Above Normal、Highest 和 Time-Critical.默认为Normal

下表总计了进程的优先级线程的相持优先级优先级(0~31)的映射关系。粗体为相对线程优先级,斜体为经过优先级

Idle Below Normal Normal Above Normal High Realtime
Time-Critical 15 15 15 15 15 31
Highest 6 8 10 12 15 26
Above Normal 5 7 9 11 14 25
Normal 4 6 8 10 13 24
Below Normal 3 5 7 9 12 23
Lowest 2 4 6 8 11 22
Idle 1 1 1 1 1 16

而在C#前后相继中,可校勘线程的相对优先级,须要设置ThreadPriority性能,可安装为ThreadPriority枚举类型的多个值之意气风发:Lowest、BelowNormal、Normal、AboveNormal 或 Highest。CL路虎极光为协和保留了IdleTime-Critical优先级,程序中不得设置。

示范代码如下所示。

static void Main(string[] args)
{
    Console.WriteLine($"当前线程优先级: {Thread.CurrentThread.Priority} rn");

    // 第一次测试,在所有核心上运行
    Console.WriteLine("运行在所有空闲的核心上");
    RunThreads();
    Thread.Sleep(TimeSpan.FromSeconds(2));

    // 第二次测试,在单个核心上运行
    Console.WriteLine("rn运行在单个核心上");
    // 设置在单个核心上运行
    System.Diagnostics.Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1);
    RunThreads();

    Console.ReadLine();
}

static void RunThreads()
{
    var sample = new ThreadSample();

    var threadOne = new Thread(sample.CountNumbers);
    threadOne.Name = "线程一";
    var threadTwo = new Thread(sample.CountNumbers);
    threadTwo.Name = "线程二";

    // 设置优先级和启动线程
    threadOne.Priority = ThreadPriority.Highest;
    threadTwo.Priority = ThreadPriority.Lowest;
    threadOne.Start();
    threadTwo.Start();

    // 延时2秒 查看结果
    Thread.Sleep(TimeSpan.FromSeconds(2));
    sample.Stop();
}

class ThreadSample
{
    private bool _isStopped = false;

    public void Stop()
    {
        _isStopped = true;
    }

    public void CountNumbers()
    {
        long counter = 0;

        while (!_isStopped)
        {
            counter++;
        }

        Console.WriteLine($"{Thread.CurrentThread.Name} 优先级为 {Thread.CurrentThread.Priority,11} 计数为 = {counter,13:N0}");
    }
}

运作结果如下图所示。Highest并吞的CPU时间显著多于Lowest。当程序运行在装有大旨上时,线程能够在差异宗旨同不经常候运维,所以HighestLowest间距会小一些。

图片 11

1.8 前台线程和后台线程

在CLCRUISER中,线程要么是前台线程,要么正是后台线程。当二个历程的具有前台线程结束运维时,CLEvoque将强制甘休仍在运营的别的后台线程,不会抛出卓殊。

在C#中可因而Thread类中的IsBackground属性来钦点是或不是为后台线程。在线程生命周期中,任哪天候都可在那早先台线程变为后台线程。线程池中的线程默以为后台线程

身体力行代码如下所示。

static void Main(string[] args)
{
    var sampleForeground = new ThreadSample(10);
    var sampleBackground = new ThreadSample(20);
    var threadPoolBackground = new ThreadSample(20);

    // 默认创建为前台线程
    var threadOne = new Thread(sampleForeground.CountNumbers);
    threadOne.Name = "前台线程";

    var threadTwo = new Thread(sampleBackground.CountNumbers);
    threadTwo.Name = "后台线程";
    // 设置IsBackground属性为 true 表示后台线程
    threadTwo.IsBackground = true;

    // 线程池内的线程默认为 后台线程
    ThreadPool.QueueUserWorkItem((obj) => {
        Thread.CurrentThread.Name = "线程池线程";
        threadPoolBackground.CountNumbers();
    });

    // 启动线程 
    threadOne.Start();
    threadTwo.Start();
}

class ThreadSample
{
    private readonly int _iterations;

    public ThreadSample(int iterations)
    {
        _iterations = iterations;
    }
    public void CountNumbers()
    {
        for (int i = 0; i < _iterations; i++)
        {
            Thread.Sleep(TimeSpan.FromSeconds(0.5));
            Console.WriteLine($"{Thread.CurrentThread.Name} prints {i}");
        }
    }
}

运行结果如下图所示。当前台线程13回巡回甘休之后,创立的后台线程和线程池线程都会被CLXC60强制截至。

图片 12

1.9 向线程传递参数

向线程中传送参数常用的有三种艺术,构造函数字传送值、Start方法传值和Lambda表明式传值,日平时用Start方法来传值。

演示代码如下所示,通过三种格局来传递参数,告诉线程中的循环最后要求循环几遍。

static void Main(string[] args)
{
    // 第一种方法 通过构造函数传值
    var sample = new ThreadSample(10);

    var threadOne = new Thread(sample.CountNumbers);
    threadOne.Name = "ThreadOne";
    threadOne.Start();
    threadOne.Join();

    Console.WriteLine("--------------------------");

    // 第二种方法 使用Start方法传值 
    // Count方法 接收一个Object类型参数
    var threadTwo = new Thread(Count);
    threadTwo.Name = "ThreadTwo";
    // Start方法中传入的值 会传递到 Count方法 Object参数上
    threadTwo.Start(8);
    threadTwo.Join();

    Console.WriteLine("--------------------------");

    // 第三种方法 Lambda表达式传值
    // 实际上是构建了一个匿名函数 通过函数闭包来传值
    var threadThree = new Thread(() => CountNumbers(12));
    threadThree.Name = "ThreadThree";
    threadThree.Start();
    threadThree.Join();
    Console.WriteLine("--------------------------");

    // Lambda表达式传值 会共享变量值
    int i = 10;
    var threadFour = new Thread(() => PrintNumber(i));
    i = 20;
    var threadFive = new Thread(() => PrintNumber(i));
    threadFour.Start();
    threadFive.Start();
}

static void Count(object iterations)
{
    CountNumbers((int)iterations);
}

static void CountNumbers(int iterations)
{
    for (int i = 1; i <= iterations; i++)
    {
        Thread.Sleep(TimeSpan.FromSeconds(0.5));
        Console.WriteLine($"{Thread.CurrentThread.Name} prints {i}");
    }
}

static void PrintNumber(int number)
{
    Console.WriteLine(number);
}

class ThreadSample
{
    private readonly int _iterations;

    public ThreadSample(int iterations)
    {
        _iterations = iterations;
    }
    public void CountNumbers()
    {
        for (int i = 1; i <= _iterations; i++)
        {
            Thread.Sleep(TimeSpan.FromSeconds(0.5));
            Console.WriteLine($"{Thread.CurrentThread.Name} prints {i}");
        }
    }
}

运转结果如下图所示,与预期结果相符。

图片 13

1.10 C# Lock关键字的选用

在多线程的系统中,由于CPU的日子片轮转等线程调节算法的接纳,轻松并发线程安全难题。具体可参照《深入理解计算机系统》大器晚成书相关的章节。

在C#中lock第一字是三个语法糖,它将Monitor包装,给object加上二个互斥锁,进而实今世码的线程安全,Monitor会在下风度翩翩节中牵线。

对于lock珍视字还是Monitor锁定的对象,都不得不小心采用,不伏贴的接受恐怕会变成深重的本性难点居然产生死锁。以下有几条有关选拔锁定目的的提议。

  1. 一块锁定的对象不能够是值类型。因为运用值类型时会有装箱的标题,装箱后的就成了多少个新的实例,会引致Monitor.Enter()Monitor.Exit()选拔到差别的实例而错失关联性
  2. 幸免锁定this、typeof(type)和stringthistypeof(type)锁定或然在任何不相干的代码中会有风度翩翩致的概念,以致三个一块块相互堵塞。string内需思索字符串拘押的主题素材,假诺同四个字符串常量在多少个地点现身,也许援用的会是同叁个实例。
  3. 对象的取舍功能域尽或许刚巧达到供给,使用静态的、私有的变量。

以下演示代码落成了十六线程景况下的计数功效,风流洒脱种实现是线程不安全的,会引致结果与预期不合乎,但也会有非常的大可能率正确。其余黄金时代种采取了lock首要字张开线程同步,所以它结果是必然的。

static void Main(string[] args)
{
    Console.WriteLine("错误的多线程计数方式");

    var c = new Counter();
    // 开启3个线程,使用没有同步块的计数方式对其进行计数
    var t1 = new Thread(() => TestCounter(c));
    var t2 = new Thread(() => TestCounter(c));
    var t3 = new Thread(() => TestCounter(c));
    t1.Start();
    t2.Start();
    t3.Start();
    t1.Join();
    t2.Join();
    t3.Join();

    // 因为多线程 线程抢占等原因 其结果是不一定的  碰巧可能为0
    Console.WriteLine($"Total count: {c.Count}");
    Console.WriteLine("--------------------------");

    Console.WriteLine("正确的多线程计数方式");

    var c1 = new CounterWithLock();
    // 开启3个线程,使用带有lock同步块的方式对其进行计数
    t1 = new Thread(() => TestCounter(c1));
    t2 = new Thread(() => TestCounter(c1));
    t3 = new Thread(() => TestCounter(c1));
    t1.Start();
    t2.Start();
    t3.Start();
    t1.Join();
    t2.Join();
    t3.Join();

    // 其结果是一定的 为0
    Console.WriteLine($"Total count: {c1.Count}");

    Console.ReadLine();
}

static void TestCounter(CounterBase c)
{
    for (int i = 0; i < 100000; i++)
    {
        c.Increment();
        c.Decrement();
    }
}

// 线程不安全的计数
class Counter : CounterBase
{
    public int Count { get; private set; }

    public override void Increment()
    {
        Count++;
    }

    public override void Decrement()
    {
        Count--;
    }
}

// 线程安全的计数
class CounterWithLock : CounterBase
{
    private readonly object _syncRoot = new Object();

    public int Count { get; private set; }

    public override void Increment()
    {
        // 使用Lock关键字 锁定私有变量
        lock (_syncRoot)
        {
            // 同步块
            Count++;
        }
    }

    public override void Decrement()
    {
        lock (_syncRoot)
        {
            Count--;
        }
    }
}

abstract class CounterBase
{
    public abstract void Increment();

    public abstract void Decrement();
}

运维结果如下图所示,与预期结果切合。

图片 14

1.11 使用Monitor类锁定能源

Monitor类珍视用以线程同步中,
lock最首要字是对Monitor类的贰个装进,其卷入结构如下代码所示。

try
{
    Monitor.Enter(obj);
    dosomething();
}
catch(Exception ex)
{  
}
finally
{
    Monitor.Exit(obj);
}

以下代码演示了动用Monitor.TyeEnter()方式制止财富死锁和采取lock爆发能源死锁的情景。

        static void Main(string[] args)
        {
            object lock1 = new object();
            object lock2 = new object();

            new Thread(() => LockTooMuch(lock1, lock2)).Start();

            lock (lock2)
            {
                Thread.Sleep(1000);
                Console.WriteLine("Monitor.TryEnter可以不被阻塞, 在超过指定时间后返回false");
                // 如果5S不能进入同步块,那么返回。
                // 因为前面的lock锁定了 lock2变量  而LockTooMuch()一开始锁定了lock1 所以这个同步块无法获取 lock1 而LockTooMuch方法内也不能获取lock2
                // 只能等待TryEnter超时 释放 lock2 LockTooMuch()才会是释放 lock1
                if (Monitor.TryEnter(lock1, TimeSpan.FromSeconds(5)))
                {
                    Console.WriteLine("获取保护资源成功");
                }
                else
                {
                    Console.WriteLine("获取资源超时");
                }
            }

            new Thread(() => LockTooMuch(lock1, lock2)).Start();

            Console.WriteLine("----------------------------------");
            lock (lock2)
            {
                Console.WriteLine("这里会发生资源死锁");
                Thread.Sleep(1000);
                // 这里必然会发生死锁  
                // 本同步块 锁定了 lock2 无法得到 lock1
                // 而 LockTooMuch 锁定了 lock1 无法得到 lock2
                lock (lock1)
                {
                    // 该语句永远都不会执行
                    Console.WriteLine("获取保护资源成功");
                }
            }
        }

        static void LockTooMuch(object lock1, object lock2)
        {
            lock (lock1)
            {
                Thread.Sleep(1000);
                lock (lock2) ;
            }
        }

运行结果如下图所示,因为使用Monitor.TryEnter()办法在逾期从此今后会回到,不会卡住线程,所以未有产生死锁。而第二段代码中lock一贯可是期重回的效率,招致能源死锁,同步块中的代码永恒不会被施行。

图片 15

1.12 八十多线程中管理极其

在三三十二线程中管理特别应当利用前后原则,在哪个线程爆发至极那么所在的代码块肯定要有对应的拾贰分管理。否则大概会引致程序崩溃、数据错过。

主线程中运用try/catch讲话是不能够捕获创造线程中的分外。然而万生机勃勃碰着不可预期的可怜,可通过监听AppDomain.CurrentDomain.UnhandledException事件来张开捕获和丰盛管理。

演示代码如下所示,非凡管理 1 和 相当处理 2 能不奇怪被试行,而不行管理 3
是无效的。

static void Main(string[] args)
{
    // 启动线程,线程代码中进行异常处理
    var t = new Thread(FaultyThread);
    t.Start();
    t.Join();

    // 捕获全局异常
    AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
    t = new Thread(BadFaultyThread);
    t.Start();
    t.Join();

    // 线程代码中不进行异常处理,尝试在主线程中捕获
    AppDomain.CurrentDomain.UnhandledException -= CurrentDomain_UnhandledException;
    try
    {
        t = new Thread(BadFaultyThread);
        t.Start();
    }
    catch (Exception ex)
    {
        // 永远不会运行
        Console.WriteLine($"异常处理 3 : {ex.Message}");
    }
}

private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
    Console.WriteLine($"异常处理 2 :{(e.ExceptionObject as Exception).Message}");
}

static void BadFaultyThread()
{
    Console.WriteLine("有异常的线程已启动...");
    Thread.Sleep(TimeSpan.FromSeconds(2));
    throw new Exception("Boom!");
}

static void FaultyThread()
{
    try
    {
        Console.WriteLine("有异常的线程已启动...");
        Thread.Sleep(TimeSpan.FromSeconds(1));
        throw new Exception("Boom!");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"异常处理 1 : {ex.Message}");
    }
}

运营结果如下图所示,与预期结果相近。

图片 16

参谋书籍

正文首要参谋了以下几本书,在这里对那一个笔者表示真心的多谢你们提供了如此好的素材。

  1. 《CLR via C#》
  2. 《C# in Depth Third Edition》
  3. 《Essential C# 6.0》
  4. 《Multithreading with C# Cookbook Second Edition》

线程底子那生机勃勃章节好不轻便收拾完了,是小编学习进程中的笔记和切磋。布署依据《Multithreading
with C# Cookbook Second
Edition》这本书的协会,黄金时代共更新十一个章节,先立个Flag。


源码下载点击链接
示范源码下载

笔者水平有限,固然不当应接各位商议指正!

admin

网站地图xml地图