iOS 從實際出發理解多線程

來源:互聯網
上載者:User

標籤:selector   set   分類   man   ext   https   線程   dex   ash   

前言

    

      多線程很多開發人員多多少少相信也都有瞭解,以前有些東西理解的不是很透,慢慢的積累之後,這方面的東西也需要自己好好的總結一下。多線程從我剛接觸到iOS的時候就知道這玩意挺重要的,但那時也是能力有限,沒辦法很好的理解它,要是只是查它的概念性的東西,網上一搜一大把,我們再那樣去總結就顯得意義不大了。這篇文章從我剛開始構思著去寫的時候,就希望自己能換個角度去寫,想從實際問題出發總結多線程,那就從第三方以及自己看到的一些例子還有前段時間讀的多線程和記憶體管理的書中分析理解總結一下多線程。

 

這幾個概念很容易繞暈

 

       進程:進程就是線程的容器,你開啟一個App就是開啟了一個進程,QQ有QQ的進程,有的進程,一個進程可以包含多個線程,要是把進程比喻成一條高速公路,線程就是高速路上的一條條車道,也正是因為有了這些車道,整個交通的運行效率變得更高,也正是因為有了多線程的出現,整個系統運行效率變得更高。

      二 線程:線程就是在進程中我麼開闢的一條條為我們做事的進程實體,總結的通俗一點,線程就是我們在進程上開闢的一條條做我們想做的事的通道。 一條線程在一個時間點上只能做一件“事”,多線程在同一時間點上,就能做多件“事”,這個理解,還是我們前面說的高速路的例子。

      一條高速路是一個進程, 一條條車道就是不同的線程,在過收費站的時候,這條進程上要是只有一條線程,也就是一條高速路上只有一個車道,那你就只能排隊一輛一輛的通過,同一時間不可能有兩輛車一起過去,但要是你一個進程上有多個線程,也就是高速路上有幾個車道,也就有多個視窗收費,這樣的話同一時間就完全有可能兩輛車一起交完費通過了,這樣說相信也能理解這個進程和線程的關係了。

  • 同步線程:同步線程會阻塞當前的線程去執行同步線程裡面想做的“事”(任務),執行完之後才會返回當前線程。       
  • 非同步線程:非同步線程不會阻塞當前的線程去執行非同步線程裡面想做的“事”,因為是非同步,所以它會重新開啟一個線程去做想做的“事”。 

      三 隊列:隊列就是用來管理下面說的“任務”的,它採用的是先進先出(FIFO)的原則,它衍生出來的就是下面的它的分類並行和串列隊列,一條線程上可以有多個隊列。

  • 並行隊列:這個隊列裡面的任務是可以並發(同時)執行的,由於我們知道,同步執行任務不會開啟新的線程,所以並行隊列同步執行任務任務只會在一條線程裡面同步執行這些任務,又由於同步執行也就是在當前線程中做事,這個時候就需要一件一件的讓“事”(任務)做完在接著做下一個。但要是是並發隊列非同步執行,就對應著開啟非同步線程執行要做的“事”(任務),就會同一時間又許多的“事”被做著。
  • 串列隊列:這個隊列裡面的任務是串列也就是一件一件做的,串列同步會一件一件的等事做完再接著做下一件,要是非同步就會開啟一條新的線程串列的執行我們的任務。

    四 任務:任務按照自己通俗一點的理解,就是提到的“事”這個概念,這個“事”就可以理解為任務,那這個“事”也肯定是線上程上面執行的(不管是在當前線程還是你另開啟的線程)。這個“事”你可以選擇同步或者而是非同步執行,這就衍生出了東西也就契合線程上面的同步線程和非同步線程。

  • 同步任務:不需要開啟新的線程,在當前線程執行就可以。
  • 非同步任務:你需要開闢一條新的線程去非同步執行這個任務。     

      iOS當中還有一個特殊的串列隊列-- 主隊列, 這個主隊列中運行著一條特殊的線程 -- 主線程

      主線程又叫UI線程,UI線程顧名思義主要的任務及時處理UI,也只有主線程有處理UI的能力,其他的耗時間的操作我們就放在子線程(也就是開闢線程)去執行,開線程也會佔據一定的記憶體的,所以不要同時開啟很多的線程。 

      通過上面的內容解釋了多線程裡面幾個關鍵的概念的東西,要是有不理解的地方歡迎多交流,下面再給出隊列執行時候的一個啟動並執行表格,我們一個一個慢慢的解釋。

     

 

