GCD ios多線程 runloop

  • 一.線程概述
  • 二.建立線程
    • 1.使用NSThread
    • 2.使用NSObject
    • 3.POSIX Thread
  • 三.多線程進階
    • 1.NSOperation & NSOperationQueue
    • 2.GCD
  • 四.進程間通訊
    • 1.和主線程的通訊
    • 2.任意線程間的通訊
  • 五.RunLoop

一.線程概述

有些程序是一條直線,起點到終點;有些程序是一個圓,不斷循環,直到將它切斷。直線的如簡單的Hello World,運行打印完,它的生命週期便結束了,像曇花一現那樣;圓如操做系統,一直運行直到你關機。 
一個運行着的程序就是一個進程或者叫作一個任務,一個進程至少包含一個線程,線程就是程序的執行流。Mac和iOS中的程序啓動,建立好一個進程的同時,一個線程便開始運行,這個線程叫主線程。主線程在程序中的地位和其餘線程不一樣,它是其餘線程最終的父線程,且全部界面的顯示操做即AppKit或UIKit的操做必須在主線程進行。 
系統中的每個進程都有本身獨立的虛擬內存空間,而同一個進程中的多個線程則共用進程的內存空間。每建立一個新的線程,都須要一些內存(如每一個線程有本身的Stack空間)和消耗必定的CPU時間。另外當多個線程對同一個資源出現爭奪的時候須要注意線程安全問題。 編程

二.建立線程

建立一個新的線程就是給進程增長了一個執行流,執行流總得有要執行的代碼吧,因此新建一個線程須要提供一個函數或者方法做爲線程的入口。 安全

1.使用NSThread

NSThread提供了建立線程的途徑,還能夠提供了檢測當前線程是不是主線程的方法。 使用NSThread建立一個新的線程有兩種方式: 網絡

  • 1.建立一個NSThread的對象,調用其start方法。對於這種方式的NSThread對象的建立,可使用一個目標對象的方法初始化一個NSThread對象,或者建立一個繼承NSThread類的子類,實現其main方法,而後在直接建立這個子類的對象。
  • 2.使用 detachNewThreadSelector:toTarget:withObject:這個類方法建立一個線程,這個比較直接了,直接使用目標對象的方法做爲線程啓動入口。

2.使用NSObject

其實NSObject直接就加入了多線程的支持,容許對象的某個方法在後臺運行。如: 多線程

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

3.POSIX Thread

因爲Mac和iOS都是基於Darwin系統,Darwin系統的XUN內核,是基於Mach和BSD的,繼承了BSD的POSIX接口,因此能夠直接使用POSIX線程的相關接口來使用線程。 併發

建立線程的接口爲 pthread_create,固然在建立以前能夠經過相關函數設置好線程的屬性。如下爲POSIX線程使用簡單的例子。 框架

// // main.c // pthread // // Created by Lu Kejin on 1/27/12. // Copyright (c) 2012 Taobao.com. All rights reserved. // #include <stdio.h> #include <pthread.h> #include <unistd.h> void *pthreadRoutine(void *); int main (int argc, const char * argv[]) { pthread_attr_t attr; pthread_t pthreadID; int returnVal; returnVal = pthread_attr_init(&attr); returnVal = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); int threadError = pthread_create(&pthreadID, &attr, &pthreadRoutine, NULL); returnVal = pthread_attr_destroy(&attr); if (threadError != 0) { // Report an error. } sleep(10); return 0; } void *pthreadRoutine(void *data){ int count = 0; while (1) { printf("count = %d\n",count++); sleep(1); } return NULL; }

三.多線程進階

NSOperation&NSOperationQueue

不少時候咱們使用多線程,須要控制線程的併發數,畢竟線程也是消耗系統資源的,當程序中同時運行的線程過多時,系統必然變慢。 因此不少時候咱們會控制同時運行線程的數目。 異步

NSOperation能夠封裝咱們的操做,而後將建立好的NSOperation對象放到NSOperationQueue中,OperationQueue便開始啓動新的線程去執行隊列中的操做,OperationQueue的併發度是能夠經過以下方式進行設置: async

- (void)setMaxConcurrentOperationCount:(NSInteger)count

GCD

GCD是Grand Central Dispatch的縮寫,是一系列的BSD層面的接口,在Mac 10.6 和iOS4.0之後才引入的,且如今NSOperation和NSOperationQueue的多線程的實現就是基於GCD的。目前這個特性也被移植到FreeBSD上了,能夠查看libdispatch這個開源項目。 ide

好比一個在UIImageView中顯示一個比較大的圖片 函數

