久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

C# 多線(xiàn)程(菜鳥(niǎo)教程及愛(ài)整理)

 吳敬銳 2022-12-09 發(fā)布于廣東

線(xiàn)程 被定義為程序的執(zhí)行路徑。每個(gè)線(xiàn)程都定義了一個(gè)獨(dú)特的控制流,。如果您的應(yīng)用程序涉及到復(fù)雜的和耗時(shí)的操作,,那么設(shè)置不同的線(xiàn)程執(zhí)行路徑往往是有益的,每個(gè)線(xiàn)程執(zhí)行特定的工作,。

線(xiàn)程生命周期

線(xiàn)程生命周期開(kāi)始于 System.Threading.Thread 類(lèi)的對(duì)象被創(chuàng)建時(shí),,結(jié)束于線(xiàn)程被終止或完成執(zhí)行時(shí)。

下面列出了線(xiàn)程生命周期中的各種狀態(tài):

  • 未啟動(dòng)狀態(tài):當(dāng)線(xiàn)程實(shí)例被創(chuàng)建但 Start 方法未被調(diào)用時(shí)的狀況,。

  • 就緒狀態(tài):當(dāng)線(xiàn)程準(zhǔn)備好運(yùn)行并等待 CPU 周期時(shí)的狀況,。

  • 不可運(yùn)行狀態(tài):下面的幾種情況下線(xiàn)程是不可運(yùn)行的:

    • 已經(jīng)調(diào)用 Sleep 方法

    • 已經(jīng)調(diào)用 Wait 方法

    • 通過(guò) I/O 操作阻塞

  • 死亡狀態(tài):當(dāng)線(xiàn)程已完成執(zhí)行或已中止時(shí)的狀況。

主線(xiàn)程

進(jìn)程中第一個(gè)被執(zhí)行的線(xiàn)程稱(chēng)為主線(xiàn)程,。

當(dāng) C# 程序開(kāi)始執(zhí)行時(shí),,主線(xiàn)程自動(dòng)創(chuàng)建。使用 Thread 類(lèi)創(chuàng)建的線(xiàn)程被主線(xiàn)程的子線(xiàn)程調(diào)用,。您可以使用 Thread 類(lèi)的 CurrentThread 屬性訪(fǎng)問(wèn)線(xiàn)程,。

下面的程序演示了主線(xiàn)程的執(zhí)行:

實(shí)例

using System ;
using System.Threading ;

namespace MultithreadingApplication
{
    class MainThreadProgram
    {
        static void Main ( string [ ] args )
        {
            Thread th = Thread . CurrentThread ;
            th . Name = 'MainThread' ;
            Console . WriteLine ( 'This is {0}', th . Name ) ;
            Console . ReadKey ( ) ;
        }
    }
}

當(dāng)上面的代碼被編譯和執(zhí)行時(shí),它會(huì)產(chǎn)生下列結(jié)果:

This is MainThread

創(chuàng)建線(xiàn)程

線(xiàn)程是通過(guò)Thread 類(lèi)創(chuàng)建的,。Thread 類(lèi)調(diào)用 Start() 方法來(lái)開(kāi)始子線(xiàn)程的執(zhí)行,。

下面的程序演示了這個(gè)概念:

using System;using System.Threading;namespace MultithreadingApplication{
    class ThreadCreationProgram
    {
        public static void CallToChildThread()
        {
            Console.WriteLine('Child thread starts');
        }
       
        static void Main(string[] args)
        {
            ThreadStart childref = new ThreadStart(CallToChildThread);
            Console.WriteLine('In Main: Creating the Child thread');
            Thread childThread = new Thread(childref);
            childThread.Start();
            Console.ReadKey();
        }
    }}

當(dāng)上面的代碼被編譯和執(zhí)行時(shí),它會(huì)產(chǎn)生下列結(jié)果:

In Main: Creating the Child threadChild thread starts

管理線(xiàn)程

Thread 類(lèi)提供了各種管理線(xiàn)程的方法,。

下面的實(shí)例演示了 sleep() 方法的使用,,用于在一個(gè)特定的時(shí)間暫停線(xiàn)程。

using System;using System.Threading;namespace MultithreadingApplication{
    class ThreadCreationProgram
    {
        public static void CallToChildThread()
        {
            Console.WriteLine('Child thread starts');
            // 線(xiàn)程暫停 5000 毫秒
            int sleepfor = 5000;
            Console.WriteLine('Child Thread Paused for {0} seconds',
                              sleepfor / 1000);
            Thread.Sleep(sleepfor);
            Console.WriteLine('Child thread resumes');
        }
       
        static void Main(string[] args)
        {
            ThreadStart childref = new ThreadStart(CallToChildThread);
            Console.WriteLine('In Main: Creating the Child thread');
            Thread childThread = new Thread(childref);
            childThread.Start();
            Console.ReadKey();
        }
    }}

當(dāng)上面的代碼被編譯和執(zhí)行時(shí),,它會(huì)產(chǎn)生下列結(jié)果:

In Main: Creating the Child threadChild thread startsChild Thread Paused for 5 secondsChild thread resumes

銷(xiāo)毀線(xiàn)程

Abort() 方法用于銷(xiāo)毀線(xiàn)程,。

通過(guò)拋出 threadabortexception 在運(yùn)行時(shí)中止線(xiàn)程。這個(gè)異常不能被捕獲,,如果有 finally 塊,,控制會(huì)被送至 finally 塊。

這個(gè)異常不能被捕獲是什么鬼,可以被捕獲呀

下面的程序說(shuō)明了這點(diǎn):

using System;using System.Threading;namespace MultithreadingApplication{
    class ThreadCreationProgram
    {
        public static void CallToChildThread()
        {
            try
            {

                Console.WriteLine('Child thread starts');
                // 計(jì)數(shù)到 10
                for (int counter = 0; counter <= 10; counter++)
                {
                    Thread.Sleep(500);
                    Console.WriteLine(counter);
                }
                Console.WriteLine('Child Thread Completed');

            }
            catch (ThreadAbortException e)
            {
                Console.WriteLine('Thread Abort Exception');
            }
            finally
            {
                Console.WriteLine('Couldn't catch the Thread Exception');
            }

        }
       
        static void Main(string[] args)
        {
            ThreadStart childref = new ThreadStart(CallToChildThread);
            Console.WriteLine('In Main: Creating the Child thread');
            Thread childThread = new Thread(childref);
            childThread.Start();
            // 停止主線(xiàn)程一段時(shí)間
            Thread.Sleep(2000);
            // 現(xiàn)在中止子線(xiàn)程
            Console.WriteLine('In Main: Aborting the Child thread');
            childThread.Abort();
            Console.ReadKey();
        }
    }}

當(dāng)上面的代碼被編譯和執(zhí)行時(shí),它會(huì)產(chǎn)生下列結(jié)果:

In Main: Creating the Child threadChild thread starts012In Main: Aborting the Child threadThread Abort ExceptionCouldn't catch the Thread Exception

C# 多線(xiàn)程--愛(ài)整理

一,、利用多線(xiàn)程提高程序性能

本節(jié)導(dǎo)讀:

隨著硬件和網(wǎng)絡(luò)的高速發(fā)展,為多線(xiàn)程(Multithreading)處理并行任務(wù),,提供了有利條件,。

其實(shí)我們每時(shí)每刻都在享受多線(xiàn)程帶來(lái)的便利,多核處理器多線(xiàn)程工作,、Windows操作系統(tǒng),、Web服務(wù)器都在使用多線(xiàn)程工作。

使用多線(xiàn)程直接提高了程序的執(zhí)行效率,,因此學(xué)習(xí)多線(xiàn)程對(duì)提高程序運(yùn)行能力非常必要,,本節(jié)主要介紹多線(xiàn)程原理及.NET中多線(xiàn)程在.NET面向?qū)ο蟪绦蛟O(shè)計(jì)中的應(yīng)用。

1. 關(guān)于多線(xiàn)程

在介紹多線(xiàn)程之前,,先了解一下進(jìn)程,。

進(jìn)程:獨(dú)立運(yùn)行的程序稱(chēng)為進(jìn)程。(比如Windows系統(tǒng)后臺(tái)程序,,也可以稱(chēng)為后臺(tái)進(jìn)程)

線(xiàn)程:對(duì)于同一個(gè)程序分為多個(gè)執(zhí)行流,,稱(chēng)為線(xiàn)程。

