1. 程式人生 > >我對多執行緒的理解和分類

我對多執行緒的理解和分類

一、多執行緒的定義和使用資訊:

多執行緒是一個比較輕量級的方法來實現單個應用程式內多個程式碼執行路徑

在系統級別內,程式並排執行,程式分配到每個程式的執行時間是基於該程式的所需時間和其他程式的所需時間來決定的。

然而,在每個程式內部,存在一個或者多個執行執行緒,它同時或在一個幾乎同時發生的方式裡執行不同的任務。

概要提示:

iPhone中的執行緒應用並不是無節制的,官方給出的資料顯示,iPhone OS下的主執行緒的堆疊大小是1M,第二個執行緒開始就是512KB,並且該值不能通過編譯器開關或執行緒API函式來更改,只有主執行緒有直接修改UI的能力

(一)執行緒概述

有些程式是一條直線,起點到終點——如簡單的hello world,執行列印完,它的生命週期便結束了,像是曇花一現。

有些程式是一個圓,不斷迴圈直到將它切斷——如作業系統,一直執行直到你關機。

一個執行著的程式就是一個程序或者叫做一個任務,一個程序至少包含一個執行緒,執行緒就是程式的執行流。

Mac和IOS中的程式啟動,建立好一個程序的同時,一個執行緒便開始運作,這個執行緒叫做主執行緒。主線成在程式中的位置和其他執行緒不同,它是其他執行緒最終的父執行緒,且所有的介面的顯示操作即AppKit或UIKit的操作必須在主執行緒進行。

系統中每一個程序都有自己獨立的虛擬記憶體空間,而同一個程序中的多個執行緒則公用程序的記憶體空間。

每建立一個新的進成,都需要一些記憶體(如每個執行緒有自己的stack空間)和消耗一定的CPU時間。

當多個進成對同一個資源出現爭奪的時候需要注意執行緒安全問題

建立執行緒

建立一個新的執行緒就是給程序增加一個執行流,所以新建一個執行緒需要提供一個函式或者方法作為執行緒的進口。

(1)使用NSThread

NSThread提供了建立執行緒的路徑,還可以提供了監測當前執行緒是否是主執行緒的方法

使用NSThread建立一個新的執行緒有兩種方式:

      1.建立一個NSThread的物件,呼叫Start方法——使用一個目標物件的方法初始化一個NSThread物件,或者建立一個繼承自NSThread的子類,實現起main方法?,然後在直接建立這個子類的物件。

      2.使用detachNewThreadSelector:toTarget:withObject:這個類方法建立一個子執行緒,這個比較直接,直接使用目標物件的方法作為執行緒啟動入口

(2)使用NSObject

使用NSObject直接就加入了對多執行緒的支援,允許物件的某個方法在後臺執行。

[my0bj performSelectorInBackground:@selector(doSomething) withObject:nil];

(3)POSIX Thread

由於Mac和IOS都是基於Darwin系統,Darwin系統的UNX核心,是基於mach和BSD的,繼承了BSD的POSIX介面,所以可以直接使用POSIX執行緒的相關介面開實現執行緒

建立執行緒的介面為 pthread_create, 當然在建立執行緒之前可以建立好相關執行緒的屬性

——————————————————————————————————————————————————————————————

NSOperation&NSOperationQueue

很多時候我們使用多執行緒,需要控制執行緒的併發數,畢竟執行緒也是需要消耗系統資源的,當程式中同時執行的執行緒過多時,系統必然變慢,所以很多時候我們會控制同時執行執行緒的數目

NSOperation可以封裝我們的操作,然後將建立好的NSOperation物件放到NSOperationQueue佇列中,OperationQueue便開始啟動新的執行緒去執行佇列中的操作,OperationQueue的併發數時可以通過如下方式進行設定的:

- (void)setMaxConcurrentOperationCount:(NSInteger)count

GCD時Grand central Dispatch的縮寫,是一系列BSD層面的介面。在mac10.6和IOS4.0以後才引入的

且現在NSOperation和NSOperationQueue的多執行緒的實現就是基於GCD的。目前這個特性也被移植到 FreeBSD上了,可以檢視libdispatch這個開源專案。

?
1 dispatch_queue_t imageDownloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
?
1 當然,GCD除了處理多執行緒外還有很多非常好的功能,其建立在強大的kqueue之上,效率也能夠得到保障。

IOS的多執行緒,一般分為三種方式:

1、Thread;

2、Cocoa operations;

3、Grand Central Dispatch (GCD) (iOS4 才開始支援)

下面簡單說明一下:

1:NSThread

建立方式主要有兩種:

[NSThread detachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:self withObject:nil];

NSThread* myThread = [[NSThread alloc] initWithTarget:self
selector:@selector(myThreadMainMethod:)
object:nil];
[myThread start]; //啟動執行緒

