国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

iOS多線程編程Part3/3-GCD

2019-11-14 19:54:31
字體:
來源:轉載
供稿:網友

前兩部分介紹了NSThread、NSRunLoop和NSOperation,本文聊聊2011年WWDC時推出的神器GCD。GCD: Grand Central Dispatch,是一組用于實現并發編程的C接口。GCD是基于Objective-C的Block特性開發的,基本業務邏輯和NSOperation很像,都是將工作添加到一個隊列,由系統來負責線程的生成和調度。由于是直接使用Block,因此比NSOperation子類使用起來更方便,大大降低了多線程開發的門檻。另外,GCD是開源的喔:libdispatch

基本用法

首先示例:

1234
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{    [self doTask];    NSLog(@"Fisinished");});

GCD的調用接口非常簡單,就是將Job提交至Queue中,主要的提交Job接口為:

  • dispatch_sync(queue, block)同步提交job dispatch_async (queue, block) 異步提交job dispatch_after(time, queue, block) 同步延遲提交job 其中第一個參數類型是dispatch_queue_t,就是一個表示隊列的數據結構typedef struct dispatch_queue_s *dispatch_queue_t;;block就是表示任務的Blocktypedef void (^dispatch_block_t)( void);

dispatch_async函數是異步非阻塞的,調用后會立刻返回,工作由系統在線程池中分配線程去執行工作。 dispatch_sync和dispatch_after是阻塞式的,會一直等到添加的工作完成后才會返回。

除了添加Block到Dispatch Queue,還有添加函數到Dispatch Queue的接口,例如dispatch_async對應的有dispatch_async_f:

123
dispatch_async_f(dispatch_queue_t queue,               void *context,               dispatch_function_t work);

其中第三個參數就是個函數指針,即typedef void (*dispatch_function_t)(void *);;第二個參數是傳給這個函數的參數。

Dispatch Queue

要添加工作到隊列Dispatch Queue中,這個隊列可以是串行或者并行的,并行隊列會盡可能的并發執行其中的工作任務,而串行隊列每次只能運行一個工作任務。

目前GCD中有三種類型的Dispatch Queue:

  • Main Queue:關聯到主線程的隊列,可以使用函數dispatch_get_main_queue()獲得,加到這個隊列中的工作都會分發到主線程運行。主線程只有一個,因此很明顯這個是串行隊列,每次運行一個工作。
  • Global Queue:全局隊列是并發隊列,又根據優先級細分為高優先級、默認優先級和低優先級三種。通過dispatch_get_global_queue加上優先級參數獲得這個全局隊列,例如dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
  • 自定義Queue:自己創建一個隊列,通過函數dispatch_queue_create創建,例如dispatch_queue_create("com.kiloapp.test", NULL)。第一個參數是隊列的名字,Apple建議使用反DNS型的名字命名,防止重名;第二個參數是創建的queue的類型,iOS 4.3以前只支持串行,即DISPATCH_QUEUE_SERIAL(就是NULL),iOS4.3以后也開始支持并行隊列,即參數DISPATCH_QUEUE_CONCURRENT。

由于有這些種不同類型的隊列,一種常見的使用模式是:

123456
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{    [self doHardWorkInBackground];    dispatch_async(dispatch_get_main_queue(), ^{        [self updateUI];    });});

將一些耗時的工作添加到全局隊列,讓系統分配線程去做,工作完成后再次調用GCD的主線程隊列去完成UI相關的工作,這樣做就不會因為大量的非UI相關工作加重主線程負擔,從而加快UI事件響應。

其他幾個可能用到的接口有:

dispatch_get_current_queue()獲取當前隊列,一般在提交的Block中使用。在提交的Block之外調用時,如果在主線程中就返回主線程Queue;如果是在其他子線程,返回的是默認的并發隊列。

dispatch_queue_get_label(queue)獲取隊列的名字,如果你自己創建的隊列沒有設置名字,那就是返回NULL。

dispatch_set_target_queue(object, queue)設置給定對象的目標隊列。這是一個非常強大的接口,目標隊列負責處理這個GCD Object(參見下面的小節“管理GCD對象”),注意這個Object還可以是另一個隊列。例如我創建了了數個私有并發隊列,而將它們的目標隊列設置為一個串行的隊列,那么我添加到這些并發隊列的任務最終還是會被串行執行。