多線(xiàn)程:使用多個(gè)線(xiàn)程進(jìn)行多任務(wù)處理,,稱(chēng)為多線(xiàn)程,。

并發(fā)是針對(duì)于單核處理器而言,但是目前市場(chǎng)上的CPU是多核的(一個(gè)芯片多個(gè)CPU核心),,多線(xiàn)程設(shè)計(jì)可以讓多個(gè)任務(wù)分發(fā)到多個(gè)CPU上并行執(zhí)行,,可以讓程序更快的執(zhí)行。但是并發(fā)通常是提高運(yùn)行在單核處理器上的程序的性能,。這么說(shuō)感覺(jué)有點(diǎn)違背直覺(jué),,因?yàn)樵趩魏颂幚砥魃线\(yùn)行并發(fā)程序開(kāi)銷(xiāo)要比順序執(zhí)行該程序的開(kāi)銷(xiāo)要大(從上到下順序執(zhí)行程序),因?yàn)椴l(fā)程序中增加了上下文切換的代價(jià)(一個(gè)線(xiàn)程切換到另外一個(gè)線(xiàn)程),,從表面上看如果順序執(zhí)行所有程序反而節(jié)省了上下文切換的代價(jià),。 讓這個(gè)問(wèn)題變得不同的是阻塞,程序中的某個(gè)任務(wù)因?yàn)樵摮绦蚩刂品秶獾囊恍l件(通常是I/O),整個(gè)程序就會(huì)停止下來(lái),,直到外部條件發(fā)生變化,。此時(shí)多線(xiàn)程的優(yōu)勢(shì)就會(huì)體現(xiàn)出來(lái)了,其他任務(wù)可以通過(guò)獲得CPU時(shí)間而繼續(xù)執(zhí)行,,而不會(huì)讓整個(gè)程序停下來(lái),。從性能的角度來(lái)看,如果沒(méi)有線(xiàn)程阻塞,,那么在單核處理器上使用并發(fā)那將毫無(wú)意義,。

2. 如何合理使用多線(xiàn)程?

A.對(duì)于用戶(hù)等待程序處理時(shí),可以使用多線(xiàn)程處理耗時(shí)任務(wù),;

B.對(duì)于一些不需要即時(shí)完成的任務(wù),,可以使用后臺(tái)任務(wù)線(xiàn)程處理;

C.對(duì)于多并發(fā)任務(wù),,可以使用多線(xiàn)程同時(shí)處理,;

這一句的意思是讓并發(fā)線(xiàn)程變成并行線(xiàn)程嗎?即讓原本是單核處理多個(gè)線(xiàn)程,變成多核處理多個(gè)線(xiàn)程(一核分配一個(gè)線(xiàn)程)

D.對(duì)于通訊類(lèi),比如對(duì)線(xiàn)程阻塞,,可以使用多線(xiàn)程,。

除過(guò)上面的幾個(gè)常用的情況,還有很多情況下可以使用多線(xiàn)程,。

3. 多線(xiàn)程的缺點(diǎn)

線(xiàn)程自然也有缺點(diǎn),,以下列出了一些:

A.如果有大量的線(xiàn)程,會(huì)影響性能,,因?yàn)椴僮飨到y(tǒng)需要在他們之間切換,;

B.更多的線(xiàn)程需要更多的內(nèi)存空間;

C.線(xiàn)程會(huì)給程序帶來(lái)更多的bug,,因此要小心使用,,比如:線(xiàn)程任務(wù)在執(zhí)行完成后,要及時(shí)釋放內(nèi)存,;

D.線(xiàn)程的中止需要考慮其對(duì)程序運(yùn)行的影響,。

4. .NET中的兩種多線(xiàn)程

.NET本身就是一個(gè)多線(xiàn)程的的環(huán)境。

在.NET中有兩種多線(xiàn)程的:

一種是使用Thread類(lèi)進(jìn)行線(xiàn)程的創(chuàng)建,、啟動(dòng),,終止等操作。

一種是使用ThreadPool類(lèi)用于管理線(xiàn)程池.

5 .NET中使用Thread進(jìn)行多線(xiàn)程處理

線(xiàn)性池是一種多線(xiàn)程并發(fā)的處理形式,,它就是由一堆已創(chuàng)建好的線(xiàn)程組成,。有新任務(wù) -> 取出空閑線(xiàn)程處理任務(wù) -> 任務(wù)處理完成放入線(xiàn)程池等待。避免了處理短時(shí)間任務(wù)時(shí)大量的線(xiàn)程重復(fù)創(chuàng)建,、銷(xiāo)毀的代價(jià),,非常適用于連續(xù)產(chǎn)生大量并發(fā)任務(wù)的場(chǎng)合。

5.1 Thread類(lèi)常用方法

.NET基礎(chǔ)類(lèi)庫(kù)的System.Threading命名空間提供了大量的類(lèi)和接口支持多線(xiàn)程,。System.Threading.Thread類(lèi)是創(chuàng)建并控制線(xiàn)程,,設(shè)置其優(yōu)先級(jí)并獲取其狀態(tài)最為常用的類(lèi)。

下面是該類(lèi)幾個(gè)至關(guān)重要的方法: Thread.Start():?jiǎn)?dòng)線(xiàn)程的執(zhí)行,; Thread.Suspend():掛起線(xiàn)程,,或者如果線(xiàn)程已掛起,則不起作用,; Thread.Resume():繼續(xù)已掛起的線(xiàn)程,; Thread.Interrupt():中止處于Wait或者Sleep或者Join線(xiàn)程狀態(tài)的線(xiàn)程,; Thread.Join():阻塞調(diào)用線(xiàn)程,直到某個(gè)線(xiàn)程終止時(shí)為止 Thread.Sleep():將當(dāng)前線(xiàn)程阻塞指定的毫秒數(shù),; Thread.Abort():終止此線(xiàn)程,。如果線(xiàn)程已經(jīng)在終止,則不能通過(guò)Thread.Start()來(lái)啟動(dòng)線(xiàn)程,。

suspend 掛起,、暫停 resume 繼續(xù)、重新開(kāi)始 interrupt 中斷,、打斷

5.2 Thread類(lèi)常用屬性

Thread的屬性有很多,我們先看最常用的幾個(gè):

CurrentThread :用于獲取當(dāng)前線(xiàn)程;

ThreadState 當(dāng)前線(xiàn)程的狀態(tài)(5.4介紹),;

Name:獲取或設(shè)置線(xiàn)程名稱(chēng),;

Priority:獲取或設(shè)置線(xiàn)程的優(yōu)先級(jí)(5.5介紹)

ManagedThreadId:獲取當(dāng)前線(xiàn)程的唯一標(biāo)識(shí)

IsBackground:獲取或設(shè)置線(xiàn)程是前臺(tái)線(xiàn)程還是后臺(tái)線(xiàn)程(5.6介紹)

IsThreadPoolThread:獲取當(dāng)前線(xiàn)程是否是托管線(xiàn)程池(后面章節(jié)會(huì)介紹)

下面創(chuàng)建一個(gè)線(xiàn)程示例,來(lái)說(shuō)明這幾個(gè)屬性:

Thread myThreadTest = new Thread(() =>//這里的new Thread(ThreadStart start)里面ThreadStart是一個(gè)委托,而(()=>{代碼塊...})是lambda表達(dá)式,所以可以說(shuō)lambda表達(dá)式是基于委托的{
    Thread.Sleep(1000);
    Thread t = Thread.CurrentThread;
    Console.WriteLine('Name: ' + t.Name);
    Console.WriteLine('ManagedThreadId: ' + t.ManagedThreadId);
    Console.WriteLine('State: ' + t.ThreadState);
    Console.WriteLine('Priority: ' + t.Priority);
    Console.WriteLine('IsBackground: ' + t.IsBackground);
    Console.WriteLine('IsThreadPoolThread: ' + t.IsThreadPoolThread);//process 進(jìn)程}){
    Name = '線(xiàn)程測(cè)試',
    Priority = ThreadPriority.Highest};myThreadTest.Start();Console.WriteLine('關(guān)聯(lián)進(jìn)程的運(yùn)行的線(xiàn)程數(shù)量:'+System.Diagnostics.Process.GetCurrentProcess().Threads.Count);

運(yùn)行結(jié)果如下:

在這里插入圖片描述

我的天竟然有6個(gè)線(xiàn)程,其他四個(gè)線(xiàn)程是?

下面的代碼是一個(gè)小插曲,有助于強(qiáng)化理解線(xiàn)程委托