這兩種方式的區別是:前一種一呼叫就會立即建立一個執行緒來做事情;而後一種雖然你 alloc 了也 init了,但是要直到我們手動呼叫 start 啟動執行緒時才會真正去建立執行緒。這種延遲實現思想在很多跟資源相關的地方都有用到。後一種方式我們還可以在啟動執行緒之前,對執行緒進行配置,比如設定 stack 大小,執行緒優先順序。

此外還有一種間接的方式:利用NSObject的方法

performSelectorInBackground:withObject: 來建立一個執行緒:
[myObj performSelectorInBackground:@selector(myThreadMainMethod) withObject:nil]; //在後臺執行某一個方法
其效果與 NSThread 的 detachNewThreadSelector:toTarget:withObject: 是一樣的。

2、NSOperation:

官方解釋:The NSOperation class is an abstract class you use to encapsulate the code and data associated with a single task. Because it is abstract, you do not use this class directly but instead subclass or use one of the system-defined subclasses (NSInvocationOperation or NSBlockOperation) to perform the actual task.

併發執行你需要過載如下4個方法

//執行任務主函式,執行緒執行的入口函式

-(void)start

//是否允許併發,返回YES,允許併發,返回NO不允許。預設返回NO

-(BOOL)isConcurrent

- (BOOL)isExecuting

//是否已經完成,這個必須要過載,不然放在放在NSOperationQueue裡的NSOpertaion不能正常釋放。

- (BOOL)isFinished

比如TestNSOperation:NSOperaion 過載上述的4個方法,

宣告一個NSOperationQueue,NSOperationQueue *queue = [[[NSOperationQueue alloc ] init]autorelease];

[queue addOperation:testNSoperation];

它會自動呼叫TestNSOperation裡的start函式,如果需要多個NSOperation,你需要設定queue的一些屬性,如果多個NSOperation之間有依賴關係,也可以設定,具體可以參考API文件。

非併發執行

-(void)main

只需要過載這個main方法就可以了。

3、GCD

dispatch_async(dispatch_queue_t queue,dispatch_block_t block);

async表明非同步執行,block代表的是你要做的事情,queue則是你把任務交給誰來處理了.

之所以程式中會用到多執行緒是因為程式往往會需要讀取資料,然後更新UI.為了良好的使用者體驗,讀取資料的操作會傾向於在後臺執行,這樣以避免阻塞主執行緒.GCD裡就有三種queue來處理。

1. Main queue:

  顧名思義,執行在主執行緒,由dispatch_get_main_queue獲得.和ui相關的就要使用MainQueue.

2.Serial quque(private dispatch queue)

  每次執行一個任務,可以新增多個,執行次序FIFO. 通常是指程式設計師生成的.

3. Concurrent queue(globaldispatch queue):

可以同時執行多個任務,每個任務的啟動時間是按照加入queue的順序,結束的順序依賴各自的任務.使用dispatch_get_global_queue獲得.

所以我們可以大致瞭解使用GCD的框架:

1

2

3

4

5

6

7

dispatch_async(getDataQueue,^{

//獲取資料,獲得一組後,重新整理UI.

dispatch_aysnc(mainQueue, ^{

//UI的更新需在主執行緒中進行

};

}

)

下面就來總結一下這三種多執行緒方式的區別吧:

Thread 是這三種正規化裡面相對輕量級的,但也是使用起來最負責的,你需要自己管理thread的生命週期,執行緒之間的同步。執行緒共享同一應用程式的部分記憶體空間, 它們擁有對資料相同的訪問許可權。你得協調多個執行緒對同一資料的訪問,一般做法是在訪問之前加鎖,這會導致一定的效能開銷。在 iOS 中我們可以使用多種形式的 thread:

Cocoa threads: 使用NSThread 或直接從 NSObject 的類方法 performSelectorInBackground:withObject: 來建立一個執行緒。如果你選擇thread來實現多執行緒,那麼 NSThread 就是官方推薦優先選用的方式。
Cocoa operations是基於 Obective-C實現的,類 NSOperation 以面向物件的方式封裝了使用者需要執行的操作,我們只要聚焦於我們需要做的事情,而不必太操心執行緒的管理,同步等事情,因為NSOperation已經為我 們封裝了這些事情。 NSOperation 是一個抽象基類,我們必須使用它的子類。iOS 提供了兩種預設實現:NSInvocationOperation 和 NSBlockOperation。
Grand Central Dispatch (GCD): iOS4 才開始支援,它提供了一些新的特性,以及執行庫來支援多核並行程式設計,它的關注點更高:如何在多個 cpu 上提升效率。

最後,既然說道多執行緒的開發,難免會在多執行緒之間進行通訊;

利用NSObject的一些類方法,可以輕鬆搞定。

在應用程式主執行緒中做事情:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array
在指定執行緒中做事情:
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array

在當前執行緒中做事情:

//Invokes a method of the receiver on the current thread using the default mode after a delay.

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay

performSelector:withObject:afterDelay:inModes:
取消傳送給當前執行緒的某個訊息
cancelPreviousPerformRequestsWithTarget:

cancelPreviousPerformRequestsWithTarget:selector:object:
如在我們在某個執行緒中下載資料,下載完成之後要通知主執行緒中更新介面等等,可以使用如下介面:- (void)myThreadMainMethod
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// to do something in your thread job
...
[self performSelectorOnMainThread:@selector(updateUI) withObject:nil waitUntilDone:NO];
[pool release];
}

