線程概述
有些程序是一條直線,從起點(diǎn)到終點(diǎn),如Hello World,運(yùn)行打印完,它的生命周期便結(jié)束了;有些程序是一個(gè)圓,不斷循環(huán),直到將它切斷,如操作系統(tǒng),一直運(yùn)行直到你關(guān)機(jī)。
一個(gè)運(yùn)行著的程序就是一個(gè)進(jìn)程或者叫做一個(gè)任務(wù),一個(gè)進(jìn)程至少包含一個(gè)線程,線程就是程序的執(zhí)行流。Mac和iOS中的程序啟動(dòng),創(chuàng)建好一個(gè)進(jìn)程的同時(shí), 一個(gè)線程便開始運(yùn)行,這個(gè)線程叫主線程。主線程在程序中的地位和其他線程不同,它是其他線程最終的父線程,且所有界面的顯示操作如AppKit或 UIKit的操作必須在主線程進(jìn)行。
系統(tǒng)中的每一個(gè)進(jìn)程都有自己獨(dú)立的虛擬內(nèi)存空間,而同一個(gè)進(jìn)程中的多個(gè)線程則共用進(jìn)程的內(nèi)存空間。每創(chuàng)建一個(gè)新的線程,都需要一些內(nèi)存(如每個(gè)線程有自己的Stack空間)和消耗一定的CPU時(shí)間。另外當(dāng)多個(gè)線程對(duì)同一個(gè)資源出現(xiàn)爭奪的時(shí)候需要注意線程安全問題。
創(chuàng)建線程
創(chuàng)建一個(gè)新的線程就是給進(jìn)程增加了一個(gè)執(zhí)行流,執(zhí)行流總得有要執(zhí)行的代碼,因此新建一個(gè)線程需要提供一個(gè)函數(shù)或者方法作為線程的入口。創(chuàng)建線程一般有以下三種方式:
NSThread提供了創(chuàng)建線程的途徑,還提供了檢測當(dāng)前線程是否是主線程的方法。 使用NSThread創(chuàng)建一個(gè)新的線程有兩種方式:
其實(shí)NSObject直接就加入了多線程的支持,允許對(duì)象的某個(gè)方法在后臺(tái)運(yùn)行。如:
[myObj performSelectorInBackground:@selector(doSomething) withObject:nil];
由于Mac和iOS都是基于Darwin系統(tǒng),Darwin系統(tǒng)的XUN內(nèi)核,是基于Mach和BSD的,繼承了BSD的POSIX接口,所以可以直接使用POSIX線程的相關(guān)接口來使用線程。
很多時(shí)候我們使用多線程,需要控制線程的并發(fā)數(shù),畢竟線程也是消耗系統(tǒng)資源的,當(dāng)程序中同時(shí)運(yùn)行的線程過多時(shí),系統(tǒng)必然變慢。所以很多時(shí)候我們會(huì)控制同時(shí)運(yùn)行線程的數(shù)目。
另外,iphone中的線程應(yīng)用并不是無節(jié)制的,官方給出的資料顯示iPhone OS下的主線程的堆棧大小是1M,第二個(gè)線程開始都是512KB。并且該值不能通過編譯器開關(guān)或線程API函數(shù)來更改。只有主線程有直接修改UI的能力。
NSOperation可以封裝我們的操作,然后將創(chuàng)建好的NSOperation對(duì)象放到NSOperationQueue中,OperationQueue便開始啟動(dòng)新的線程去執(zhí)行隊(duì)列中的操作,OperationQueue的并發(fā)度是可以通過如下方式進(jìn)行設(shè)置:
- (void)setMaxConcurrentOperationCount:(NSInteger)count GCD是Grand Central Dispatch的縮寫,是一系列的BSD層面的接口,在Mac 10.6 和iOS4.0以后才引入的,且現(xiàn)在NSOperation和NSOperationQueue的多線程的實(shí)現(xiàn)就是基于GCD的。目前這個(gè)特性也被移植到 FreeBSD上了,可以查看libdispatch這個(gè)開源項(xiàng)目。
線程間通信
線程間通信和進(jìn)程間通信從本質(zhì)上講是相似的。線程間通信就是在進(jìn)程內(nèi)的兩個(gè)執(zhí)行流之間進(jìn)行數(shù)據(jù)的傳遞,就像兩條并行的河流之間挖出了一道單向流動(dòng)長溝,使得一條河流中的水可以流入另一條河流,物質(zhì)得到了傳遞。
1.performSelect On The Thread
框架為我們提供了強(qiáng)制在某個(gè)線程中執(zhí)行方法的途徑,如果兩個(gè)非主線程的線程需要相互間通信,可以先將自己的當(dāng)前線程對(duì)象注冊到某個(gè)全局的對(duì)象中去,這樣相互之間就可以獲取對(duì)方的線程對(duì)象,然后就可以使用下面的方法進(jìn)行線程間的通信了,由于主線程比較特殊,所以框架直接提供了在出線程執(zhí)行的方法。

