作為一個(gè)ASP.NET開(kāi)發(fā)人員,在之前的開(kāi)發(fā)經(jīng)歷中接觸多線程編程的機(jī)會(huì)并不是很多,但是隨著.NET 4.0的發(fā)布臨近,我越來(lái)越感受到未來(lái)的1-2年中并行計(jì)算將會(huì)有很大的應(yīng)用。于是決定通過(guò)寫(xiě)日志的方式來(lái)總結(jié)一下.NET 3.5下的多線程編程進(jìn)而引入.NET 4.0提供的新的并行庫(kù)以及新的并行編程模式和編程的思維方式。
個(gè)人覺(jué)得在日常的編程中對(duì)于ASP.NET程序員來(lái)說(shuō)使用多線程編程不是很多,其實(shí)我們無(wú)時(shí)無(wú)刻不在享受多線程的優(yōu)勢(shì)。首先,WEB服務(wù)器環(huán)境就是一個(gè)多線程環(huán)境,每一個(gè)請(qǐng)求都是獨(dú)立的線程,如果沒(méi)有多線程很難想象只能同步處理一個(gè)請(qǐng)求的WEB服務(wù)器有什么用,類似,我們的數(shù)據(jù)庫(kù)也應(yīng)該是一個(gè)多線程環(huán)境。對(duì)于Windows應(yīng)用程序的程序員來(lái)說(shuō)恐怕就很難不接觸多線程了,最簡(jiǎn)單的就是我們會(huì)新開(kāi)線程去做一些耗時(shí)的操作,這樣就可以避免UI停止響應(yīng),在操作結(jié)束后再把操作結(jié)果應(yīng)用在主線程的控件上。雖然說(shuō)這樣的應(yīng)用是多線程,甚至很多程序員習(xí)慣什么操作都新開(kāi)一個(gè)線程去做,但是我覺(jué)得這樣的多線程應(yīng)用的思維還停留在單核時(shí)代,在多核時(shí)代,我們確實(shí)可以讓任務(wù)實(shí)際的并行執(zhí)行而不是看上去并行執(zhí)行。
首先來(lái)說(shuō)說(shuō)概念,進(jìn)程和線程的基本的概念不用多說(shuō),自然我們也能理解一個(gè)進(jìn)程至少包含一個(gè)線程。通過(guò)在一個(gè)進(jìn)程中開(kāi)啟多個(gè)線程,我們就可以讓一個(gè)程序在同一時(shí)間看上去能做多個(gè)事情,比如可以在接受用戶響應(yīng)的時(shí)候進(jìn)行一些計(jì)算。在以前處理器往往只有一個(gè)核心,也就是說(shuō)在同一時(shí)間,處理器只能做一件事情。那么怎么實(shí)現(xiàn)之前說(shuō)的多個(gè)線程同時(shí)執(zhí)行呢。其實(shí)這個(gè)同時(shí)只是表面上看上去同時(shí),本質(zhì)上多個(gè)線程依次占用處理器的若干時(shí)間片,大家輪流使用其資源,由于這個(gè)時(shí)間片非常短,所以在一個(gè)長(zhǎng)的時(shí)間看來(lái)似乎是幾個(gè)線程同時(shí)得到了執(zhí)行。
舉一個(gè)生動(dòng)的例子,我們經(jīng)??吹接幸恍┊?huà)家能同時(shí)在一個(gè)畫(huà)布上畫(huà)兩個(gè)不同的圖片,一個(gè)畫(huà)人一個(gè)畫(huà)房子,最后一起完成這個(gè)畫(huà)。但仔細(xì)看的話發(fā)現(xiàn),他是兩手拿了兩只畫(huà)筆,在這里畫(huà)一筆那里畫(huà)一筆,在同一時(shí)間其實(shí)也只有一只筆在畫(huà)。這個(gè)畫(huà)家應(yīng)該也像普通人一樣是單核的,只是線程切換比較快罷了。我經(jīng)常在打電話的時(shí)候和網(wǎng)友進(jìn)行聊天,在同一時(shí)間做兩件事情,但是這樣很費(fèi)腦子,在打字前我要回憶一下剛才聊天的內(nèi)容,然后輸入聊天的文字,然后再去回想一下剛才那哥們說(shuō)了啥,在電話里面回他一句,這種回憶的工作就是準(zhǔn)備線程的上下文,交給腦子去處理。雖然同一時(shí)間是做了兩件事情,但是這個(gè)上下文的準(zhǔn)備工作也浪費(fèi)了點(diǎn)時(shí)間,如果我在打電話網(wǎng)絡(luò)聊天的同事在去做第三件事情比如看電影,那我估計(jì)就不行了。所以,線程也不能開(kāi)的很多,特別對(duì)于人腦來(lái)說(shuō)。但是對(duì)于電腦處理器來(lái)說(shuō)就不一樣了,你只要準(zhǔn)備好數(shù)據(jù)和指令他執(zhí)行就是了,至于這些事情來(lái)自幾件事情它不關(guān)心,24小時(shí)一秒都不浪費(fèi)在執(zhí)行指令完全沒(méi)問(wèn)題,當(dāng)然你也可以讓它閑著。
您可能會(huì)想了,既然線程切換需要時(shí)間,那么我們開(kāi)兩個(gè)線程執(zhí)行兩個(gè)任務(wù)不是還沒(méi)有一個(gè)一個(gè)執(zhí)行來(lái)的快嗎?其實(shí)即使對(duì)于單核的處理器都不一定,因?yàn)樵趯?shí)際的應(yīng)用中我們的任務(wù)往往不可能從頭計(jì)算到尾一直占用處理器資源,在很多時(shí)候我們要等待IO響應(yīng)或用戶的響應(yīng),如果只是一個(gè)線程做事情的話處理器太閑了。對(duì)于現(xiàn)在多核的處理器來(lái)說(shuō),在同一時(shí)刻理論上可以在每一個(gè)處理器上都并行執(zhí)行指令,我們就更需要利用多線程來(lái)提高運(yùn)算速度了。當(dāng)然也不是說(shuō)一個(gè)任務(wù)要執(zhí)行10秒,我們?cè)陔p核的機(jī)器上并行執(zhí)行這個(gè)任務(wù)只需要5秒了,那是因?yàn)楹芏鄷r(shí)候這個(gè)任務(wù)很難劃分成兩個(gè)分支來(lái)并行執(zhí)行,如果每個(gè)指令都要依靠上個(gè)指令的執(zhí)行結(jié)果,那么這樣的操作很難在多個(gè)處理器上并行執(zhí)行。但是,我們可以這樣想,至少如果有兩個(gè)這樣任務(wù)的話,我們就可以完全利用多個(gè)處理器的優(yōu)勢(shì)來(lái)并行執(zhí)行了。
但是也不是多可以隨便的開(kāi)線程,每一個(gè)線程默認(rèn)情況下都會(huì)占用1M的棧空間(對(duì)于普通應(yīng)用程序來(lái)說(shuō)),在32位Windows平臺(tái)下可以給一個(gè)用戶進(jìn)程使用的程序最大在2G,那么也就是說(shuō)在程序中使用的線程不能超過(guò)2000個(gè),在實(shí)際測(cè)試中可以發(fā)現(xiàn)一般來(lái)說(shuō)開(kāi)1930左右個(gè)線程就會(huì)收到內(nèi)存不足的異常,其實(shí)這個(gè)數(shù)量是絕對(duì)夠用的,即使復(fù)雜的Outlook2007程序一般也只用了50個(gè)不到的線程(可以在任務(wù)管理器中觀察到)。
在不得已的情況下很多人都不太會(huì)去使用多線程也是有原因的,一是因?yàn)槎嗑€程編程復(fù)雜,我們也習(xí)慣了一行一行代碼執(zhí)行的編程模式,對(duì)于一個(gè)好的多線程程序來(lái)說(shuō),要盡量分割任務(wù)讓它在多個(gè)線程中使用以利用到多個(gè)處理器核。還有就是多個(gè)線程使用相同資源的話還要考慮資源的鎖定以免產(chǎn)生數(shù)據(jù)的不一致。鎖定/事務(wù)/并發(fā)的概念在數(shù)據(jù)庫(kù)中也是非常常見(jiàn)的。二是因?yàn)檎{(diào)試?yán)щy,特別是一個(gè)線程的執(zhí)行依賴其它線程的執(zhí)行。三是因?yàn)槎嗑€程的程序隨著環(huán)境的變化(處理器/操作系統(tǒng))可能執(zhí)行的性能還不一定相同,如果只針對(duì)某個(gè)環(huán)境進(jìn)行編程可能還不能充分利用多處理器的優(yōu)勢(shì)。比如我們對(duì)一個(gè)任務(wù)劃分成2個(gè)線程并行執(zhí)行,那么對(duì)于四核的處理器來(lái)說(shuō),劃分成4個(gè)線程并行執(zhí)行會(huì)不會(huì)更合理呢,說(shuō)實(shí)話我也舉的這事挺難說(shuō)的?還有,我們的編程基于.NET框架,而其本質(zhì)還是使用的是操作系統(tǒng)的線程,操作系統(tǒng)中本來(lái)就有很多進(jìn)程運(yùn)行著,處理器是大家的處理器,不是專供我們程序使用的,在這么一個(gè)魚(yú)龍混雜的環(huán)境,我們的程序究竟是不是會(huì)表現(xiàn)的如我們預(yù)期那樣,也很難說(shuō)。
多線程好,多線程難,本系列文章也只能在一個(gè)比較淺顯的層次來(lái)談?wù)勅绾卧?NET框架中進(jìn)行多線程編程,以及一些常見(jiàn)應(yīng)用(比如Windows應(yīng)用)中多線程的典型應(yīng)用。本系列文章預(yù)計(jì)會(huì)有30篇這樣的規(guī)模,希望對(duì)大家有幫助。
聯(lián)系客服