NSThread

 

      其實在我們日常的開發中NSThread使用也是挺多的,具體關於它的一些我們需要注意的地方我們一步步的開始說,先看看它的初始化的幾個方法

/* 初始化NSThread的類方法,具體的任務在Block中執行 + (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));  利用selector方法初始化NSThread,target指selector方法從屬於的對象  selector方法也是指定的target對象的方法 + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;  初始化NSThread的方法,這兩個方法和上面兩個方法的區別就是這兩個你能擷取到NSThread的對象 具體的參數和前面解釋的參數意義都是一樣的 切記一點:  下面兩個方法初始化的NSThread你需要手動start開啟線程 - (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument NS_AVAILABLE(10_5, 2_0);  - (instancetype)initWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)); */

 

      除了上面四個我們提出的方法,我們在初始化這個問題上還需要注意的還有一點,就是 NSObject (NSThreadPerformAdditions) ,為我們的NSObject添加的這個類別,它裡面的具體的一些方法我們也是很常用的:

/* 這個方法你執行的aSelector就是在MainThread執行的,也就是在主線程 注意這裡的waitUntilDone這個後面的BOOL類型的參數,這個參數表示是否等待一直到aSelector這個方法執行結束 modes是RunLoop的啟動並執行類型這個RunLoop我也會好好在總結後面 - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;  - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;   // equivalent to the first method with kCFRunLoopCommonModes  上面的兩個方法是直接在主線程裡面運行,下面的這兩個方法是要在你初始化的thr中去運行,其他的參數和上面解釋的一樣 - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array NS_AVAILABLE(10_5, 2_0);  - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);   // equivalent to the first method with kCFRunLoopCommonModes  - (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0); */

     

      我們在說說前面說的waitUntilDone後面的這個BOOL類型的參數,這個參數的意義有點像我們是否同步執行aSelector這個任務!具體的看下面兩張圖的內容就一目瞭然了:

      在看看等於YES的時候結果的輸出情況:

 

      關於NSThread我們再說下面幾個方法的具體的含義就不在描述了,關於NSThread有什麼其他的問題,可以加我QQ交流:

/*  設定線程沉睡到指定日期 + (void)sleepUntilDate:(NSDate *)date;  線程沉睡時間間隔,這個方法在設定啟動頁間隔的時候比較常見 + (void)sleepForTimeInterval:(NSTimeInterval)ti;  線程退出,當執行到某一個特殊情況下的時候你可以退出當前的線程,注意不要在主線程隨便調用 + (void)exit;  線程的優先順序 + (double)threadPriority;  設定線程的優先順序 + (BOOL)setThreadPriority:(double)p;  */

 

NSOperation

 

      多線程我們還得提一下NSOperation,它可能比我們認識中的要強大一點,NSOperation也是有很多東西可以說的,前面的NSThread其實也是一樣,這些要是仔細說的話都能寫一篇文章出來,可能以後隨著自己接觸的越來越多,關於多線程這一塊的東西我們會獨立的建立一個分類總結出去。

      首先得知道NSOperation是基於GCD封裝的,NSOperation這個類本身我們使用的時候不躲,更多的是集中在蘋果幫我們封裝好的NSInvocationOperation和NSBlockOperation

      你command一下NSOperation進去看看,有幾個點你還是的瞭解一下的,主要的就是下面的幾個方法:

NSOperation * operation = [[NSOperation alloc]init];[operation start];   //開始[operation cancel]; //取消[operation setCompletionBlock:^{    //operation完成之後的操作}];

      我們具體的說一下我們上面說的兩個類:NSInvocationOperation和NSBlockOperation,先看看NSInvocationOperation的初始化:

/*  初始化方法 看過前面的文章之後它的target 、sel 、arg 等參數相信不難理解 -(nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;  -(instancetype)initWithInvocation:(NSInvocation *)inv NS_DESIGNATED_INITIALIZER;  */

       補充: NS_DESIGNATED_INITIALIZER 指定初識化方法並不是對使用者。而是對內部的現實,可以點擊進去具體瞭解一下它!NSInvocationOperation其實是同步執行的,因此單獨使用的話就價值不大了,它和NSOperationQueue一起去使用才能實現多線程調用。這個我們後面再具體的說

      在看看NSBlockOperation這個,它重要的方法就我們下面的兩個        