using System;using System.Threading;namespace MultithreadingApplication{
    delegate void P1(object n);
    delegate void P2(int n1, int n2);
    class ThreadCreationProgram
    {
        public static void MyThreadStart0()
        {
            Console.WriteLine('我的線(xiàn)程:0');
        }
        public static void MyThreadStart11(object n)
        {
            for (int i = 0; i < (int)n; i++)
            {
                Console.WriteLine('我的線(xiàn)程:' + i);
            }
        }
        public static void MyThreadStart12(int n)
        {
            for (int i = 0; i < n; i++)
            {
                Console.WriteLine('我的線(xiàn)程:' + i);
            }
        }
        public static void MyThreadStart2(int n1, int n2)
        {
            for (int i = n1; i < n2; i++)
            {
                Console.WriteLine('我的線(xiàn)程:' + i);
            }
        }
      
        static void Main(string[] args)
        {
            //平常情況(自定義委托(參數(shù),返回值情況任意))
            P1 p1 = new P1(MyThreadStart11);
            p1(5);
            P2 p2 = new P2(MyThreadStart2);
            p2(2, 5);

            //線(xiàn)程情況(系統(tǒng)線(xiàn)程定義委托(總共兩個(gè):1無(wú)參無(wú)返回值 2有一個(gè)參無(wú)返回值)
            ThreadStart ts = new ThreadStart(MyThreadStart0);
            ts();
            ParameterizedThreadStart pts = new ParameterizedThreadStart(MyThreadStart11);//實(shí)例委托pts就相當(dāng)于一個(gè)方法指針,指向一個(gè)方法
            pts(5);

            //我的天在線(xiàn)程中委托的參數(shù)居然是從start()里面?zhèn)鬟M(jìn)去的
            new Thread(pts).Start(5);

            //注意下面兩個(gè)的比較
            new Thread((n) => MyThreadStart11(n)).Start(5);//lambda表達(dá)式:(n) => MyThreadStart11(n)就像當(dāng)于委托public delegate void ParameterizedThreadStart(object obj)的一個(gè)實(shí)例,所以傳進(jìn)的參數(shù)n就是object類(lèi)型,下面的語(yǔ)句需要(int)n進(jìn)行強(qiáng)制轉(zhuǎn)換.
            new Thread((n) => MyThreadStart12((int)n)).Start(5);//委托的方法參數(shù)類(lèi)型與委托的參數(shù)數(shù)據(jù)類(lèi)型不一致,此處卻可以這樣搞,就當(dāng)是封裝的原因吧
           
           new Thread(MyThreadStart0);//甚至可以直接跟個(gè)方法名里面的括號(hào)都省略了
            Console.ReadKey();
        }
    }}

5.3 帶參數(shù)的線(xiàn)程方法

首先我們寫(xiě)“簡(jiǎn)單線(xiàn)程”中無(wú)參數(shù)的方法,,如下:

注意看注釋

namespace MultithreadingApplication{
    class ThreadCreationProgram
    {
        static void MyThreadStart()
        {
            Console.WriteLine('我是一個(gè)簡(jiǎn)單線(xiàn)程');
        }
        static void Main(string[] args)
        {
            //簡(jiǎn)單的線(xiàn)程
            Thread myThread = new Thread(MyThreadStart);//此時(shí)的Thread(MyThreadStart)等于Thread(() =>MyThreadStart()),可能是lambda express的簡(jiǎn)寫(xiě)形式
            //那么也就是說(shuō)在無(wú)參方法調(diào)用委托時(shí)可以不用先實(shí)例化一個(gè)委托ThreadStart ts = new ThreadStart(MyThreadStart)然后再將ts傳進(jìn)線(xiàn)程Thread myThread = new Thread(ts),而可以直接在線(xiàn)程里傳此方法Thread myThread = new Thread(MyThreadStart),這樣的話(huà)就簡(jiǎn)單一些了
            myThread.Start();
        }     
    }}

我們使用Lambda表達(dá)式來(lái)改寫(xiě)前面“簡(jiǎn)單線(xiàn)程”中無(wú)參數(shù)的方法,,如下:

namespace MultithreadingApplication{
    class ThreadCreationProgram
    {
        static void Main(string[] args)
        {
            new Thread(() => {  //此處可以說(shuō)用無(wú)參的方法調(diào)用委托
                for (int i = 0; i < 5; i++)
                    Console.WriteLine('我的線(xiàn)程一-[{0}]', i);
            }).Start(); Console.ReadKey();
        }     
    }}

在這里插入圖片描述

上面示例創(chuàng)建的線(xiàn)程并沒(méi)有帶參數(shù),如果是一個(gè)有參數(shù)的方法,,線(xiàn)程該如何創(chuàng)建,?

別擔(dān)心,.NET為我們提供了一個(gè)ParameterizedThreadStart 委托 來(lái)解決帶一個(gè)參數(shù)的問(wèn)題,,如下:

new Thread((num) =>{
    for (int i = 0; i < (int)num; i++)
        Console.WriteLine('我的線(xiàn)程二--[{0}]', i);}).Start(5);/*由于ParameterizedThreadStart 委托傳的值是object類(lèi)型的,所以要強(qiáng)制轉(zhuǎn)化一下*/

運(yùn)行結(jié)果如下:

在這里插入圖片描述

那么問(wèn)題來(lái)了,,ParameterizedThreadStart委托只有一個(gè)包含數(shù)據(jù)的參數(shù), 對(duì)于多個(gè)參數(shù)呢,?我們可以使用一個(gè)無(wú)參數(shù)的方法來(lái)包裝它,,如下:

先創(chuàng)建一個(gè)帶參數(shù)的方法:

static void myThreadStart(int numA, int numB){
    for (int i = (int)numA; i < (int)numB; i++)
        Console.WriteLine('我的線(xiàn)程三---[{0}]', i);}

然后通過(guò)無(wú)參數(shù)的委托來(lái)包裝它,如下 :

//這里默認(rèn)匹配是public delegate void ThreadStart();的一個(gè)實(shí)例new Thread(() => myThreadStart(0, 5)).Start();//事到如今我感覺(jué)可以肯定的說(shuō)lambda表達(dá)式實(shí)質(zhì)上就是一個(gè)委托

運(yùn)行結(jié)果如下:

在這里插入圖片描述

5.4 Thread狀態(tài)

我們對(duì)于線(xiàn)程啟動(dòng)以后,,如何進(jìn)行掛起和終止,、重新啟用,首先線(xiàn)程在運(yùn)行后有一個(gè)狀態(tài),。

System.Threading.Thread.ThreadState屬性定義了執(zhí)行時(shí)線(xiàn)程的狀態(tài),。線(xiàn)程從創(chuàng)建到線(xiàn)程終止,它一定處于其中某一個(gè)狀態(tài),。

A.Unstarted:當(dāng)線(xiàn)程被創(chuàng)建時(shí),,它處在Unstarted狀態(tài)。

B.Running:Thread類(lèi)的Start() 方法將使線(xiàn)程狀態(tài)變?yōu)镽unning狀態(tài),,線(xiàn)程將一直處于這樣的狀態(tài),,除非我們調(diào)用了相應(yīng)的方法使其掛起、阻塞,、銷(xiāo)毀或者自然終止,。

C.Suspended:如果線(xiàn)程被掛起,,它將處于Suspended狀態(tài)。

D.Running:我們調(diào)用Resume()方法使其重新執(zhí)行,,這時(shí)候線(xiàn)程將重新變?yōu)镽unning狀態(tài),。

E.Stopped:一旦線(xiàn)程被銷(xiāo)毀或者終止,線(xiàn)程處于Stopped狀態(tài),。處于這個(gè)狀態(tài)的線(xiàn)程將不復(fù)存在,,正如線(xiàn)程開(kāi)始啟動(dòng),線(xiàn)程將不可能回到Unstarted狀態(tài),。

F.Background:線(xiàn)程還有一個(gè)Background狀態(tài),,它表明線(xiàn)程運(yùn)行在前臺(tái)還是后臺(tái)。在一個(gè)確定的時(shí)間,,線(xiàn)程可能處于多個(gè)狀態(tài),。

G.WaitSleepJoin、AbortRequested:舉例子來(lái)說(shuō),,一個(gè)線(xiàn)程被調(diào)用了Sleep而處于阻塞,,而接著另外一個(gè)線(xiàn)程調(diào)用Abort方法于這個(gè)阻塞的線(xiàn)程,這時(shí)候線(xiàn)程將同時(shí)處于WaitSleepJoin和AbortRequested狀態(tài),。

