1. 程式人生 > >[讀書筆記]iOS與OS X多執行緒和記憶體管理 [GCD部分]

[讀書筆記]iOS與OS X多執行緒和記憶體管理 [GCD部分]

3.2 GCD的API

蘋果對GCD的說明:開發者要做的只是定義想執行的任務並追加到適當的Dispatch Queue中。 “Dispatch Queue”是執行處理的等待佇列。通過dispatch_async函式等API,在Block語法中記述想執行的處理並追加到Dispatch Queue中,Dispatch Queue按照追加的順序,執行處理。 Dispatch Queue分為兩種:
種類 說明
Serial Dispatch Queue 等待現在執行中處理結束(順序執行)
Concurrent Dispatch Queue 不等待現在執行中處理結束(併發執行)

生成Dispatch Queue的方式有兩種           第一通過GCD的API生成Dispatch Queue;
使用方法dispatch_queue_create函式可以生成Dispatch Queue。 dispatch_queue_t myDQ=dispatch_queue_create(<#const char *label#>, <#dispatch_queue_attr_t attr#>); 第一個引數是 Dispatch Queue名稱,推薦使用應用程式ID這種逆序全程域名。第二個引數是指定生成Dispatch Queue的型別,NULL代表Serial Dispatch Queue, DISPATCH_QUEUE_CONCURRENT
代表生成的是
Concurrent Dispatch Queue。使用示例:

dispatch_queue_t myQu=dispatch_queue_create("log", DISPATCH_QUEUE_CONCURRENT);
多個Serial Dispatch Queue之間是並行執行的,只在為了避免多個執行緒更新相同資源導致資料競爭時使用 Serial Dispatch Queue。 如果是iOS6或之前的系統,生成的Dispatch Queue在使用結束後要手動釋放:dispatch_release(myQu); 同樣也存在dispatch_retain函式,即Dispatch Queue也想OC的引用計數式記憶體管理一樣,可以釋放和持有。 在iOS7或之後的系統中,ARC環境下系統會自動釋放。
    dispatch_queue_t myQU=dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
   
dispatch_async(myQU, ^{
       
NSLog(@"test");
    });
    dispatch_release(myQU);
像這樣追加Block到Dispatch Queue後直接釋放Dispatch Queue是沒問題的。因為在dispatch_async函式中把Block追加到Dispatch Queue後,該Dispatch Queue就會被Block所持有。在之後介紹的GCD的API中,當使用名稱中含有”create”的API生成物件時,在不需要其生成的物件釋放(iOS7和之後系統ARC環境下不需要)。
          第二種方法是獲取系統標準提供的Dispatch Queue。
名稱 種類 說明
Main Dispatch Queue Serial Dispatch Queue 主執行緒執行
Global Dispatch Queue Concurrent Dispatch Queue
有四個等級:High、Default、Low、Background。分別對應高、預設、低、後臺

獲取方法
名稱 獲取方法
Main Dispatch Queue
dispatch_queue_t mainQU=dispatch_get_main_queue();
Global Dispatch Queue
dispatch_queue_t globalQU=dispatch_get_global_queue(<#dispatch_queue_priority_t priority#>, 0); 第一個引數代表等級,分別有: #define DISPATCH_QUEUE_PRIORITY_HIGH 2 #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-
2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 第二個引數是保留引數,直接寫0吧。
通過系統獲得的這兩個Dispatch Queue可以不用考慮記憶體管理問題,比較輕鬆。 使用例項:
dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
       
/**
         * 
並行執行的處理
         */

       
//需要在主執行緒展處理的,例如介面更新
       
dispatch_async(dispatch_get_main_queue(), ^{
           
/**
             * 
主執行緒處理
             */

        });
    });

3.2.4 dispatch_set_target_queue

我們自己生成的Dispatch Queue都是使用與Global Dispatch Queue預設優先順序相同優先順序的執行緒,變更Dispatch Queue的優先順序要使用dispatch_set_target_queue函式。
    dispatch_queue_t mySerialQueue=dispatch_queue_create("test_serial", NULL);
   