2.Mach Port
在蘋果的Thread PRogramming Guide的Run Pool一節(jié)的Configuring a Port-Based Input Source 這一段中就有使用Mach Port進(jìn)行線程間通信的例子。 其實(shí)質(zhì)就是父線程創(chuàng)建一個(gè)NSMachPort對(duì)象,在創(chuàng)建子線程的時(shí)候以參數(shù)的方式將其傳遞給子線程,這樣子線程中就可以向這個(gè)傳過來的 NSMachPort對(duì)象發(fā)送消息,如果想讓父線程也可以向子線程發(fā)消息的話,那么子線程可以先向父線程發(fā)個(gè)特殊的消息,傳過來的是自己創(chuàng)建的另一個(gè) NSMachPort對(duì)象,這樣父線程便持有了子線程創(chuàng)建的port對(duì)象了,可以向這個(gè)子線程的port對(duì)象發(fā)送消息了。
當(dāng)然各自的port對(duì)象需要設(shè)置delegate以及schdule到自己所在線程的RunLoop中,這樣來了消息之后,處理port消息的delegate方法會(huì)被調(diào)用,你就可以自己處理消息了。
RunLoop
RunLoop從字面上看是運(yùn)行循環(huán)的意思,這一點(diǎn)也不錯(cuò),它確實(shí)就是一個(gè)循環(huán)的概念,或者準(zhǔn)確的說是線程中的循環(huán)。 本文一開始就提到有些程序是一個(gè)圈,這個(gè)圈本質(zhì)上就是這里的所謂的RunLoop,就是一個(gè)循環(huán),只是這個(gè)循環(huán)里加入了很多特性。
首先循環(huán)體的開始需要檢測是否有需要處理的事件,如果有則去處理,如果沒有則進(jìn)入睡眠以節(jié)省CPU時(shí)間。 所以重點(diǎn)便是這個(gè)需要處理的事件,在RunLoop中,需要處理的事件分兩類,一種是輸入源,一種是定時(shí)器,輸入源傳遞異步事件,通常消息來自于其他線程或程序。定時(shí)源好理解就是那些需要定時(shí)執(zhí)行的操作,傳遞同步事件,發(fā)生在特定時(shí)間或者重復(fù)的時(shí)間間隔。兩種源都使用程序的某一特定的處理例程來處理到達(dá)的事件。輸入源分三類:performSelector源,基于端口(Mach port)的源,以及自定義的源。編程的時(shí)候可以添加自己的源。RunLoop還有一個(gè)觀察者Observer的概念,可以往RunLoop中加入自己的觀察者以便監(jiān)控著RunLoop的運(yùn)行過程。
如果你使用過select系統(tǒng)調(diào)用寫過程序你便可以快速的理解runloop事件源的概念,本質(zhì)上講事件源的機(jī)制和select一樣是一種多路復(fù)用IO的 實(shí)現(xiàn),在一個(gè)線程中我們需要做的事情并不單一,如需要處理定時(shí)鐘事件,需要處理用戶的觸控事件,需要接受網(wǎng)絡(luò)遠(yuǎn)端發(fā)過來的數(shù)據(jù),將這些需要做的事情統(tǒng)統(tǒng)注冊到事件源中,每一次循環(huán)的開始便去檢查這些事件源是否有需要處理的數(shù)據(jù),有的話則去處理。 拿具體的應(yīng)用舉個(gè)例子,NSURLConnection網(wǎng)絡(luò)數(shù)據(jù)請求,默認(rèn)是異步的方式,其實(shí)現(xiàn)原理就是創(chuàng)建之后將其作為事件源加入到當(dāng)前的 RunLoop,而等待網(wǎng)絡(luò)響應(yīng)以及網(wǎng)絡(luò)數(shù)據(jù)接受的過程則在一個(gè)新創(chuàng)建的獨(dú)立的線程中完成,當(dāng)這個(gè)線程處理到某個(gè)階段的時(shí)候比如得到對(duì)方的響應(yīng)或者接受完了網(wǎng)絡(luò)數(shù)據(jù)之后便通知之前的線程去執(zhí)行其相關(guān)的delegate方法。所以在Cocoa中經(jīng)常看到scheduleInRunLoop:forMode: 這樣的方法,這個(gè)便是將其加入到事件源中,當(dāng)檢測到某個(gè)事件發(fā)生的時(shí)候,相關(guān)的delegate方法便被調(diào)用。對(duì)于CoreFoundation這一層而言,通常的模式是創(chuàng)建輸入源,然后將輸入源通過CFRunLoopAddSource函數(shù)加入到RunLoop中,相關(guān)事件發(fā)生后,相關(guān)的回調(diào)函數(shù)會(huì)被調(diào) 用。如Cfsocket的使用。 另外RunLoop中還有一個(gè)運(yùn)行模式的概念,每一個(gè)運(yùn)行循環(huán)必然運(yùn)行在某個(gè)模式下,而模式的存在是為了過濾事件源和觀察者的,只有那些和當(dāng)前 RunLoop運(yùn)行模式一致的事件源和觀察者才會(huì)被激活。
每一個(gè)線程都有其對(duì)應(yīng)的RunLoop,但是默認(rèn)非主線程的RunLoop是沒有運(yùn)行的,需要為RunLoop添加至少一個(gè)事件源,然后去run它。一般情況下我們是沒有必要去啟用線程的RunLoop的,除非你在一個(gè)單獨(dú)的線程中需要長久的檢測某個(gè)事件。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注