dispatch_main()會阻塞主線程等待主隊列Main Queue中的Block執行結束。

Dispatch Group

GCD確實非常簡單好用,不過有些場景下還是有點問題,例如:

12345
for(id obj in array){    [self doWorkOnItem:obj];}[self doWorkOnArray:array];

前半部分可以用GCD得到處理性能的提升:

123456
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);for(id obj in array)    dispatch_async(queue, ^{        [self doWorkOnItem:obj];    });[self doWorkOnArray:array];

問題是[self doWorkOnArray:array];原先是在全部數組各個成員的工作完成后才會執行的,現在由于dispatch_async是異步的,[self doWorkOnArray:array];很有可能在各個成員的工作完成前就開始運行,這明顯不符合原先的語義。如果將dispatch_async改成dispatch_sync可以解決問題,但是和原來的方法一樣沒有并行處理數組,使用GCD也就沒有意義了。

針對這種情況,GCD提供了Dispatch Group可以將一組工作集合在一起,等待這組工作完成后再繼續運行。dispatch_group_create函數可以用來創建這個Group:

123456789
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);dispatch_group_t group = dispatch_group_create();for(id obj in array)    dispatch_group_async(group, queue, ^{        [self doWorkOnItem:obj];    });dispatch_group_wait(group, DISPATCH_TIME_FOREVER);dispatch_release(group);[self doWorkOnArray:array];

方法是不是很簡單,將并發的工作用dispatch_group_async異步添加到一個Group和全局隊列中,dispatch_group_wait會等待這些工作完成后再返回,這樣你就可以再運行[self doWorkOnArray:array];

不過有點不好的是dispatch_group_wait會阻塞當前線程,如果當前是主線程豈不是不好,有更絕的dispatch_group_notify接口:

12345678910
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);dispatch_group_t group = dispatch_group_create();for(id obj in array)    dispatch_group_async(group, queue, ^{        [self doWorkOnItem:obj];    });dispatch_group_notify(group, queue, ^{    [self doWorkOnArray:array];});dispatch_release(group);

dispatch_group_notify函數可以將這個Group完成后的工作也同樣添加到隊列中(如果是需要更新UI,這個隊列也可以是主隊列),總之這樣做就完全不會阻塞當前線程了。

Dispatch Group還有兩個接口可以顯式的告知group要添加block操作: dispatch_group_enter(group)和dispatch_group_leave(group),這兩個接口的調用數必須平衡,否則group就無法知道是不是處理完所有的Block了。

Dispatch Apply

如果就是要同步的執行對數組元素的逐個操作,GCD也提供了一個簡便的dispatch_apply函數:

12345
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);dispatch_apply([array count], queue, ^(size_t index){    [self doWorkOnItem:obj:[array objectAtIndex:index]];});[self doWorkOnArray:array];

Dispatch Barrier

在使用dispatch_async異步提交時,是無法保證這些工作的執行順序的,如果需要某些工作在某個工作完成后再執行,那么可以使用Dispatch Barrier接口來實現,barrier也有同步提交dispatch_barrier_async(queue, block)和異步提交dispatch_barrier_sync(queue, block)兩種方式。例如:

12345
dispatch_async(queue, block1);dispatch_async(queue, block2);dispatch_barrier_async(queue, block3);dispatch_async(queue, block4);dispatch_async(queue, block5);

dispatch_barrier_async是異步的,調用后立刻返回,即使block3到了隊列首部,也不會立刻執行,而是等到block1和block2的并行執行完成后才會執行block3,完成后再會并行運行block4和block5。注意這里的queue應該是一個并行隊列,而且必須是dispatch_queue_create(label, attr)創建的自定義并行隊列,否則dispatch_barrier_async操作就失去了意義。

Dispatch Source

Run Loop有Input Source,GCD也同樣支持一系列事件監聽和處理,GCD有一組Dispatch Source接口可以監聽底層系統對象(例如文件描述符、網絡描述符、Mach Port、Unix信號、VFS文件系統的vnode等)的事件,可以設置這些事件的處理函數,如果事件發生時,Dispatch Source就可以將事件的處理方法提交到隊列中執行。