dispatch_queue_t imageDownloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(imageDownloadQueue, ^{ NSURL *imageURL = [NSURL URLWithString:@"http://test.com/test.png"]; NSData *imageData = [NSData dataWithContentsOfURL:imageURL]; UIImage *image = [UIImage imageWithData:imageData]; dispatch_async(dispatch_get_main_queue(), ^{ [imageView setImage:image];//UIKit必須在主線程執行 }); });

固然,GCD除了處理多線程外還有不少很是好的功能,其創建在強大的kqueue之上,效率也可以獲得保障。

四.線程間通訊

線程間通訊和進程間通訊從本質上講是類似的。線程間通訊就是在進程內的兩個執行流之間進行數據的傳遞,就像兩條並行的河流之間挖出了一道單向流動長溝,使得一條河流中的水能夠流入另外一條河流,物質獲得了傳遞。

1.performSelect On The Thread

框架爲咱們提供了強制在某個線程中執行方法的途徑,若是兩個非主線程的線程須要相互間通訊,能夠先將本身的當前線程對象註冊到某個全局的對象中去,這樣相互之間就能夠獲取對方的線程對象,而後就可使用下面的方法進行線程間的通訊了,因爲主線程比較特殊,因此框架直接提供了在出線程執行的方法。

@interface NSObject (NSThreadPerformAdditions) - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array; - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait; // equivalent to the first method with kCFRunLoopCommonModes - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0); - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0); // equivalent to the first method with kCFRunLoopCommonModes ... @end

2.Mach Port 
在蘋果的Thread Programming Guide的Run Pool一節的Configuring a Port-Based Input Source 這一段中就有使用Mach Port進行線程間通訊的例子。 其實質就是父線程建立一個NSMachPort對象,在建立子線程的時候以參數的方式將其傳遞給子線程,這樣子線程中就能夠向這個傳過來的NSMachPort對象發送消息,若是想讓父線程也能夠向子線程發消息的話,那麼子線程能夠先向父線程發個特殊的消息,傳過來的是本身建立的另外一個NSMachPort對象,這樣父線程便持有了子線程建立的port對象了,能夠向這個子線程的port對象發送消息了。

固然各自的port對象須要設置delegate以及schdule到本身所在線程的RunLoop中,這樣來了消息以後,處理port消息的delegate方法會被調用,你就能夠本身處理消息了。

五.RunLoop

RunLoop從字面上看是運行循環的意思,這一點也不錯,它確實就是一個循環的概念,或者準確的說是線程中的循環。 本文一開始就提到有些程序是一個圈,這個圈本質上就是這裏的所謂的RunLoop,就是一個循環,只是這個循環里加入不少特性。 
首先循環體的開始須要檢測是否有須要處理的事件,若是有則去處理,若是沒有則進入睡眠以節省CPU時間。 因此重點即是這個須要處理的事件,在RunLoop中,須要處理的事件分兩類,一種是輸入源,一種是定時器,定時器好理解就是那些須要定時執行的操做,輸入源分三類:performSelector源,基於端口(Mach port)的源,以及自定義的源。編程的時候能夠添加本身的源。RunLoop還有一個觀察者Observer的概念,能夠往RunLoop中加入本身的觀察者以便監控着RunLoop的運行過程,CFRunLoop.h中定義了全部觀察者的類型:

enum CFRunLoopActivity { kCFRunLoopEntry = (1 << 0), kCFRunLoopBeforeTimers = (1 << 1), kCFRunLoopBeforeSources = (1 << 2), kCFRunLoopBeforeWaiting = (1 << 5), kCFRunLoopAfterWaiting = (1 << 6), kCFRunLoopExit = (1 << 7), kCFRunLoopAllActivities = 0x0FFFFFFFU }; typedef enum CFRunLoopActivity CFRunLoopActivity;

若是你使用過select系統調用寫過程序你即可以快速的理解runloop事件源的概念,本質上講事件源的機制和select同樣是一種多路複用IO的實現,在一個線程中咱們須要作的事情並不單一,如須要處理定時鐘事件,須要處理用戶的觸控事件,須要接受網絡遠端發過來的數據,將這些須要作的事情通通註冊到事件源中,每一次循環的開始便去檢查這些事件源是否有須要處理的數據,有的話則去處理。 拿具體的應用舉個例子,NSURLConnection網絡數據請求,默認是異步的方式,其實現原理就是建立以後將其做爲事件源加入到當前的RunLoop,而等待網絡響應以及網絡數據接受的過程則在一個新建立的獨立的線程中完成,當這個線程處理到某個階段的時候好比獲得對方的響應或者接受完了網絡數據以後便通知以前的線程去執行其相關的delegate方法。因此在Cocoa中常常看到scheduleInRunLoop:forMode:這樣的方法,這個即是將其加入到事件源中,當檢測到某個事件發生的時候,相關的delegate方法便被調用。對於CoreFoundation這一層而言,一般的模式是建立輸入源,而後將輸入源經過CFRunLoopAddSource函數加入到RunLoop中,相關事件發生後,相關的回調函數會被調用。如CFSocket的使用。 另外RunLoop中還有一個運行模式的概念,每個運行循環必然運行在某個模式下,而模式的存在是爲了過濾事件源和觀察者的,只有那些和當前RunLoop運行模式一致的事件源和觀察者纔會被激活。

每個線程都有其對應的RunLoop,可是默認非主線程的RunLoop是沒有運行的,須要爲RunLoop添加至少一個事件源,而後去run它。通常狀況下咱們是沒有必要去啓用線程的RunLoop的,除非你在一個單獨的線程中須要長久的檢測某個事件。

相關文章
相關標籤/搜索