dispatch_queue_t globalBackground=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    dispatch_set_target_queue(mySerialQueue, globalBackground);
第一個引數是需要更改優先順序的Dispatch Queue,將第二個的優先順序賦值給第一個,也就是說第二個引數就是目標Dispatch Queue,我們需要使用它的優先順序。 dispatch_set_target_queue 不僅可以改變 Dispatch Queue的優先順序,還可以作為Dispatch Queue的執行階層,如果將多個Serial Dispatch Queue使用函式指定目標為同一個Serial Dispatch Queue。那麼原本應該並行執行的Serial Dispatch Queue只能同時執行一個處理。
3.2.5 dispatch_after

dispatch_after在指定的時間之後將Block追加到Dispatch Queue。
函式: void  dispatch_after(dispatch_time_t when, dispatch_queue_t queue,dispatch_block_t block); 使用:     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{         NSLog(@“after");     });
dispatch_time_t  dispatch_time(dispatch_time_t when, int64_t delta); dispatch_time_t  dispatch_walltime(const struct timespec *when, int64_t delta); timespec結構體可以通過NSDate型別的資料生成。
dispatch_time通常用於計算相對時間,dispatch_walltime函式用來計算絕對時間。
3.2.6 Dispatch Group

如果想等待追加到Dispatch Queue中的多個處理結束後再執行某些結束處理,可以選擇使用Serial Dispatch Queue,也可以使用Dispatch Group 配合 Concurrent Dispatch Queue的方式。 使用例項:
    dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);     dispatch_group_t group=dispatch_group_create();     dispatch_group_async(group, globalQueue, ^{NSLog(@"0");});
   
dispatch_group_async(group, globalQueue, ^{NSLog(@"1");});
   
dispatch_group_async(group, globalQueue, ^{NSLog(@"2");});
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"done");});
在追加到Dispatch Queue中的全部處理結束後,dispatch_group_notify函式會將執行的Block追加到Dispatch Queue中。另外,Dispatch Group中也可以使用 dispatch_group_wait函式等待全部處理執行結束。dispatch_group_wait函式的第二個引數是等待的時間(超時),下面原始碼中選擇DISPATCH_TIME_FOREVER表示一直等待,直到group中的操作全部結束。假設你給dispatch_group_wait函式設定等待時間為1秒,一秒後返回0時表示成功,返回其他數值表示group的中的某個處理還在進行中。
    dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);     dispatch_group_t group=dispatch_group_create();     dispatch_group_async(group, globalQueue, ^{NSLog(@“0");});     dispatch_group_async(group, globalQueue, ^{NSLog(@"1");});     dispatch_group_async(group, globalQueue, ^{NSLog(@"2");});     dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

3.2.7 dispatch_barrier_async

本節參考蘋果官網有改動。        dispatch_barrier_async函式同dispatch_queue_create函式生成的Concurrent Dispatch Queue一起使用。一個比較合適的場景是對資料庫的操作,不應在同一時刻對資料庫進行多個寫操作,但是多個讀操作是允許的,此時的寫操作的實現是比較複雜的。 dispatch_barrier_async函式的作用就是會等待追加到 Concurrent Dispatch Queue處理全部結束後再將指定的處理追加到該 Concurrent Dispatch Queue。並不等待Block被呼叫函式就返回,等 dispatch_barrier_async函式追加到 Concurrent Dispatch Queue處理執行結束後, dispatch_barrier_async 追加到 Concurrent Dispatch Queue的處理又開始執行。比較適合資料庫和檔案訪問。
    dispatch_queue_t concurrentQU=dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);     dispatch_async(concurrentQU, ^{ NSLog(@"write data0");});     dispatch_async(concurrentQU, ^{ NSLog(@"write data1");});     dispatch_async(concurrentQU, ^{ NSLog(@"write data2"); });             dispatch_async(concurrentQU, ^{ NSLog(@"write data3");});     dispatch_barrier_async(concurrentQU, ^{         NSLog(@"read...");     });     dispatch_async(concurrentQU, ^{ NSLog(@"write data4); });     dispatch_async(concurrentQU, ^{ NSLog(@"write data5); });
執行結果是: write data1 write data0 write data2 write data3 read... write data4 write data5
官網說明:
dispatch_barrier_async

Submits a barrier block for asynchronous execution and returns immediately.

Declaration

Objective-C

void dispatch_barrier_async ( dispatch_queue_t queue, dispatch_block_t block );

Parameters
         queue           The dispatch queue on which to execute the barrier block. The queue is retained by the     system until the block has run to completion. This parameter cannot be NULL.
block

The barrier block to submit to the target dispatch queue. This block is copied and retained until it finishes executing, at which point it is released. This parameter cannot be NULL.

Discussion

Calls to this function always return immediately after the block has been submitted and never wait for the block to be invoked. When the barrier block reaches the front of a private concurrent queue, it is not executed immediately. Instead, the queue waits until its currently executing blocks finish executing. At that point, the barrier block executes by itself. Any blocks submitted after the barrier block are not executed until the barrier block completes.

The queue you specify should be a concurrent queue that you create yourself using the dispatch_queue_create function. If the queue you pass to this function is a serial queue or one of the global concurrent queues, this function behaves like the dispatch_async function.


與此相對,存在函式 dispatch_barrier_sync,此函式與dispatch_barrier_async函式的區別是它會等待它所提交的Block執行完畢才返回,也就是說會阻塞執行緒,等Block執行完畢後函式後面的任務才會被提交到程序。個人理解是,dispatch_barrier_async提交任務後函式之後的任務也被提交,但並未執行,等函式提交的任務執行後,在其之後提交的任務才執行,而dispatch_barrier_sync函式提交任務後知道任務執行結束才接著往下執行。dispatch_barrier_async使用率較高。 官網說明如下:
dispatch_barrier_sync

Submits a barrier block object for execution and waits until that block completes.

Declaration

Objective-C

void dispatch_barrier_sync ( dispatch_queue_t queue, dispatch_block_t block );

Parameters

queue

The dispatch queue on which to execute the barrier block. This parameter cannot be NULL.

block

The barrier block to be executed. This parameter cannot be NULL.

Discussion

Submits a barrier block to a dispatch queue for synchronous execution. Unlike dispatch_barrier_async, this function does not return until the barrier block has finished. Calling this function and targeting the current queue results in deadlock.

When the barrier block reaches the front of a private concurrent queue, it is not executed immediately. Instead, the queue waits until its currently executing blocks finish executing. At that point, the queue executes the barrier block by itself. Any blocks submitted after the barrier block are not executed until the barrier block completes.

The queue you specify should be a concurrent queue that you create yourself using the dispatch_queue_create function. If the queue you pass to this function is a serial queue or one of the global concurrent queues, this function behaves like the dispatch_sync function.

Unlike with dispatch_barrier_async, no retain is performed on the target queue. Because calls to this function are synchronous, it "borrows" the reference of the caller. Moreover, no Block_copy is performed on the block.

As an optimization, this function invokes the barrier block on the current thread when possible.




3.2.8 dispatch_sync
dispatch_async意味著“非同步”(asynchronous),與之相對應的就是同步(synchronous),即dispatch_sync函式,將指定的Block“同步“追加到指定的Dispatch Queue中,在追加Block結束之前,函式會一直等待。可以說 dispatch_sync是簡易版的 dispatch_group_wait函式。dispatch_sync容易引起死鎖,下面的程式碼在主執行緒中執行就會死鎖。
    dispatch_queue_t queue=dispatch_get_main_queue();     dispatch_sync(queue, ^{  NSLog(@"hello"); }); 該原始碼在主執行緒中執行指定的Block,並等待其執行結束,然而主執行緒正在執行原始碼,已經被阻塞,無法繼續執行任何原始碼。

3.2.9 dispach_apply

dispatch_apply函式和Dispatch Group關聯的API。該函式按指定的次數將指定的Block追加到某個Dispatch Queue中,並等待處理全部結束。
    dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   
dispatch_apply(5, queue, ^(size_t index) {
       
NSLog(@"%zu",index);
    });
    NSLog(@"done"); 執行結果:1 0 3 2 4 done
第一個引數為重複次數,第二個引數為追加物件的Dispatch Queue,第三個引數是追加的處理。第三個引數是有引數的Block,為了區分Block使用,例如要對NSArray類物件的所有元素執行處理時,不需要for迴圈。
3.2.10 dispatch_suspend/dispatch_resume

dispatch_suspand掛起指定的Dispatch Queue,dispatch_resume恢復指定的Dispatch Queue。
3.2.11 Dispatch Semaphore

Dispatch Semaphore是持有計數的訊號,該計數是多執行緒程式設計中計數型別訊號。
    dispatch_semaphore_t semaphore=dispatch_semaphore_create(1);//引數表示計數的初始值
   
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//此函式等待計數值等於或大於1,對該技術進行減法並返回,第二個引數是等待時間,若在等待時間內semaphore的計數滿足要求,則返回0,反之返回非0
    dispatch_semaphore_signal(semaphore);// 該函式使 semaphore 1
示例:
    dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   
//生成計數訊號(訊號量),初始值為1,保證同時訪問陣列的執行緒只有一個。
   
dispatch_semaphore_t sema=dispatch_semaphore_create(1);
   
NSMutableArray*array=[[NSMutableArray alloc]init];
   
for (int i=0; i<10000; i++) {
       
dispatch_async(queue, ^{
          
//等待計數訊號直到大於等於1
           
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
           
/**
             * 
由於第一個訪問的執行緒訪問時,訊號量等於1,滿足條件,wait函式返回,訊號量為0,其餘訪問的執行緒只能等待,能訪問array的執行緒始終只有一個
             */

            [array
addObject:[NSNumber numberWithInt:i]];
           
//排他控制結束,訊號量加1
           
dispatch_semaphore_signal(sema);
        });
    }

3.2.12 dispatch_once dispatch_once是保證在應用程式執行中只執行一次的API。
    static dispatch_once_t pred;     dispatch_once(&pred, ^{
       
/**
         * 
初始化程式碼
         */
    });
dispatch_once可保證此原始碼在多執行緒環境下的安全。
3.2.13 Dispatch I/O

Dispatch I/O和Dispatch Data 能實現讀取檔案時將檔案分成合適大小使用Global Dispatch Queue將一個檔案按某個大小讀取。如:
    dispatch_async(queue, ^{/*讀取0~8191位元組*/});
   
dispatch_async(queue, ^{/*讀取8192~16383位元組*/});
    dispatch_async(queue, ^{/* 讀取 16383~24575 位元組 */ });

蘋果官方使用示例:
pipe_q = dispatch_queue_create("PipeQ", NULL);
pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){
        close(fd);
});

