国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
C# 溫故而知新: 線程篇

Thread

目錄:

 

 

 

1 線程基礎的簡單介紹

 

 

首先讓我們翻開書本來了解下線程的一些基礎知識:

1 線程有時被稱為輕量級進程,程序執(zhí)行流的最小單元

2 線程時由線程ID,當前指令指針(PC),寄存器集合堆棧組成。

線程自身不能擁有系統(tǒng)資源,但是可以使用線程所屬進程所占有的系統(tǒng)資源

4 線程可以創(chuàng)建和撤銷另一個線程

5 線程可以擁有自身的狀態(tài),例如 運行狀態(tài),掛起狀態(tài),銷毀釋放狀態(tài)等等

6 線程具有優(yōu)先級,每個線程都分配了0-31 級別的其中一個優(yōu)先級,數(shù)字越大,優(yōu)先級越高,然而手動分配優(yōu)先級過于復雜,

所以微軟為我們的Thread類提供一個優(yōu)先級的枚舉,ThreadPriority枚舉便是優(yōu)先級枚舉,我們可以利用thread.Priority屬性來進行設置

7 線程開銷,這個是個復雜的話題,希望有機會的話能夠單獨寫一遍文章解釋下

 

 

 

 

 

 

 

 

   

 

那么多線程有什么實際好處呢?

首先讓我們了解下多線程的概念:個程序或者進程中同時運行多個線程完成不同的工作

從概念中我們便可知道多線程的優(yōu)點了

1 能夠?qū)崿F(xiàn)并行操作,也就是說多個線程可以同時進行工作

2 利用多線程后許多復雜的業(yè)務或者是計算可以交給后臺線程去完成,從而提高整體程序的性能

3 類似于第一條利用多線程可以達到異步的作用(注意,實現(xiàn)異步的一種方式是多線程

 

 

 

 

 

當然多線程也有一定的問題需要注意,那就是線程同步問題,關于這個問題我會今后的文章中詳細說明

 

2 線程同步與線程異步的簡單介紹

*1 線程同步

關于線程同步的概念最簡單的理解就是

同步方法調(diào)用在程序繼續(xù)執(zhí)行之前,需要等待同步方法執(zhí)行完畢返回結(jié)果

很有可能多個線程都會對一個資源進行訪問,從而導致資源被破壞,所以必須采用線程的同步機制,例如為共享資源加鎖

,當其中一個線程占有了鎖之后,其余線程均不能使用共享資源,只有等其釋放鎖之后,接下來的其中一個線程會占有該

鎖,本系列會從Thread類開始講起,以后多章都會討論線程同步機制,例如鎖機制,臨界區(qū),互斥,信號量 同步事件等待句柄; 等等

 

 

 

 

 

*2 線程異步

線程異步指的是一個調(diào)用請求發(fā)送給被調(diào)用者,而調(diào)用者不用等待其結(jié)果的返回,一般異步執(zhí)行的任務都需要比較長的時間,

所以為了不影響主線程的工作,可以使用多線程或者新開辟一個線程來實現(xiàn)異步,同樣,異步和線程池也有著非常緊密的聯(lián)系,

這點我會在今后有關線程池的文章中詳細敘述,線程池和異步線程將在第二章中詳細闡述下

 

 

3 前臺線程與后臺線程的簡單介紹

前臺線程:

諸如我們Console程序的主線程,wpf或者sliverlight的 界面線程等等,都屬于前臺線程,一旦前臺線程奔潰或者終止,相應的后臺

線程都會終止,本章中通過Thread類產(chǎn)生的線程默認都是前臺線程,當然我們可以設置Thread的屬性讓該對象成為后臺線程,必須

注意的是,一旦前臺線程全部運行完畢,應用程序的進程也會釋放,但是假設Console程序中main函數(shù)運行完畢,但是其中幾個前臺

線程還處在運行之中,那么這個Console程序的進程是不會釋放的,仍然處于運行之中,直到所有的前臺線程都釋放為止      

 

后臺線程:

和前臺線程唯一的區(qū)別是,后臺線程更加默默無聞,甚至后臺線程因某種情況,釋放銷毀時不會影響到進程,也就是說后臺線程釋放時

不會導致進程的釋放

用一個例子再來說明下前后臺線程的區(qū)別:

有時我們打開outlook 后接受郵件時,程序會失去響應或被卡住,這時候我們?nèi)c擊outlook時系統(tǒng)會提示 outlook 失去響應,是否等待或者關閉,