/* 初始化方法 + (instancetype)blockOperationWithBlock:(void (^)(void))block;  添加一個可以執行的block到前面初始化得到的NSBlockOperation中 - (void)addExecutionBlock:(void (^)(void))block; */

      NSBlockOperation這個我們得提一點: 它的最大的並發具體的最大並發數和運行環境也是有關係的,具體的內容我們可以戳戳這裡同行總結以及驗證的,我們由於篇幅的原因就不在這裡累贅。

      其實只要是上面這些的話是不夠我們日常使用的,但還有一個啟用他們倆的類我們也得說說:NSOPerationQueue 下面是關於它的大概的一個說明,都挺簡單,就不在特意寫Demo。

      

      關於NSOperation的我們就說這麼多,下面重點說一下GCD。

  

主角GCD -- 主線程

      

      1、我們先從主隊列,主線程開始說起,通過下面的方法我們就可以擷取得到主隊列:

dispatch_queue_t mainqueue = dispatch_get_main_queue(); 

      2、我們在主線程同步執行任務,下面是操作的結果以及列印的資訊:

 

      我們解釋一下為什麼在主線程中執行同步任務會出現這個結果,我們一步一步的梳理一下這個執行過程:

  1. 擷取到在主隊列主線程中執行了最前面的列印資訊,這個沒什麼問題
  2. 開始執行dispatch_sync這個函數,主隊列是串列隊列,這個函數會把這個任務插入到主隊列的最後面(理解隊列新增工作)
  3. 主線程執行到這裡的時候就會等待插入的這個同步任務執行完之後再執行後面的操作
  4. 但由於這個同步任務是插入到主隊列的最後面,最隊列前面的任務沒有執行完之前是不會執行這個block的(主線程在執行initMainQueue任務)
  5. 這樣就造成了一個相互等待的過程,主線程在等待block完返回,block卻在等待主線程執行它,這樣就造成了死結,看列印的資訊你也就知道block是沒有被執行的。

      這裡我們你可能會思考,主隊列是一個串列隊列,那我們在主線程中添加一個串列隊列,再給串列隊列添加一個同步任務,這時候和前面主線程主隊列添加同步任務不就情境一樣了嗎?那結果呢? 我們看看下面的列印:

 

 

      我們按照前面的方式解釋一下這個的執行步驟:

  1. 主線程在執行主隊列中的方法initSerialQueue,到這個方法時候建立了一個串列隊列(注意不是主隊列)列印了前面的第一條資訊
  2. 執行到dispatch_sync函數,這個函數給這個串列隊列中添加了一個同步任務,同步任務是會立馬執行的
  3. 主線程就直接操作執行了這個隊列中的同步任務,列印的第二條資訊
  4. 主線程接著執行下面的第三條列印資訊

      理解:看這個執行的過程對比前面的,你就知道了不同的地方就是前面是添加在了主隊列當中,但這裡有添加到主隊列,由於是插入到主隊列的末尾,所以需要主隊列的任務都執行完才能指定到它,但主線程執行到initMainQueue這個方法的時候在等待這個方法中添加的同步任務執行完接著往下執行,但它裡面的同步任務又在等待主線程執行完在執行它,就相互等待了,但主線程執行不是主隊列裡面的同步任務的時候是不需要主線程執行完所有操作在執行這個任務的,這個任務是它添加到串列隊列的開始也是結束的任務,由於不需要等待,就不會造成死結!

      上面這個問題經常會看到有人問,有許多解釋,也希望自己能把這個問題給說清楚了!

 

      3、主線程這裡我們再提一點,就是線程間的資訊簡單傳遞

      前面我們有說到主線程又叫做UI線程,所有關於UI的事我們都是在主線程裡面更新的,像下載資料以及資料庫的訪問等這些耗時的操作我們是建議放在子線程裡面去做,那就會產生子線程處理完這些之後要回到主線程更行UI的問題上,這一點值得我們好好的注意一下,但其實這一點也是我們用的最多的,相信大家也都理解!

 