H.一旦線(xiàn)程響應(yīng)轉(zhuǎn)為Sleep阻塞或者中止,,當(dāng)銷(xiāo)毀時(shí)會(huì)拋出ThreadAbortException異常。

ThreadState枚舉的10種執(zhí)行狀態(tài)如下:

在這里插入圖片描述

上圖了解一個(gè)WaitSleepJoin就可以了 monitor 監(jiān)視器,監(jiān)聽(tīng)器,監(jiān)控器

對(duì)于線(xiàn)程阻塞和同步問(wèn)題,,將在下一節(jié)繼續(xù)介紹,。

5.5. 線(xiàn)程優(yōu)先級(jí)

對(duì)于多線(xiàn)程任務(wù),我們可以根據(jù)其重要性和運(yùn)行所需要的資源情況,,設(shè)置他的優(yōu)先級(jí) System.Threading.ThreadPriority枚舉了線(xiàn)程的優(yōu)先級(jí)別,,從而決定了線(xiàn)程能夠得到多少CPU時(shí)間。

高優(yōu)先級(jí)的線(xiàn)程通常會(huì)比一般優(yōu)先級(jí)的線(xiàn)程得到更多的CPU時(shí)間

主線(xiàn)程與各種線(xiàn)程(高優(yōu)先級(jí),低優(yōu)先級(jí)線(xiàn)程)搶奪cup資源,一般優(yōu)先級(jí)越高搶到cup資源的概率越高,從而cpu占用的時(shí)間越多.

而不是每個(gè)線(xiàn)程一個(gè)一個(gè)排序來(lái),排到高優(yōu)先級(jí)線(xiàn)程時(shí)cup分配的時(shí)間多一些,排到低優(yōu)先級(jí)線(xiàn)程時(shí)cup分配的時(shí)間少一些,

新創(chuàng)建的線(xiàn)程優(yōu)先級(jí)為一般優(yōu)先級(jí),,我們可以設(shè)置線(xiàn)程的優(yōu)先級(jí)別的值,,如下面所示:

在這里插入圖片描述

線(xiàn)程搶占cpu資源可以用如下代碼測(cè)試:

static void Main(string[] args){
    int numberH1 = 0,numberH2=0, numberL1 = 0, numberL2=0;
    bool state = true;
    new Thread(() => { while (state) { numberH1++; Console.WriteLine('H1'); }; }) { Priority = ThreadPriority.Highest, Name = '線(xiàn)程A' }.Start();
    new Thread(() => { while (state) { numberH2++; Console.WriteLine('H2'); }; }) { Priority = ThreadPriority.Highest, Name = '線(xiàn)程A' }.Start();
    new Thread(() => { while (state) { numberL1++; Console.WriteLine('L1'); }; }) { Priority = ThreadPriority.Lowest, Name = '線(xiàn)程B' }.Start();
    //讓主線(xiàn)程掛件1秒
    Thread.Sleep(1000);
    state = false;
    Console.WriteLine('線(xiàn)程H1: {0}, 線(xiàn)程H2: {1}, 線(xiàn)程L1: {2}', numberH1,numberH2,numberL1);
    Console.ReadKey();}

5.6 前臺(tái)線(xiàn)程和后臺(tái)線(xiàn)程

線(xiàn)程有兩種,默認(rèn)情況下為前臺(tái)線(xiàn)程,,要想設(shè)置為后臺(tái)線(xiàn)程也非常容易,,只需要加一個(gè)屬性:thread.IsBackground = true;就可以變?yōu)橐粋€(gè)后臺(tái)線(xiàn)程了。

重點(diǎn)來(lái)了,,前后臺(tái)線(xiàn)程的區(qū)別:

A.前臺(tái)線(xiàn)程:應(yīng)用程序必須執(zhí)行完所有的前臺(tái)線(xiàn)程才能退出,;

B.后臺(tái)線(xiàn)程:應(yīng)用程序不必考慮其是否全部完成,可以直接退出,。應(yīng)用程序退出時(shí),,自動(dòng)終止后臺(tái)線(xiàn)程。

下面我們使用一個(gè)輸出從0到1000的數(shù)字,,來(lái)實(shí)驗(yàn)一下前臺(tái)線(xiàn)程和后臺(tái)線(xiàn)程的區(qū)別:

static void Main(string[] args){
    Thread myThread = new Thread(() => { for (int i = 0; i < 1000; i++) Console.WriteLine(i); });

    var key = Console.ReadLine();
    if (key == '1')
    {
        myThread.IsBackground = true;
        myThread.Start();
    }
    else
    {
        myThread.IsBackground = false;
        myThread.Start();
    }}

如果輸入1(后臺(tái)線(xiàn)程),,線(xiàn)程會(huì)很快關(guān)閉,,并不會(huì)等輸出完1000個(gè)數(shù)字再關(guān)閉;

如果輸入其它(前臺(tái)線(xiàn)程),,回車(chē)后,,則線(xiàn)程會(huì)等1000個(gè)數(shù)字輸出完后,窗口關(guān)閉,;

6. 本節(jié)要點(diǎn):

A.本節(jié)主要介紹了線(xiàn)程的基本知識(shí),;

B.Thread常用的屬性、方法;

C.Thread委托的方法有多個(gè)參數(shù)的用法,;

D.Thread的優(yōu)先級(jí),;

E.Thread的執(zhí)行狀態(tài);

F.前臺(tái)線(xiàn)程和后臺(tái)線(xiàn)程,;

后面會(huì)繼續(xù)深入介紹利用線(xiàn)程提高程序性能,。

二、多線(xiàn)程高級(jí)應(yīng)用

本節(jié)要點(diǎn):

上節(jié)介紹了多線(xiàn)程的基本使用方法和基本應(yīng)用示例,,本節(jié)深入介紹.NET多線(xiàn)程中的高級(jí)應(yīng)用。

主要有在線(xiàn)程資源共享中的線(xiàn)程安全和線(xiàn)程沖突的解決方案,;多線(xiàn)程同步,,使用線(xiàn)程鎖和線(xiàn)程通知實(shí)現(xiàn)線(xiàn)程同步。

1,、 ThreadStatic特性

特性:[ThreadStatic]

功能:指定靜態(tài)字段在不同線(xiàn)程中擁有不同的值

在此之前,,我們先看一個(gè)多線(xiàn)程的示例:

我們定義一個(gè)靜態(tài)字段:

static int num = 0;new Thread(() =>{
    for (int i = 0; i < 1000000; i++)
        ++num;
    Console.WriteLine('來(lái)自{0}:{1}', Thread.CurrentThread.Name, num);}){ Name = '線(xiàn)程一' }.Start();隱藏代碼new Thread(() =>{
    for (int i = 0; i < 2000000; i++)
        ++num;
    Console.WriteLine('來(lái)自{0}:{1}', Thread.CurrentThread.Name, num);}){ Name = '線(xiàn)程二' }.Start();

運(yùn)行多次結(jié)果如下:

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

可以看到,三次的運(yùn)行結(jié)果均不相同,產(chǎn)生這種問(wèn)題的原因是多線(xiàn)程中同步共享問(wèn)題導(dǎo)致的,,即是多個(gè)線(xiàn)程同時(shí)共享了一個(gè)資源,。

此處代碼與上下文無(wú)關(guān),知識(shí)兩個(gè)疑惑,注意看注釋