*out_fd = fdpair[1];

dispatch_io_set_low_water(pipe_channel, SIZE_MAX);

dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){
        if (err == 0)
        {
                size_t len = dispatch_data_get_size(pipedata);
                if (len > 0)
                {
                        const char *bytes = NULL;
                        char *encoded;
                        dispatch_data_t md = dispatch_data_create_map(pipedata, (const void **)&bytes, &len);
                        encoded = asl_core_encode_buffer(bytes, len);
                        asl_set((aslmsg)merged_msg, ASL_KEY_AUX_DATA, encoded);
                        free(encoded);
                        _asl_send_message(NULL, merged_msg, -1, NULL);
                        asl_msg_release(merged_msg);
                        dispatch_release(md);
                }
}

if (done)
{
        dispatch_semaphore_signal(sem);
        dispatch_release(pipe_channel);
        dispatch_release(pipe_q);
}
});
dispatch_io_create函式建立了一個dispatch I/O。它指定了一個會在發生錯誤的時候被執行的block,以及執行block的佇列。dispatch_io_set_low_water函式指定了每次讀取的大小(資料會按這個分塊)。dispatch_io_read函式使用Global Dispatch Queue開始並列讀取。每當各個分割的檔案塊讀取結束時,將含有檔案塊資料的Dispatch Data傳遞給dispatch_io_read函式指定的讀取結束時回撥的Block,這個Block處理傳遞過來的Dispatch Data。
3.3 GCD實現 略