主角GCD  --  串列隊列

 

      串列隊列的概念性的東西我們就不在這裡累贅,不管是串列隊列+同步任務還是串列隊列+非同步任務都簡單,有興趣可以自己是這寫一下,後面分析會提到他們的具體使用的,我們在一個稍微比前面的說的複雜一點點的問題,串列隊列+非同步+同步,可以先試著不要往下面看先分析一下下面這段代碼的執行結果是什嗎?

static void * DISPATCH_QUEUE_SERIAL_IDENTIFY;-(void)initDiapatchQueue{        dispatch_queue_t serialQueue = dispatch_queue_create(DISPATCH_QUEUE_SERIAL_IDENTIFY, DISPATCH_QUEUE_SERIAL);        dispatch_async(serialQueue, ^{                           NSLog(@"一個非同步任務的內容%@",[NSThread currentThread]);                dispatch_sync(serialQueue, ^{                                           NSLog(@"一個同步任務的內容%@",[NSThread currentThread]);                });        });}

 

不知道你分析數來的這點代碼的結果是什麼,我們這裡來看看結果,然後和上面一步一步的分析一下它的整個的執行過程,就能找到答案:

 

 

      答案就是crash了,其實也是死結,下面一步一步的走一下這整個過程,分析一下哪裡死結了:

  1. 主線程主隊列中執行任務initDispatchQueue,進入了這個方法,在這個方法裡面建立了一個串列隊列,這一步相信大家都明白,沒什麼問題。
  2. 給這個串列隊列添加了一個非同步任務,由於是非同步任務,所以會開啟一條新的線程,為了方便描述,我們把新開的這個線程記做線程A, 把這個任務記做任務A,也由於是非同步任務,主線程就不會等待這個任務返回,就接著往下執行其他任務了。
  3. 接下來的分析就到了這個線程A上,這個任務A被添加到串列隊列之後就開始線上程A上執行,列印出了我們的第一條資訊,也證明了不是在主線程,這個也沒問題。
  4. 線程A開始執行這個任務A,進入這個任務A之後在這個任務A裡面又同步在串列隊列裡面新增工作,記做任務B,由於任務B是dispatch_sync函數同步添加的,需要立馬被執行,就等待線程A執行它
  5. 但是這個任務B是添加到串列隊列的末尾的,線程A在沒有執行完當前任務A是不會去執行它的,這樣就造成線程A在等待當前任務A執行完,任務B又在等待線程A執行它,就形成了死結

      經過上面的分析,你就能看到這個情境和你在主線程同步新增工作是一樣的,我們再仔細的考慮一下這整個過程,在分析一下上面主線程+串列隊列+同步任務為什麼沒有形成死結!相互對比理解,就能把整個問題想明白。

 

主角GCD  --  並行隊列

 

      下面我們接著再說說這個並行隊列,並行隊列+同步執行或者並行隊列+非同步執行這個我們也就沒什麼好說的了,在這裡說說並行+非同步需要注意的地方,不知道大家有沒有想過,並行的話很多任務會一起執行,要是非同步任務的話會開啟新的線程,那是不是我們添加了十個非同步任務就會開啟十條線程呢?那一百個非同步任務豈不是要開啟一百條線程,答案肯定是否定的!那系統到底是怎麼處理的,我們也說說,下面的是進階編程書裡面的解釋我們梳理一下給出結論。

  • 當為DISPATCH_QUEUE_CONCURRENT的時候,不用等待前面任務的處理結束,後面的任務也是能夠直接執行的
  • 並存執行的處理數量取決於當前系統的狀態,即iOS和OS X基於Dispatch Queue中的處理數、CPU核心數以及CPU負荷等當前系統狀態來決定DISPATCH_QUEUE_CONCURRENT中並存執行的處理數
  • iOS 和 OS X的核心 -- XNU核心決定應當使用的線程數,並且產生所需的線程執行處理
  • 當處理結束,應當執行的處理數減少時,XNU核心會結束不在需要的線程
  • 處理並行非同步任務時候線程是可以迴圈往複使用的,比如任務1的線程執行完了任務1,線程可以接著去執行後面沒有執行的任務

      這裡的東西就這些,我們在前面串列隊列的時候,串列隊列+非同步任務嵌套同步任務會造成死結,那我們要是把它變成同步隊列呢?結果又會是什麼樣子呢?我們看看下面這段代碼的執行結果:     

 

      從上面的結果可以看得出來,是沒有問題的,這裡我們就不在一步一步的分析它的執行過程了,就說說為什麼並行的隊列就沒有問題,但是串列的隊列就會出問題:

      並行隊列添加了非同步任務也是建立了一個新的線程,然後再在這個任務裡面給並行隊列添加一個同步任務,由於是並行隊列 ,執行這個同步任務是不需要前面的非同步任務執行完了,就直接開始執行,所以也就有了下面的列印資訊,通過上面幾個問題,相信理解了之後,對於串列隊列或者並行隊列添加同步任務或者非同步任務都有了一個比較深的理解了,我們再接著往下總結。

 