using System;using System.Threading;namespace MultithreadingApplication{
    class ThreadCreationProgram
    {       
        static int num = 0;
        static void Main(string[] args)
        {
            new Thread(() =>
            {
                int k=0;
                for (k = 0; k < 100000; k++) 
                    ++num;
                Console.WriteLine('來(lái)自{0}:{1} 此時(shí)k的值為{2}', Thread.CurrentThread.Name, num,k);
            })
            { Name = '線(xiàn)程一' }.Start();

            new Thread(() =>
            {
                int j = 0;
                for (; j < 200000; j++)
                    ++num;
                Console.WriteLine('來(lái)自{0}:{1} 此時(shí)j的值為{2}', Thread.CurrentThread.Name, num,j);
            })
            { Name = '線(xiàn)程二' }.Start();

            Thread.Sleep(5*1000);
            Console.WriteLine('主線(xiàn)程又開(kāi)始');
            Console.ReadKey();
        }
    }
    //疑惑 1 兩個(gè)線(xiàn)程執(zhí)行次數(shù)竟然大于3000000,也竟然有小于3000000的   
    //2 竟然會(huì)輸出:來(lái)自線(xiàn)程一:56265   答;這是因?yàn)椴粌H只有兩個(gè)線(xiàn)程在執(zhí)行,還有個(gè)主線(xiàn)程在執(zhí)行,不要忽略了.
    //因?yàn)橹骶€(xiàn)程走到了Console.ReadKey(),所以會(huì)在控制臺(tái)輸出線(xiàn)程一還未走完的num值,此時(shí)num值也就小于1000000了
    //那么為了避免主線(xiàn)程對(duì)子線(xiàn)程的影響可以阻塞主線(xiàn)程一段時(shí)間知道子線(xiàn)程完成(用sleep方法)--我的天吶我發(fā)現(xiàn)排除了主線(xiàn)程readkey的干擾后
    //仍然會(huì)輸出:來(lái)自線(xiàn)程一:989265的情況者,這發(fā)生了什么?}

如何解決上述問(wèn)題,最簡(jiǎn)單的方法就是使用靜態(tài)字段的ThreadStatic特性,。

在定義靜態(tài)字段時(shí),,加上[ThreadStatic]特性,如下:

[ThreadStatic]static int num = 0;

兩個(gè)線(xiàn)程不變的情況下,,再次運(yùn)行,,結(jié)果如下:

在這里插入圖片描述

不論運(yùn)行多少次,結(jié)果都是一樣的,,當(dāng)字段被ThreadStatic特性修飾后,,它的值在每個(gè)線(xiàn)程中都是不同的,即每個(gè)線(xiàn)程對(duì)static字段都會(huì)重新分配內(nèi)存空間,,就當(dāng)然于一次new操作,,這樣一來(lái),由于static字段所產(chǎn)生的問(wèn)題也就沒(méi)有了,。

2. 資源共享

多線(xiàn)程的資源共享,,也就是多線(xiàn)程同步(即資源同步),,需要注意的是線(xiàn)程同步指的是線(xiàn)程所訪(fǎng)問(wèn)的資源同步,并非是線(xiàn)程本身的同步,。

在實(shí)際使用多線(xiàn)程的過(guò)程中,,并非都是各個(gè)線(xiàn)程訪(fǎng)問(wèn)不同的資源。

下面看一個(gè)線(xiàn)程示例,,假如我們并不知道線(xiàn)程要多久完成,,我們等待一個(gè)固定的時(shí)間(假如是500毫秒):

先定義一個(gè)靜態(tài)字段:

static int result;Thread myThread = new Thread(() =>{
    Thread.Sleep(1000);
    result = 100;});myThread.Start();Thread.Sleep(500);             Console.WriteLine(result);

運(yùn)行結(jié)果如下:

在這里插入圖片描述

可以看到結(jié)果是0,顯然不是我們想要的,,但往往在線(xiàn)程執(zhí)行過(guò)程中,,我們并不知道它要多久完成,能不能在線(xiàn)程完成后有一個(gè)通知,?

下面的代碼與上下文無(wú)關(guān),只是一個(gè)小注意點(diǎn)

using System;using System.Threading;namespace MultithreadingApplication{
    class ThreadCreationProgram
    {
        static int result;
        static void Main(string[] args)
        {
           
            Thread myThread = new Thread(() =>
            {
                Thread.Sleep(10000);
                result = 100;
                Console.WriteLine(result);
                Console.ReadKey();
            });
            myThread.Start();//這一步再往下走兩個(gè)線(xiàn)程就開(kāi)始搶奪cup資源了
            Thread.Sleep(1000);
            Console.WriteLine(result);
            Console.ReadKey();//執(zhí)行完這一步,并不會(huì)就一直停在這里,當(dāng)myThread線(xiàn)程睡眠時(shí)間到了,會(huì)自動(dòng)執(zhí)行myThread線(xiàn)程
           //然后停在myThread的Readkey處,在控制臺(tái)輸入任意值,走到24,再輸入任意值,走到18.
        }
    }}

.NET為我們提供了一個(gè)Join方法,,就是線(xiàn)程阻塞,可以解決上述問(wèn)題,,我們使用Stopwatch來(lái)記時(shí)

改進(jìn)線(xiàn)程代碼如下:

using System;using System.Threading;namespace MultithreadingApplication{
    class ThreadCreationProgram
    {
        static int result;
        static void Main(string[] args)
        {
            //Diagnostic 診斷.   Stopwatch 跑表   
            //StartNew()初始化新的 System.Diagnostics.Stopwatch 實(shí)例,,將運(yùn)行時(shí)間屬性設(shè)置為零,然后開(kāi)始測(cè)量運(yùn)行時(shí)間,。
            System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
            Thread myThread = new Thread(() =>
            {
                Thread.Sleep(1000);
                result = 100;
            });
            myThread.Start();
            Thread.Sleep(500);//走到這一步主線(xiàn)程睡眠,進(jìn)入子線(xiàn)程myThread
            myThread.Join();//0.5秒鐘后回到主線(xiàn)程這一步,走到這一步時(shí)會(huì)停下來(lái)直到子線(xiàn)程myThread執(zhí)行完畢.
            Console.WriteLine(watch.ElapsedMilliseconds);//Elapsed 消逝,、過(guò)去    Millisecond 毫秒
            Console.WriteLine(result);
            Console.ReadKey();
        }
    }}//Join()和sleep()都是線(xiàn)程阻塞

運(yùn)行結(jié)果如下:

在這里插入圖片描述

結(jié)果和我們想要的是一致的。

3. 線(xiàn)程鎖

除了上面示例的方法,,對(duì)于線(xiàn)程同步,,.NET還為我們提供了一個(gè)鎖機(jī)制來(lái)解決同步,再次改進(jìn)上面示例如下:

using System;using System.Threading;namespace MultithreadingApplication{
    class ThreadCreationProgram
    {
        //先定義一個(gè)靜態(tài)字段來(lái)存儲(chǔ)鎖
        static object locker = new object();
        static int result;
        static void Main(string[] args)
        {
            System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
            Thread t1 = new Thread(() =>
            {
                lock (locker)//lock (x)里面的x是引用類(lèi)型
                {
                    Thread.Sleep(10000);
                    result = 100;
                }
            });
            t1.Start();
            Thread.Sleep(5000);
            lock (locker)//lock(x)中的x是同一個(gè)引用類(lèi)型的變量時(shí),這些鎖之間是互斥的,只有最先執(zhí)行的鎖執(zhí)行完,才會(huì)執(zhí)行下一個(gè)鎖
            {
                Console.WriteLine('線(xiàn)程耗時(shí):' + watch.ElapsedMilliseconds);
                Console.WriteLine('線(xiàn)程輸出:' + result);
            }
            Console.ReadKey();
        }
    }}

運(yùn)行結(jié)果如下:

在這里插入圖片描述

運(yùn)行結(jié)果和上面示例一樣,,如果線(xiàn)程處理過(guò)程較復(fù)雜,,可以看到耗時(shí)明顯減少,這是一種用比阻塞更效率的方式完成線(xiàn)程同步,。

4. 線(xiàn)程通知

前面說(shuō)到了能否在一個(gè)線(xiàn)程完成后,,通知等待的線(xiàn)程呢,這里.NET為我們提供了一個(gè)事件通知的方法來(lái)解決這個(gè)問(wèn)題,。

4.1 AutoResetEvent

改進(jìn)上面的線(xiàn)程如下:

using System;using System.Threading;//一個(gè)線(xiàn)程完成后通知另外一個(gè)線(xiàn)程(是一個(gè)!與下面的幾個(gè)不同)namespace MultithreadingApplication{
    class ThreadCreationProgram
    {
        //先定義一個(gè)通知對(duì)象                                     //EventWaitHandle 表示一個(gè)線(xiàn)程同步事件,。         
        static EventWaitHandle tellMe = new AutoResetEvent(false);//里面的boolean該值指示是否將初始狀態(tài)設(shè)置為終止?fàn)顟B(tài)的類(lèi)。
        static int result = 0;                              
        static void Main(string[] args)
        {
            System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
            Thread myThread = new Thread(() =>
            {
                Thread.Sleep(5000);
                result = 100;
                tellMe.Set();//將事件狀態(tài)設(shè)置為有信號(hào),,從而允許一個(gè)或多個(gè)等待線(xiàn)程繼續(xù)執(zhí)行,。
            });
            myThread.Start();
            tellMe.WaitOne();//阻止當(dāng)前線(xiàn)程,直到當(dāng)前 System.Threading.WaitHandle 收到信號(hào),。
            Console.WriteLine('線(xiàn)程耗時(shí):' + watch.ElapsedMilliseconds);
            Console.WriteLine('線(xiàn)程輸出:' + result);
        }
    }//待在同一個(gè)代碼塊的兩個(gè)線(xiàn)程是資源共享的,即兩個(gè)線(xiàn)程是同步的}

運(yùn)行結(jié)果如下:

在這里插入圖片描述

4.2 ManualResetEvent

和AutoResetEvent 相對(duì)的還有一個(gè) ManualResetEvent 手動(dòng)模式,,他們的區(qū)別在于,在線(xiàn)程結(jié)束后ManualResetEvent 還是可以通行的,,除非手動(dòng)Reset關(guān)閉,。下面看一個(gè)示例:

這句話(huà)的意思是在mre.Set()和mre.WaitOne()執(zhí)行完之后,如果有另一個(gè)mre.WaitOne(),此時(shí)仍可以通過(guò).如果是AutoResetEvent的話(huà)就不可以了,可以將下面的代碼ManualResetEvent改成AutoResetEvent試一下

using System;using System.Threading;namespace MultithreadingApplication{
    //一個(gè)線(xiàn)程完成後通知其他個(gè)線(xiàn)程.(其他的意思是多于一個(gè))
    class ThreadCreationProgram
    {
        //EventWaitHandle 表示一個(gè)線(xiàn)程同步事件,。
        static EventWaitHandle mre = new ManualResetEvent(false);//布爾值指示是否將初始狀態(tài)設(shè)置為終止?fàn)顟B(tài)的類(lèi)。
        static int result = 0;
        static void Main(string[] args)
        {
            System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
            Thread myThreadFirst = new Thread(() =>
            {
                Thread.Sleep(10000);
                result = 100;
                mre.Set();//將事件狀態(tài)設(shè)置為有信號(hào),,從而允許一個(gè)或多個(gè)等待線(xiàn)程繼續(xù)執(zhí)行,。
            })
            { Name = '線(xiàn)程一' };
            Thread myThreadSecond = new Thread(() =>
            {
            	兩個(gè)WaitOne()執(zhí)行后進(jìn)入線(xiàn)程一執(zhí)行Set(),Set執(zhí)行后代表兩個(gè)WaitOne都已經(jīng)通過(guò)
                mre.WaitOne();
                Console.WriteLine(Thread.CurrentThread.Name + '獲取結(jié)果:' + result + '(' + System.DateTime.Now.ToString() + ')');
            })
            { Name = '線(xiàn)程二' };
            myThreadFirst.Start();
            myThreadSecond.Start();
            mre.WaitOne();//阻止當(dāng)前線(xiàn)程,直到當(dāng)前 System.Threading.WaitHandle 收到信號(hào),。
            Console.WriteLine('線(xiàn)程耗時(shí):' + watch.ElapsedMilliseconds + '(' + System.DateTime.Now.ToString() + ')');
            Console.WriteLine('線(xiàn)程輸出:' + result + '(' + System.DateTime.Now.ToString() + ')');
            Console.ReadKey();
        }
    }//手動(dòng)Reset關(guān)閉,mre.Reset();}

運(yùn)行結(jié)果如下:

在這里插入圖片描述

線(xiàn)程二獲取結(jié)果:100可能先輸出,或者在中間輸出,也可能最后輸出,這取決于主線(xiàn)程與線(xiàn)程二對(duì)cpu資源的搶奪

下面代碼是手動(dòng) Reset()關(guān)閉展示

using System;using System.Threading;namespace MultithreadingApplication{
    //一個(gè)線(xiàn)程完成後通知其他個(gè)線(xiàn)程.(其他的意思是多于一個(gè))
    class ThreadCreationProgram
    {
        //EventWaitHandle 表示一個(gè)線(xiàn)程同步事件,。
        static EventWaitHandle mre = new ManualResetEvent(false);//布爾值指示是否將初始狀態(tài)設(shè)置為終止?fàn)顟B(tài)的類(lèi)。
        static int result = 0;
        static void Main(string[] args)
        {
            System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
            Thread myThreadFirst = new Thread(() =>
            {
                Thread.Sleep(1000);
                result = 100;
                mre.Set();//將事件狀態(tài)設(shè)置為有信號(hào),,從而允許一個(gè)或多個(gè)等待線(xiàn)程繼續(xù)執(zhí)行,。
               
            })
            { Name = '線(xiàn)程一' };
            Thread myThreadSecond = new Thread(() =>
            {
                
                mre.WaitOne();//兩個(gè)WaitOne()同時(shí)執(zhí)行后進(jìn)入線(xiàn)程一執(zhí)行Set(),Set執(zhí)行后代表兩個(gè)WaitOne都已經(jīng)通過(guò)
                
                Console.WriteLine(Thread.CurrentThread.Name + '獲取結(jié)果:' + result + '(' + System.DateTime.Now.ToString() + ')');
            })
            { Name = '線(xiàn)程二' };

            Thread myThreadThird = new Thread(() =>
            {
                mre.Reset();
                mre.WaitOne();                
                Console.WriteLine(Thread.CurrentThread.Name + '獲取結(jié)果:' + result + '(' + System.DateTime.Now.ToString() + ')');
            })
            { Name = '線(xiàn)程三' };

            myThreadFirst.Start();
            myThreadSecond.Start();
            mre.WaitOne();//阻止當(dāng)前線(xiàn)程,直到當(dāng)前 System.Threading.WaitHandle 收到信號(hào),。
            Console.WriteLine('線(xiàn)程耗時(shí):' + watch.ElapsedMilliseconds + '(' + System.DateTime.Now.ToString() + ')');
            Console.WriteLine('線(xiàn)程輸出:' + result + '(' + System.DateTime.Now.ToString() + ')');

            myThreadThird.Start();
            Thread.Sleep(1000);
            mre.Set();//將這一句注釋掉線(xiàn)程三WaitOne()就等不到信號(hào),從而會(huì)被一直阻塞.
            Console.ReadKey();
        }
    }}

4.3. Semaphore

Semaphore也是線(xiàn)程通知的一種,,上面的通知模式,在線(xiàn)程開(kāi)啟的數(shù)量很多的情況下,,使用Reset()關(guān)閉時(shí),,如果不使用Sleep休眠一下,很有可能導(dǎo)致某些線(xiàn)程沒(méi)有恢復(fù)的情況下,,某一線(xiàn)程提前關(guān)閉,,對(duì)于這種很難預(yù)測(cè)的情況,.NET提供了更高級(jí)的通知方式Semaphore,可以保證在超多線(xiàn)程時(shí)不會(huì)出現(xiàn)上述問(wèn)題,。

using System;using System.Threading;//semaphor 發(fā)信號(hào),打旗語(yǔ)namespace MultithreadingApplication{
    class Program
    {
        //先定義一個(gè)通知對(duì)象的靜態(tài)字段
        //Semaphore(初始授予1個(gè)請(qǐng)求數(shù),設(shè)置最大可授予5個(gè)請(qǐng)求數(shù))
        static Semaphore semaphore = new Semaphore(1, 5);//初始授予1個(gè)請(qǐng)求數(shù),如果沒(méi)有semaphore.Release()語(yǔ)句,則只會(huì)執(zhí)行一個(gè)子線(xiàn)程,執(zhí)行完之后請(qǐng)求數(shù)又會(huì)變成0
        static void Main(string[] args)
        {
            for (int i = 1; i <= 5; i++)
            {
                Thread thread = new Thread(Work);
                thread.Start(i);
            }
            Thread.Sleep(2000);
            //授予3個(gè)請(qǐng)求
            semaphore.Release(3);
            Console.ReadLine();
        }
        static void Work(object obj)
        {
            semaphore.WaitOne();
            Console.WriteLine('print: {0}', obj);
        }
    }//程序執(zhí)行完畢會(huì)輸出四個(gè)記錄}

5. 本節(jié)要點(diǎn):

A.線(xiàn)程中靜態(tài)字段的ThreadStatic特性,,使用該字段在不同線(xiàn)程中擁有不同的值

B.線(xiàn)程同步的幾種方式,,線(xiàn)程鎖和線(xiàn)程通知

C.線(xiàn)程通知的兩種方式:AutoResetEvent /ManualResetEvent 和 Semaphore

多線(xiàn)程的更多特性,下一節(jié)繼續(xù)深入介紹,。

三,、利用多線(xiàn)程提高程序性能(下)

本節(jié)導(dǎo)讀:

上節(jié)說(shuō)了線(xiàn)程同步中使用線(xiàn)程鎖和線(xiàn)程通知的方式來(lái)處理資源共享問(wèn)題,這些是多線(xiàn)程的基本原理,。

.NET 4.0 以后對(duì)多線(xiàn)程的實(shí)現(xiàn)變得更簡(jiǎn)單了,。

本節(jié)主要討論 .NET4.0 多線(xiàn)程的新特性——使用 Task類(lèi)創(chuàng)建多線(xiàn)程。

讀前必備:

A. LINQ使用 [.net 面向?qū)ο缶幊袒A(chǔ)] (20) LINQ使用

B. 泛型 [.net 面向?qū)ο缶幊袒A(chǔ)] (18) 泛型