dispatch_source_t是Dispatch Source的數據結構,使用dispatch_source_create(type, handle, mask, queue)來創建,第一個參數是source的類型:

12345678910
#define DISPATCH_SOURCE_TYPE_DATA_ADD#define DISPATCH_SOURCE_TYPE_DATA_OR#define DISPATCH_SOURCE_TYPE_MACH_RECV#define DISPATCH_SOURCE_TYPE_MACH_SEND#define DISPATCH_SOURCE_TYPE_PROC#define DISPATCH_SOURCE_TYPE_READ#define DISPATCH_SOURCE_TYPE_SIGNAL#define DISPATCH_SOURCE_TYPE_TIMER#define DISPATCH_SOURCE_TYPE_VNODE#define DISPATCH_SOURCE_TYPE_WRITE

第二個參數handle和第三個參數mask與source的類型相關,有不同的含義,第四個參數是source綁定的queue,由于篇幅問題這些含義請參考《Grand Central Dispatch (GCD) Reference》。

dispatch_source_set_event_handler(source, handler)接口可以添加source的處理方法handler,這里的handler是一個block。如果是dispatch_source_set_event_handler_f(source, handler),這里的handler就是function。

dispatch_source_cancel(source)接口可以異步取消一個source,取消后上面設置dispatch_source_set_event_handler的evnet handler就不會再執行。取消一個source時,如果之前使用dispatch_source_set_cancel_handler(source, handler)設置了一個取消時的處理block,那么這個block就會在取消source的時候提交至source關聯的queue中去執行,可以用來清理資源。

dispatch_source_get_data(source)接口用于返回source需要處理的數據,根據當初創建source類型不同有不同的含義,而且這個接口必須在event handler中調用,否則返回結果可能未定義。

dispatch_source_get_handle(source)和dispatch_source_get_mask(source)接口分布用于獲取當初創建source時的兩個參數handle和mask。

dispatch_source_merge_data(source, value)接口用于將一個value值合并到souce中,這個source的類型必須是DISPATCH_SOURCE_TYPE_DATA_ADD或者DISPATCH_SOURCE_TYPE_DATA_OR。

下面舉個source的例子,使用dispatch_source_get_data和dispatch_source_merge_data,假如我們在處理上面那個數組時要在UI中顯示一個進度條:

1234567891011
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());dispatch_source_set_event_handler(source, ^{    [progressIndicator incrementBy:dispatch_source_get_data(source)];});dispatch_resume(source);dispatch_apply([array count], globalQueue, ^(size_t index) {    [self doWorkOnItem:obj:[array objectAtIndex:index]];    dispatch_source_merge_data(source, 1);});

注意dispatch source創建后是處于suspend狀態的,必須使用dispatch_resume來恢復,dispatch_apply中每處理一個數組元素會調用dispatch_source_merge_data加1,那么這個source的事件handler就可以通過dispatch_source_get_data拿到source的數據。

Dispatch Once

dispatch_once的意思是在App整個生命周期內運行并且只允許一次,類似于pthread庫中的pthread_once)。由于dispatch_once的調試非常困難,所以最好還是少用,單例應該是少數值得用的地方了。

傳統我們實現單例是這樣:

12345678910
+ (id)sharedManager{    static Manager *theManager = nil;    @synchronized([Manager class])    {        if(!theManager)            theManager = [[Manager alloc] init];    }    return theManager;}

這個的成本還是有點高,每次訪問都會有同步鎖,使用dispatch_once可以保證只運行一次初始化:

123456789
+ (id)sharedWhatever{    static dispatch_once_t pred;    static Manager *theManager = nil;    dispatch_once(&pred, ^{        theManager = [[Manager alloc] init];    });    return theManager;}

需要注意dispatch_once_t最好使用全局變量或者是static的,否則可能導致無法確定的行為。

Dispatch Semaphore

和其他多線程技術一樣,GCD也支持信號量,dispatch_semaphore_create(value)用于創建一個信號量類型dispatch_semaphore_t,參數是long類型,表示信號量的初始值;dispatch_semaphore_signal(semaphore)用于通知信號量(增加一個信號量);dispatch_semaphore_wait(semaphore, timeout)用于等待信號量(減少一個信號量),第二個參數是超時時間,如果返回值小于0,會按照先后順序等待其他信號量的通知。