GCD不僅僅這些 

 

    關於GCD的內容還有下面這些都是值得我們關注的,下面我們開始一一說一說:

  • dispatch_barrier_async

        dispatch_barrier_async 函數是我們俗稱的柵欄方法,“柵欄”的意思理解一下字面的,就是把外面和裡面阻隔開,這個函數的作用就是這樣,把插入的這個柵欄之前和之後的阻隔開,等前面的執行完了就執行“柵欄函數”插入的任務,等柵欄的任務執行結束了就開始執行柵欄後面的任務。看下面一個簡單的Demo就理解了。

      從上面就可以看到,我們把0插入到第三個任務的位置,它是等前面的兩個任務執行完了,在去執行第三個,要是你覺得這裡前兩個任務簡單,執行不需要太多的時間的話,你可以試著把前面兩個任務的“任務量”設定大一點,這樣有助於你更好的理解這個“柵欄”操作!

  • dispatch_after

        dispatch_after 延時操作

        如果某一條任務你想等多少時間之後再執行的話,你就完全可以使用這個函數處理,寫法很簡單,因為已經幫我們封裝好了,看下面這兩行代碼:

        // DISPATCH_TIME_NOW 目前時間開始        // NSEC_PER_SEC 表示時間的宏,這個可以自己上網搜尋理解        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{                                NSLog(@"延遲了10秒執行");        });
  • dispatch_apply

       dispatch_apply 類似一個for迴圈,會在指定的dispatch queue中運行block任務n次,如果隊列是並發隊列,則會並發執行block任務,dispatch_apply是一個同步調用,block任務執行n次後才返回。 由於它是同步的,要是我們下面這樣寫就會有出問題:

      可以看到出問題了,但我們要是把它放在串列隊列或者並行隊列就會是下面這樣的情況

     

  • dispatch_group_t

        dispatch_group_t的作用我們先說說,在追加到Dispatch Queue 中的多個任務全部結束之後想要執行結束的處理,這種情況也會經常的出現,在只使用一個Serial Dispatch Queue時,只要將想執行的操作全部追加該Serial Dispatch Queue中並且追加在結束處理就可以實現,但是在使用 Concurrent Dispatch Queue 時或者同時使用多個 Dispatch Queue時候,就比較的複雜了,在這樣的情況下 Dispatch Group 就可以發揮它的作用了。看看下面的這段代碼:

-(void)testDispatch_group_t{        dispatch_group_t group_t = dispatch_group_create();        dispatch_queue_t queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);        dispatch_group_async(group_t, queue_t, ^{                                NSLog(@"1--當前的線程%@",[NSThread currentThread]);        });        dispatch_group_async(group_t, queue_t, ^{                                NSLog(@"2--當前的線程%@",[NSThread currentThread]);        });        dispatch_group_async(group_t, queue_t, ^{                                NSLog(@"3--當前的線程%@",[NSThread currentThread]);        });        dispatch_group_async(group_t, queue_t, ^{                                for (int i = 1; i<10; i++) {                                             NSLog(@"4--當前的線程%@",[NSThread currentThread]);                }        });        // 當前的所有的任務都執行結束        dispatch_group_notify(group_t, queue_t, ^{                           NSLog(@"前面的全都執行結束了%@",[NSThread currentThread]);        });}

      這段代碼的意圖很明顯,看了下面的列印資訊這個你也就理解它了:

      總結: 關於多線程的最基本的問題暫時先總結這麼多,還有許多的問題,自己也在總結當中,比如以下線程鎖等等的問題,等總結到差不多的時候再分享!

 

iOS 從實際出發理解多線程

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.