1.線(xiàn)程池ThreadPool

在介紹4.0以后的多線(xiàn)程新特征之前,,先簡(jiǎn)單說(shuō)一下線(xiàn)程池,。

通過(guò)前面對(duì)多線(xiàn)程的學(xué)習(xí),我們發(fā)現(xiàn)多線(xiàn)程的創(chuàng)建和使用并不難,,難的在于多線(xiàn)程的管理,,特別是線(xiàn)程數(shù)量級(jí)很多的情況下,如何進(jìn)行管理和資源釋放,。需要使用線(xiàn)程池來(lái)解決,。

簡(jiǎn)單來(lái)說(shuō)線(xiàn)程池就是.NET提供的存放線(xiàn)程的一個(gè)對(duì)象容器,。

為什么要使用線(xiàn)性池

微軟官網(wǎng)說(shuō)法如下:許多應(yīng)用程序創(chuàng)建大量處于睡眠狀態(tài),等待事件發(fā)生的線(xiàn)程,。還有許多線(xiàn)程可能會(huì)進(jìn)入休眠狀態(tài),,這些線(xiàn)程只是為了定期喚醒以輪詢(xún)更改或更新的狀態(tài)信息。 線(xiàn)程池,,使您可以通過(guò)由系統(tǒng)管理的工作線(xiàn)程池來(lái)更有效地使用線(xiàn)程,。