管理GCD對象

所有GCD的對象同樣是有引用計數的,如果引用計數為0就被釋放,如果你不再需要所創建的GCD對象,就可以使用dispatch_release(object)將對象的引用計數減一;同樣可以使用dispatch_retain(object)將對象的引用計數加一。注意由于全局和主線程隊列對象都不需要去dispatch_release和dispatch_retain,即使調用了也沒有作用。

dispatch_suspend(queue)可以暫停一個GCD隊列的執行,當然由于是block粒度的,如果調用dispatch_suspend時正好有隊列中block正在執行,那么這些運行的block結束后不會有其他的block再被執行;同理dispatch_resume(queue)可以恢復一個GCD隊列的運行。注意dispatch_suspend的調用數目需要和dispatch_resume數目保持平衡,因為dispatch_suspend是計數的,兩次調用dispatch_suspend會設置隊列的暫停數為2,必須再調用兩次dispatch_resume才能讓隊列重新開始執行block。

可以使用dispatch_set_context(object, context)給一個GCD對象設置一個關聯的數據,第二個參數任何一個內存地址;dispatch_set_context(object)就是獲得這個關聯數據,這樣可以方便傳遞各類上下文數據。

本小節提到的GCD對象(Dispatch Object)不單指隊列dispatch_queue_t,是指在GCD中出現的各種類型,聲明類型dispatch_object_t是個union:

1234567891011121314
typedef union {   struct dispatch_object_s *_do;   struct dispatch_continuation_s *_dc;   struct dispatch_queue_s *_dq;   struct dispatch_queue_attr_s *_dqa;   struct dispatch_group_s *_dg;   struct dispatch_source_s *_ds;   struct dispatch_source_attr_s *_dsa;   struct dispatch_semaphore_s *_dsema;   struct dispatch_data_s *_ddata;   struct dispatch_io_s *_dchannel;   struct dispatch_operation_s *_doperation;   struct dispatch_fld_s *_dfld;} dispatch_object_t 

Dispatch Data 對象

GCD是基于C的接口,其內部處理數據是無法直接使用Objective-C的數據類型,如果要使用數據buffer時需要自己malloc一塊內存空間來用,因此GCD提供了類似Objective-C中NSData的dispatch_data_t數據結構作為數據buffer。

dispatch_data_t的類型dispatch_data_s的指針,使用dispatch_data_create(buffer, size, queue, destructor)可以創建一個dispatch_data_t,第一個參數是保存數據的內存地址,第二個參數size是數據字節大小,第三個參數queue提交destructor block的隊列,第四個參數destructor是用于釋放data的block,默認是DISPATCH_DATA_DESTRUCTOR_DEFAULT和DISPATCH_DATA_DESTRUCTOR_FREE,后者在buffer是使用malloc生成的緩沖區時使用。示例:

12
void *buffer = malloc(length);dispatch_data_t data = dispatch_data_create(buffer, length, NULL, DISPATCH_DATA_DESTRUCTOR_FREE);

如果是從NSData轉換為dispatch_data_t:

12345
nsdata = [nsdata copy];dispatch_queue_t queue = dispatch_get_global_queue(0, 0);  return dispatch_data_create([nsdata bytes], [nsdata length], queue, ^{      [nsdata release];  });

與直接使用己malloc分配的連續內存空間不同,dispatch_data_t可以直接將兩塊數據用dispatch_data_create_concat(dataA, dataB)拼接起來,還可以用dispatch_data_create_subrange(data, offset, length)獲取部分dispatch_data_t。

如果反過來要訪問一個dispatch_data_t對應的內存空間,就需要使用dispatch_data_create_map(data, buffer_ptr, size_ptr)接口,示例:

123456789
const void *buffer;size_t length;dispatch_data_t tmpData = dispatch_data_create_map(data, &buffer, &length);//可以得到dispatch_data_t的內存空間地址和字節大小//這里我們可以直接使用buffer指針對應的內存//返回的tmpData是一個新的對應data連續內存空間的dispatch_data_tdispatch_release(tmpData);

Dispatch I/O Channel

GCD提供的這組Dispatch I/O Channel接口用于異步處理基于文件和網絡描述符的操作,可以用于文件和網絡I/O操作。