當我們點擊關閉時,其實在程序中關于outlook的所有運行的前臺線程被終止,導致了outlook被關閉了,其進程也隨之釋放消失。但是,當我們在

outlook中點擊更新郵件時,后臺線程會去收取郵件的工作,我們可以在此期間關閉 outlook接受新郵件的后臺線程,而不會導致整個outlook的關閉

 

 

 

 

 

 

4 細說下Thread 最為關鍵的構造函數(shù)

相信大家再看過前幾章對于線程的介紹后,對線程應該有一個溫故的感覺,那么讓我們開始對thread這個線程類進行深層次的研究下,

首先要啟動一個線程必須將該線程將要做的任務告訴該線程,否則,線程會不知道干什么事導致線程無意義的開啟,浪費系統(tǒng)資源,果然,

Thread類的構造函數(shù)提供了以下的版本

ThreadStart 和 ParameterThreadStart 參數(shù)都是委托,所以可以看出委托其實就是方法的抽象,前者用于不帶參數(shù)的并且無返回值的

方法的抽象后者是帶object參數(shù)的方法的抽象,大家通過以下簡單的方法注意下線程如何調(diào)用帶參數(shù)的方法

 public class ThreadStartTest     {        //無參數(shù)的構造函數(shù)        Thread thread = new Thread(new ThreadStart(ThreadMethod));        //帶有object參數(shù)的構造函數(shù)        Thread thread2 = new Thread(new ParameterizedThreadStart(ThreadMethodWithPara));        public ThreadStartTest()         {            //啟動線程1            thread.Start();            //啟動線程2            thread2.Start(new Parameter { paraName="Test" });        }        static void ThreadMethod()         {           //....        }        static void ThreadMethodWithPara(object o)         {            if (o is Parameter)             {               // (o as Parameter).paraName.............            }        }    }    public class Parameter     {        public string paraName { get; set; }    }

不帶參數(shù)的方法似乎很簡單的能被調(diào)用,只要通過第一個構造函數(shù)便行,對于帶參數(shù)的方法,大家注意下參數(shù)是如何傳入線程所調(diào)用的方法,

當啟動線程時,參數(shù)通過thread.Start方法傳入,于是我們便成功啟動了thread線程,大伙可千萬不要小看基礎啊,往往在復雜的項目中很多

就是因為一些基礎導致,所以一定不要忽視它。。。

 

5 細說下Thread 的 Sleep方法

  話說微軟對Thread.Sleep方法的解釋過于簡單,導致許多人會誤認為這個方法并不重要,其實這是錯誤的,其實線程是非常復雜的,

  而且我們圍繞這個方法來溫故下windows系統(tǒng)對于CPU競爭的策略:

  所謂搶占式操作系統(tǒng),就是說如果一個進程得到了 CPU 時間,除非它自己放棄使用 CPU ,否則將完全霸占 CPU 。因此可以看出,

  在搶占式操作系統(tǒng)中,操作系統(tǒng)假設所有的進程都是“人品很好”的,會主動退出 CPU 。

  發(fā)現(xiàn)寫到這里貌似真的已經(jīng)比較復雜了,由于本人對操作系統(tǒng)底層的知識比較匱乏,決定還是引用下別人的理解,順便自己也學習下

引用:

假設有源源不斷的蛋糕(源源不斷的時間),一副刀叉(一個CPU),10個等待吃蛋糕的人(10 個進程)。如果是 Unix 操作系統(tǒng)來負責分蛋糕,

那么他會這樣定規(guī)矩:每個人上來吃 1 分鐘,時間到了換下一個。最后一個人吃完了就再從頭開始。于是,不管這10個人是不是優(yōu)先級不同、饑餓

程度不同、飯量不同,每個人上來的時候都可以吃 1 分鐘。當然,如果有人本來不太餓,或者飯量小,吃了30秒鐘之后就吃飽了,那么他可以跟操

作系統(tǒng)說:我已經(jīng)吃飽了(掛起)。于是操作系統(tǒng)就會讓下一個人接 著來。如果是 Windows 操作系統(tǒng)來負責分蛋糕的,那么場面就很有意思了。

他會這樣定規(guī)矩:我會根據(jù)你們的優(yōu)先級、饑餓程度去給你們每個人計算一個優(yōu)先級。優(yōu)先級最高的那個人,可 以上來吃蛋糕——吃到你不想吃為止。

等這個人吃完了,我再重新根據(jù)優(yōu)先級、饑餓程度來計算每個人的優(yōu)先級,然后再分給優(yōu)先級最高的那個人。這樣看來,這個 場面就有意思了——

可能有些人是PPMM,因此具有高優(yōu)先級,于是她就可以經(jīng)常來吃蛋糕。可能另外一個人的優(yōu)先級特別低,于是好半天了才輪到他一次(因為 隨著時間

的推移,他會越來越饑餓,因此算出來的總優(yōu)先級就會越來越高,因此總有一天會輪到他的)。而且,如果一不小心讓一個大胖子得到了刀叉,因為他

飯量 大,可能他會霸占著蛋糕連續(xù)吃很久很久,導致旁邊的人在那里咽口水。。。而且,還可能會有這種情況出現(xiàn):操作系統(tǒng)現(xiàn)在計算出來的結(jié)果,是

5號PPMM總優(yōu) 先級最高——高出別人一大截。因此就叫5號來吃蛋糕。5號吃了一小會兒,覺得沒那么餓了,于是說“我不吃了”(掛起)。因此操作系

統(tǒng)就會重新計算所有人的 優(yōu)先級。因為5號剛剛吃過,因此她的饑餓程度變小了,于是總優(yōu)先級變小了;而其他人因為多等了一會兒,饑餓程度都變大了,

所以總優(yōu)先級也變大了。不過這時 候仍然有可能5號的優(yōu)先級比別的都高,只不過現(xiàn)在只比其他的高一點點——但她仍然是總優(yōu)先級最高的啊。因此操作

系統(tǒng)就會說:5號mm上來吃蛋糕……(5號 mm心里郁悶,這不剛吃過嘛……人家要減肥……誰叫你長那么漂亮,獲得了那么高的優(yōu)先級)。那么,

Thread.Sleep 函數(shù)是干嗎的呢?還用剛才的分蛋糕的場景來描述。上面的場景里面,5號MM在吃了一次蛋糕之后,覺得已經(jīng)有8分飽了,她覺得在未來

的半個小時之內(nèi)都不想再 來吃蛋糕了,那么她就會跟操作系統(tǒng)說:在未來的半個小時之內(nèi)不要再叫我上來吃蛋糕了。這樣,操作系統(tǒng)在隨后的半個小時

里面重新計算所有人總優(yōu)先級的時候, 就會忽略5號mm。Sleep函數(shù)就是干這事的,他告訴操作系統(tǒng)“在未來的多少毫秒內(nèi)我不參與CPU競爭”。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

6 細說下Thread 的 join 方法

為什么我要把Thread.Join()方法單獨細說下,個人認為join方法非常重要,

在細說前我想再次強調(diào)下主線程和子線程的區(qū)別:

 

首先大家肯定知道在Console程序中,主線程自上而下著運行著main函數(shù),假如我們在main函數(shù)中新增一個線程thread對象的話,

也就是說,在主線程中再開啟一個子線程,同時子線程和主線程可以同時工作(前提是子線程使用Start方法),同理,假如我在這

個子線程中再開辟一個屬于這個子線程的子線程,同理這3個爺爺,父親,兒子線程也可以使用Start()方法一起工作,假如在主線

程中添加2個thread對象并開啟,那么這2 線程便屬于同一層次的線程(兄弟線程)(和優(yōu)先級無關,只同一位置層次上的兄弟),

有可能上述的讓你覺得郁悶或者難以理解?沒關系看簡單例子就能夠理解了

 

     public static void ShowFatherAndSonThread(Thread grandFatherThread)        {            Console.WriteLine("爺爺主線程名:{0}", grandFatherThread.Name);            Thread brotherThread = new Thread(new ThreadStart(() => { Console.WriteLine("兄弟線程名:{0}", Thread.CurrentThread.Name); }));            Thread fatherThread = new Thread(new ThreadStart(                () =>                {                    Console.WriteLine("父親線程名:{0}", Thread.CurrentThread.Name);                    Thread sonThread = new Thread(new ThreadStart(() =>                    {                        Console.WriteLine("兒子線程名:{0}", Thread.CurrentThread.Name);                    }));                    sonThread.Name = "SonThread";                    sonThread.Start();                }                    ));            fatherThread.Name = "FatherThread";            brotherThread.Name="BrotherThread";            fatherThread.Start();            brotherThread.Start();        }

言歸正傳讓我們溫故下Jion方法,先看msdn中是怎么解釋的:

繼續(xù)執(zhí)行標準的 COM 和 SendMessage 消息泵處理期間,阻塞調(diào)用線程,直到某個線程終止為止。

大家把注意力移到后面紅色的部分,什么是“調(diào)用線程”呢?如果你理解上述線程關系的話,可能已經(jīng)理解了,主線程(爺爺輩)的調(diào)用了父親線程,

父親線程調(diào)用了兒子線程,假設現(xiàn)在我們有一個奇怪的需求,必須開啟爺爺輩和父親輩的線程但是,爺爺輩線程必須等待父親線程結(jié)束后再進行,

這該怎么辦? 這時候Join方法上場了,我們的目標是阻塞爺爺線程,那么后面的工作就明確了,讓父親線程(thread)對象去調(diào)用join方法就行

一下是個很簡單的例子,讓大家再深入理解下。

        public static void ThreadJoin()        {            Console.WriteLine("我是爺爺輩線程,子線程馬上要來工作了我得準備下讓個位給他。");            Thread t1 = new Thread(                new ThreadStart                    (                     () =>                     {                         for (int i = 0; i < 10; i++)                         {                             if (i == 0)                                 Console.WriteLine("我是父親線層{0}, 完成計數(shù)任務后我會把工作權交換給主線程", Thread.CurrentThread.Name);                             else                             {                                 Console.WriteLine("我是父親線層{0}, 計數(shù)值:{1}", Thread.CurrentThread.Name, i);                             }                             Thread.Sleep(1000);                         }                     }                    )                );            t1.Name = "線程1";            t1.Start();            //調(diào)用join后調(diào)用線程被阻塞
t1.Join(); Console.WriteLine(
"終于輪到爺爺輩主線程干活了"); }

代碼中當父親線程啟動后會立即進入Jion方法,這時候調(diào)用該線程爺爺輩線程被阻塞,直到父親線程中的方法執(zhí)行完畢為止,最后父親線程將控制

權再次還給爺爺輩線程,輸出最后的語句。聰明的你肯定會問:兄弟線程怎么保證先后順序呢?很明顯如果不使用join,一并開啟兄弟線程后結(jié)果

是隨機的不可預測的(暫時不考慮線程優(yōu)先級),但是我們不能在兄弟線程全都開啟后使用join,這樣阻塞了父親線程,而對兄弟線程是無效的,

其實我們可以變通一下,看以下一個很簡單的例子:

        public static void ThreadJoin2()        {            IList<Thread> threads = new List<Thread>();            for (int i = 0; i < 3; i++)            {                Thread t = new Thread(                    new ThreadStart(                        () =>                        {                            for (int j = 0; j < 10; j++)                            {                                if (j == 0)                                    Console.WriteLine("我是線層{0}, 完成計數(shù)任務后我會把工作權交換給其他線程", Thread.CurrentThread.Name);                                else                                {                                    Console.WriteLine("我是線層{0}, 計數(shù)值:{1}", Thread.CurrentThread.Name, j);                                }                                Thread.Sleep(1000);                            }                        }));                t.Name = "線程" + i;                //將線程加入集合                threads.Add(t);            }            foreach (var thread in threads)            {                thread.Start();                //每次按次序阻塞調(diào)用次方法的線程                thread.Join();            }        }

 輸出結(jié)果:

但是這樣我們即便能達到這種效果,也會發(fā)現(xiàn)其中存在著不少缺陷:

1:必須要指定順序

2:一旦一個運行了很久,后續(xù)的線程會一直等待很久

3: 很容易產(chǎn)生死鎖

從前面2個例子能夠看出 jion是利用阻塞調(diào)用線程的方式進行工作,我們可以根據(jù)需求的需要而靈活改變線程的運行順序,但是在復雜的項目或業(yè)務中

對于jion方法的調(diào)試和糾錯也是比較困難的。

 

7 細說下Thread 的 Abort和 Interrupt方法

Abort 方法:

其實 Abort 方法并沒有像字面上的那么簡單,釋放并終止調(diào)用線程,其實當一個線程調(diào)用 Abort方法時,會在調(diào)用此方法的線程上引發(fā)一個異常:

ThreadAbortException ,讓我們一步步深入下對這個方法的理解:

      1 首先我們嘗試對主線程終止釋放

  static void Main(string[] args)        {            try            {                Thread.CurrentThread.Abort();            }            catch            {                //Thread.ResetAbort();                Console.WriteLine("主線程接受到被釋放銷毀的信號");                Console.WriteLine( "主線程的狀態(tài):{0}",Thread.CurrentThread.ThreadState);            }            finally            {                Console.WriteLine("主線程最終被被釋放銷毀");                Console.WriteLine("主線程的狀態(tài):{0}", Thread.CurrentThread.ThreadState);                Console.ReadKey();            }}

從運行結(jié)果上看很容易看出當主線程被終止時其實報出了一個ThreadAbortException, 從中我們可以進行捕獲,但是注意的是,主線程直到finally語

句塊執(zhí)行完畢之后才真正結(jié)束(可以仔細看下主線程的狀態(tài)一直處于AbortRequest),如果你在finally語句塊中執(zhí)行很復雜的邏輯或者計算的話,那

么只有等待直到運行完畢才真正銷毀主線程(也就是說主線程的狀態(tài)會變成Aborted,但是由于是主線程所以無法看出).

 

2 嘗試終止一個子線程

同樣先看下代碼:

static void TestAbort()         {            try            {                Thread.Sleep(10000);            }            catch             {                Console.WriteLine("線程{0}接受到被釋放銷毀的信號",Thread.CurrentThread.Name);                Console.WriteLine("捕獲到異常時線程{0}主線程的狀態(tài):{1}", Thread.CurrentThread.Name,Thread.CurrentThread.ThreadState);            }            finally            {                Console.WriteLine("進入finally語句塊后線程{0}主線程的狀態(tài):{1}", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);            }        }Main:static void Main(string[] args)        {                     Thread thread1 = new Thread(TestAbort);            thread1.Name = "Thread1";            thread1.Start();            Thread.Sleep(1000);            thread1.Abort();            thread1.Join();            Console.WriteLine("finally語句塊后,線程{0}主線程的狀態(tài):{1}", thread1.Name, thread1.ThreadState);            Console.ReadKey();        }