說(shuō)得簡(jiǎn)單一點(diǎn),每新建一個(gè)線(xiàn)程都需要占用內(nèi)存空間和其他資源,,而新建了那么多線(xiàn)程,,有很多在休眠,或者在等待資源釋放,;又有許多線(xiàn)程只是周期性的做一些小工作,,如刷新數(shù)據(jù)等等,太浪費(fèi)了,,劃不來(lái),,實(shí)際編程中大量線(xiàn)程突發(fā),然后在短時(shí)間內(nèi)結(jié)束的情況很少見(jiàn),。于是,,就提出了線(xiàn)程池的概念。線(xiàn)程池中的線(xiàn)程執(zhí)行完指定的方法后并不會(huì)自動(dòng)消除,,而是以?huà)炱馉顟B(tài)返回線(xiàn)程池,,如果應(yīng)用程序再次向線(xiàn)程池發(fā)出請(qǐng)求,那么處以?huà)炱馉顟B(tài)的線(xiàn)程就會(huì)被激活并執(zhí)行任務(wù),,而不會(huì)創(chuàng)建新線(xiàn)程,,這就節(jié)約了很多開(kāi)銷(xiāo)。只有當(dāng)線(xiàn)程數(shù)達(dá)到最大線(xiàn)程數(shù)量,,系統(tǒng)才會(huì)自動(dòng)銷(xiāo)毀線(xiàn)程,。因此,使用線(xiàn)程池可以避免大量的創(chuàng)建和銷(xiāo)毀的開(kāi)支,,具有更好的性能和穩(wěn)定性,,其次,開(kāi)發(fā)人員把線(xiàn)程交給系統(tǒng)管理,,可以集中精力處理其他任務(wù),。

線(xiàn)程池線(xiàn)程分為兩類(lèi):工作線(xiàn)程和 IO 線(xiàn)程 . 線(xiàn)程池是一種多線(xiàn)程處理形式,處理過(guò)程中將任務(wù)添加到隊(duì)列,,然后在創(chuàng)建線(xiàn)程后自動(dòng)啟動(dòng)這些任務(wù),。

下面是一個(gè)線(xiàn)程池的示例:

using System;using System.Threading;namespace MultithreadingApplication{
    class Program
    {
        //先設(shè)置一個(gè)創(chuàng)建線(xiàn)程總數(shù)靜態(tài)字段:
        static readonly int totalThreads = 20; 
        static void Main(string[] args)
        {
            //線(xiàn)性池是靜態(tài)類(lèi)可以直接使用
            //參數(shù)1:要由線(xiàn)程池根據(jù)需要?jiǎng)?chuàng)建的新的最小工作程序線(xiàn)程數(shù)。
            //參數(shù)2:要由線(xiàn)程池根據(jù)需要?jiǎng)?chuàng)建的新的最小空閑異步 I/O 線(xiàn)程數(shù)。
            ThreadPool.SetMinThreads(2, 2);
            //參數(shù)1:線(xiàn)程池中輔助線(xiàn)程的最大數(shù)目,。
            //參數(shù)2:線(xiàn)程池中異步 I/O 線(xiàn)程的最大數(shù)目,。
            ThreadPool.SetMaxThreads(20, 20);
            for (int i = 0; i < totalThreads; i++)
            {
                ThreadPool.QueueUserWorkItem(o =>
                {
                    Thread.Sleep(1000);
                    int a, b;
                    //參數(shù)1:可用輔助線(xiàn)程的數(shù)目。
                    //參數(shù)2:可用異步 I/O 線(xiàn)程的數(shù)目,。
                    ThreadPool.GetAvailableThreads(out a, out b);
                    Console.WriteLine(string.Format('({0}/{1}) #{2} : {3}', a, b, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
                });
            }
            Console.WriteLine('主線(xiàn)程完成');
            Console.ReadKey();
        }
    }}

2. Task類(lèi)

用 ThreadPool 的 QueueUserWorkItem() 方法發(fā)起一次異步的線(xiàn)程執(zhí)行很簡(jiǎn)單,,但是該方法最大的問(wèn)題是沒(méi)有一個(gè)內(nèi)建的機(jī)制讓你知道操作什么時(shí)候完成,有沒(méi)有一個(gè)內(nèi)建的機(jī)制在操作完成后獲得一個(gè)返回值,。為此,, 在.NET 4.0 以后,我們 可以使用 System.Threading.Tasks 中的 Task 類(lèi),。 這也是.NET 4.0 以后多線(xiàn)程的推薦做法,。

構(gòu)造一個(gè) Task<T> 對(duì)象,并為泛型 T 參數(shù)傳遞一個(gè)操作的返回類(lèi)型,。

Task類(lèi)可以使用多種方法創(chuàng)建多線(xiàn)程,,下面詳細(xì)介紹。

2.1 使用Factory屬性

Task 實(shí)例可以用各種不同的方式創(chuàng)建,。 最常見(jiàn)的方法是使用任務(wù)的 Factory 屬性檢索可用來(lái)創(chuàng)建用于多個(gè)用途的 TaskFactory 實(shí)例,。

例如,要?jiǎng)?chuàng)建運(yùn)行操作的 Task ,,可以使用工廠(chǎng)的 StartNew 方法:

//最簡(jiǎn)單的線(xiàn)程示例Task.Factory.StartNew(() =>{
    Console.WriteLine('我是使用Factory屬性創(chuàng)建的線(xiàn)程');});

如果想簡(jiǎn)單的創(chuàng)建一個(gè)Task,,那么使用Factory.StartNew()來(lái)創(chuàng)建,很簡(jiǎn)便,。

如果像對(duì)所創(chuàng)建的Task附加更多的定制和設(shè)置特定的屬性,,請(qǐng)繼續(xù)往下看。

2.2 使用Task實(shí)例實(shí)現(xiàn)多線(xiàn)程

//簡(jiǎn)單的Task實(shí)例創(chuàng)建線(xiàn)程Action<object> action = (object obj) =>{
    Console.WriteLine('Task={0}, obj={1}, Thread={2}', Task.CurrentId, obj.ToString(), Thread.CurrentThread.ManagedThreadId);};//上面的是簡(jiǎn)寫(xiě)形式,也可以寫(xiě)成下面的形式.//Action<object> action = new Action<object>((object obj) =>//{//    Console.WriteLine('Task={0}, obj={1}, Thread={2}', Task.CurrentId, obj.ToString(), Thread.CurrentThread.ManagedThreadId);//});Task t1 = new Task(action, '參數(shù)');t1.Start();