新增內容:

iOS開發中的多執行緒,無疑是個很重要的知識點,要想把握多執行緒這塊,就要學會以下這些。
一、程序
在移動端,一個app就是一個程序,在記憶體中佔用一定的空間。
在計算機裡,一個程式就是一個程序,同樣也佔用記憶體空間。
iOS同一時間點只有一個程序在使用CPU,只是系統把這個時間片分割地非常短,造成一種多個程序同時在執行的假象。

二、執行緒
一個程序的執行,必然從一個主執行緒開始。整個應用可以由單個主執行緒執行,但是涉及到一些耗時的任務,例如開啟淘寶app,必然要載入一大堆的圖片。
這時,如果只有單執行緒執行,程式必須等著圖片都載入完畢才能繼續往下執行,期間使用者的互動就不起作用,這樣使用者體驗很不好。
所以,這時就衍生出多執行緒的概念,可以開子執行緒給那些耗時的任務,在旁邊默默地執行,而不影響應用的大局。
主執行緒,一般用來處理主體的展示(例如控制器的切換)和互動事件。
子執行緒,一般用來處理耗時的任務。當然,並不是執行緒越多越好,多執行緒的使用也是要慎重考慮。

三、同步和非同步
我之前一直對同步和非同步這個概念理解不清,常常混淆。同步是執行緒安全呢,還是非同步執行緒安全呢?
今天終於記清楚了這個概念,只要記住一句話——同步,就是同類;非同步,就是異類。已經是同類,那肯定是處於同一個執行緒;異類,那就說明不是一個執行緒。

四、並行和序列
並行:併發執行
序列:按順序執行,一個接一個

五、三種常用建立多執行緒方式
1.NSThread:程式設計師手動管理執行緒,而多執行緒的情況下,執行緒什麼時候執行完畢是未知的,如果管理不好,會造成記憶體洩露,所以這種方法不提倡。
2.NSOperation\NSOperationQueue。這兩個類必須是搭配使用的,將操作放入操作佇列中,依次執行。
使用步驟:建立NSOperation;新增NSOperation到NSOperationQueue
優點:更加面向物件;可以控制最大併發數 maxConcurrentOperationCount,使用這個屬性可以保證同一時間內最大的併發數;新增任務(Operation)之間的依賴 addDependency,使用這個屬性可以控制一個Operation必須在其依賴的Operation執行完畢後才呼叫。
3.GCD(官方推薦使用,純C語言)
呼叫同步(非同步)執行的方法,傳入要並行(序列)執行的佇列引數,執行方法內的block程式碼。說白了就是同一時間有一個還是多個執行緒執行,就看呼叫的方法和傳入的佇列型別。
佇列型別:
全域性佇列:所有新增到全域性佇列中的任務都是併發執行(同時執行,可能會開啟多個執行緒)
序列佇列:所有新增到序列佇列中的任務都是按順序執行(開一條執行緒)
主佇列:所有新增到主佇列中的任務都是在主執行緒中執行的(跟方法名沒有關係)
同步還是非同步,取決於方法名(不影響主佇列,影響全域性佇列、序列佇列)
同步:dispatch_sync,在當前執行緒執行任務,不會開啟新的執行緒
非同步:dispatch_async,在其他執行緒執行任務,會開啟新的執行緒
程式碼demo演示:從組合學上說,總是共有四種情況:序列-同步、序列-非同步、並行-同步、並行-非同步。
序列-同步:顯然一直只有一個執行緒在執行(這個就是真正意義上單執行緒)
序列-非同步:可能會產生多個執行緒,但是同一時間只有一個執行緒在執行(非同步雖然會產生多個不同執行緒,但是同一時間只有一個執行緒在執行)
並行-同步:同一時間點有多個相同的執行緒在執行
並行-非同步:同一時間有多個不同的執行緒在執行(這是真正意義上的多執行緒)

下面就只舉兩個例子,剩下的2種情況舉一反三就是了

而主佇列的使用,常常用來執行完子執行緒後,要講資料返回主執行緒來進行處理。比如開啟子執行緒下載某個資源,下載完畢需要回調到主執行緒來展示。可以在子執行緒完成的時候呼叫以下的方法返回主執行緒,同時能夠將子執行緒得到的引數傳給處理的selector方法裡執行。
4、開啟後臺執行緒