Dispatch IO Channel對象dispatch_io_t就是對一個文件或網絡描述符的封裝,使用dispatch_io_t dispatch_io_create(type, fd, queue, cleanup_hander)接口生成一個dispatch_io_t對象。第一個參數type表示channel的類型,有DISPATCH_IO_STREAM和DISPATCH_IO_RANDOM兩種,分布表示流讀寫和隨機讀寫;第二個參數fd是要操作的文件描述符;第三個參數queue是cleanup_hander提交需要的隊列;第四個參數cleanup_hander是在系統釋放該文件描述符時的回調。示例:

12345
dispatch_io_t fileChannel = dispatch_io_create(DISPATCH_IO_STREAM, STDIN_FILENO, dispatch_get_global_queue(0, 0), ^(int error) {        if(error)            fprintf(stderr, "error from stdin: %d (%s)/n", error, strerror(error));    });

dispatch_io_close(channel, flag)可以將生成的channel關閉,第二個參數是關閉的選項,如果使用DISPATCH_IO_STOP (0x01)就會立刻中斷當前channel的讀寫操作,關閉channel。如果使用的是0,那么會在正常讀寫結束后才會關閉channel。

During a read or write operation, the channel uses the high- and low-water mark values to determine how often to enqueue the associated handler block. It enqueues the block when the number of bytes read or written is between these two values.

在channel的讀寫操作中,channel會使用low_water和high_water值來決定讀寫了多大數據才會提交相應的數據處理block,可以dispatch_io_set_low_water(channel, low_water)和dispatch_io_set_high_water(channel, high_water)設置這兩個值。

Channel的異步讀寫操作使用接口dispatch_io_read(channel, offset, length, queue, io_handler)和dispatch_io_write(channel, offset, data, queue, io_handler)。dispatch_io_read接口參數分布表示channel,偏移量,字節大小,提交IO處理block的隊列,IO處理block;dispatch_io_write接口參數分別表示channel,偏移量,數據(dispatch_data_t),提交IO處理block的隊列,IO處理block。其中io_handler的定義為^(bool done, dispatch_data_t data, int error)()

舉個例子,將STDIN讀到的數據寫到STDERR:

123456
dispatch_io_read(stdinChannel, 0, SIZE_MAX, dispatch_get_global_queue(0, 0), ^(bool done, dispatch_data_t data, int error) {       if(data)       {           dispatch_io_write(stderrChannel, 0, data, dispatch_get_global_queue(0, 0), ^(bool done, dispatch_data_t data, int error) {});       }});

看起來使用上還挺麻煩的,需要創建Channel才能進行讀寫,因此GCD直接提供了兩個方便異步讀寫文件描述符的接口(參數含義和channel IO的類似):

123456789101112
void dispatch_read(   dispatch_fd_t fd,   size_t length,   dispatch_queue_t queue,   void (^handler)(dispatch_data_t data, int error));void dispatch_write(   dispatch_fd_t fd,   dispatch_data_t data,   dispatch_queue_t queue,   void (^handler)(dispatch_data_t data, int error));

總結

GCD的API按功能分為:

  • 創建管理Queue
  • 提交Job
  • Dispatch Group
  • 管理Dispatch Object
  • 信號量Semaphore
  • 隊列屏障Barrier
  • Dispatch Source
  • Queue Context數據
  • Dispatch I/O Channel
  • Dispatch Data 對象

各組接口的詳細說明還是參考《Grand Central Dispatch (GCD) Reference》。

參考資料

Grand Central Dispatch (GCD) Reference

Blocks Programming Topics


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 鄂州市| 广西| 萨迦县| 陇南市| 汝州市| 天柱县| 莱西市| 建德市| 陵川县| 宁武县| 那坡县| 榆树市| 临邑县| 竹溪县| 鄢陵县| 梁平县| 福鼎市| 全椒县| 中超| 六枝特区| 包头市| 贵阳市| 南汇区| 黄陵县| 惠安县| 平安县| 舒城县| 岚皋县| 米泉市| 清水河县| 连云港市| 云梦县| 佛山市| 金川县| 儋州市| 沾化县| 南乐县| 陕西省| 安龙县| 宁明县| 安龙县|