運(yùn)行結(jié)果如下:

在這里插入圖片描述

//簡(jiǎn)寫(xiě)上面實(shí)例,,并創(chuàng)建100個(gè)線(xiàn)程System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();int m = 100;Task[] tasks = new Task[m];for (int i = 0; i < m; i++){
    tasks[i] = new Task((object obj) =>
        {
            Thread.Sleep(200);
            Console.WriteLine('Task={0}, obj={1}, Thread={2},當(dāng)前時(shí)間:{3}',
            Task.CurrentId, obj.ToString(),
            Thread.CurrentThread.ManagedThreadId,
            System.DateTime.Now.ToString());
        }, '參數(shù)' + i.ToString()   //public Task(Action<object> action, object state);
    );
    tasks[i].Start();//線(xiàn)程開(kāi)始}
           Task.WaitAll(tasks);  //等待提供的所有 System.Threading.Tasks.Task 對(duì)象完成執(zhí)行過(guò)程,。Console.WriteLine('線(xiàn)程耗時(shí):{0},當(dāng)前時(shí)間:{1}' ,watch.ElapsedMilliseconds,System.DateTime.Now.ToString());

這里task創(chuàng)建的100個(gè)線(xiàn)程貌似是異步執(zhí)行的

運(yùn)行結(jié)果如下:

在這里插入圖片描述

2.3 Task傳入?yún)?shù)

上面介紹了使用一個(gè)Action委托來(lái)完成線(xiàn)程,,那么給線(xiàn)程中傳入?yún)?shù),就可以使用System.Action<object>來(lái)完成,。

傳入一個(gè)參數(shù)的示例:

/// <summary>/// 一個(gè)參數(shù)的方法/// </summary>/// <param name='parameter'></param>static void MyMethod(string parameter){
    Console.WriteLine('{0}', parameter);}

調(diào)用如下:

//Task傳入一個(gè)參數(shù)Task myTask = new Task((parameter) => MyMethod(parameter.ToString()), 'aaa');myTask.Start();

傳入多個(gè)參數(shù)如下:

/// <summary>/// 多個(gè)參數(shù)的方法/// </summary>/// <param name='parameter1'></param>/// <param name='parameter2'></param>/// <param name='parameter3'></param>static void MyMethod(string parameter1,int parameter2,DateTime parameter3){
    Console.WriteLine('{0} {1} {2}', parameter1,parameter2.ToString(),parameter3.ToString());}

調(diào)用如下:

//Task傳入多個(gè)參數(shù)for (int i = 1; i <= 20; i++){              
    new Task(() => { MyMethod('我的線(xiàn)程', i, DateTime.Now); }).Start();
    Thread.Sleep(200);}

運(yùn)行結(jié)果如下:

在這里插入圖片描述

對(duì)于傳入多個(gè)參數(shù),,可以使用無(wú)參數(shù)委托包裝一個(gè)多參數(shù)的方法來(lái)完成。

2.4 Task的結(jié)果

要獲取Task的結(jié)果,,在創(chuàng)建Task的時(shí)候,,就要采用Task<T>來(lái)實(shí)例化一個(gè)Task。

其中的T就是Task執(zhí)行完成之后返回結(jié)果的類(lèi)型,。

通過(guò)Task實(shí)例的Result屬性就可以獲取結(jié)果,。

System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();Task<int> myTask = new Task<int>(() =>//這里面泛型委托修飾符out表示協(xié)變{
    int sum = 0;
    for (int i = 0; i < 10000; i++)
        sum += i;
    return sum;});myTask.Start();           Console.WriteLine('結(jié)果: {0} 耗時(shí):{1}', myTask.Result,watch.ElapsedMilliseconds);

這里task創(chuàng)建線(xiàn)程執(zhí)行完才會(huì)繼續(xù)執(zhí)行主線(xiàn)程

運(yùn)行結(jié)果如下:

在這里插入圖片描述

使用Factory屬性來(lái)完成上面的示例:

//使用Factory屬性創(chuàng)建System.Diagnostics.Stopwatch watchSecond = System.Diagnostics.Stopwatch.StartNew();Task<int> myTaskSecond = Task.Factory.StartNew<int>(() =>{
    int sum = 0;
    for (int i = 0; i < 10000; i++)
        sum += i;
    return sum;});            Console.WriteLine('結(jié)果: {0} 耗時(shí):{1}', myTaskSecond.Result, watchSecond.ElapsedMilliseconds);

這里task創(chuàng)建線(xiàn)程執(zhí)行完才會(huì)繼續(xù)執(zhí)行主線(xiàn)程

運(yùn)行結(jié)果如下:

在這里插入圖片描述

多線(xiàn)程除以上的一些基礎(chǔ)知識(shí),在處理各種并行任務(wù)和多核編程中的使用,小伙伴可以參考專(zhuān)門(mén)關(guān)于多線(xiàn)程的書(shū)籍學(xué)習(xí),。

想要完全深入的學(xué)習(xí)多線(xiàn)程需要慢慢修煉,,不斷積累。

3. 本節(jié)要點(diǎn):

A.本點(diǎn)簡(jiǎn)單介紹了線(xiàn)程池ThreadPool的使用,;

B.介紹一使用Task進(jìn)行多線(xiàn)程創(chuàng)建及Tast的參數(shù)傳入和返回結(jié)果,。

線(xiàn)程一些小知識(shí)點(diǎn)

并發(fā)與并行
例子:當(dāng)你要吃飯又要玩游戲
順序執(zhí)行:先吃完飯?jiān)偻嬗螒?br> 并發(fā):吃口飯玩一會(huì)游戲
并行:邊吃飯邊玩游戲

異步
與同步相對(duì)應(yīng),當(dāng)一個(gè)異步過(guò)程調(diào)用發(fā)出后,,調(diào)用者在沒(méi)有得到結(jié)果之前,,就可以繼續(xù)執(zhí)行后續(xù)操作。當(dāng)這個(gè)調(diào)用完成后,,一般通過(guò)狀態(tài),、通知和回調(diào)來(lái)通知調(diào)用者。對(duì)于異步調(diào)用,,調(diào)用的返回并不受調(diào)用者控制,。

同步
同步多線(xiàn)程資源共享;Java中所有方法都是同步調(diào)用,應(yīng)為必須要等到結(jié)果后才會(huì)繼續(xù)執(zhí)行,。
簡(jiǎn)單來(lái)說(shuō),,同步就是必須一件一件事做,等前一件做完了才能做下一件事,。

Thread ThreadPool和Task
Thread就是Thread,,需要自己調(diào)度,適合長(zhǎng)跑型的操作,。

ThreadPool是Thread基礎(chǔ)上的一個(gè)線(xiàn)程池,,目的是減少頻繁創(chuàng)建線(xiàn)程的開(kāi)銷(xiāo)。線(xiàn)程很貴,,要開(kāi)新的stack,,要增加CPU上下文切換,所以ThreadPool適合頻繁,、短期執(zhí)行的小操作,。調(diào)度算法是自適應(yīng)的,會(huì)根據(jù)程序執(zhí)行的模式調(diào)整配置,,通常不需要自己調(diào)度線(xiàn)程,。另外分為Worker和IO兩個(gè)池。

Task或者說(shuō)TPL是一個(gè)更上層的封裝,,NB之處在于continuation,。continuation的意義在于:高性能的程序通常都是跑在IO邊界或者UI事件的邊界上的,TPL的continuation可以更方便的寫(xiě)這種高scalability的代碼,。Task會(huì)根據(jù)一些flag,,比如是不是long-running來(lái)決定底層用Thread還是ThreadPool 

結(jié)論:能用Task就用Task,底下都是用的Thread或者ThreadPool。但是要注意細(xì)節(jié),,比如告訴Task是不是long-running,;比如盡量別Wait;再比如IO之后的continuation要盡快結(jié)束然后把線(xiàn)程還回去,,有事開(kāi)個(gè)Worker做,,要不然會(huì)影響后面的IO,等等,。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,,所有內(nèi)容均由用戶(hù)發(fā)布,不代表本站觀(guān)點(diǎn),。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶(hù) 評(píng)論公約

    類(lèi)似文章 更多