你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> iOS多線程中的dispatch_semaphore_t semaphore(dispatch組和信號量。)

iOS多線程中的dispatch_semaphore_t semaphore(dispatch組和信號量。)

編輯:IOS開發綜合

Windows平台下, 對線程的同步控制,可以有Critical Section,Mutex,Semaphore,Event 等方式.

在IOS平台,使用GCD進行簡單的多線程編程時,可以使用dispatch_semaphore_t進行相應的同步操作.

IOS平台上沒有對應的Event這個控制對像. 對於一些適合Event模式的情況下,可以通過dispatch_semaphore_t模 擬event(autoReset)的特性.


dispatch源(dispatch source)和RunLoop源概念上有些類似的地方,而且使用起來更簡單。要很好地理解dispatch源,其實把它看成一種特別的生產消費模式。dispatch源好比生產的數據,當有新數據時,會自動在dispatch指定的隊列(即消費隊列)上運行相應地block,生產和消費同步是dispatch源會自動管理的。

dispatch源的使用基本為以下步驟:

1. dispatch_source_t source = dispatch_source_create(dispatch_source_type, handler, mask, dispatch_queue); //創建dispatch源,這裡使用加法來合並dispatch源數據,最後一個參數是指定dispatch隊列

2. dispatch_source_set_event_handler(source, ^{ //設置響應dispatch源事件的block,在dispatch源指定的隊列上運行

  //可以通過dispatch_source_get_data(source)來得到dispatch源數據

});

3. dispatch_resume(source); //dispatch源創建後處於suspend狀態,所以需要啟動dispatch源

4. dispatch_source_merge_data(source, value); //合並dispatch源數據,在dispatch源的block中,dispatch_source_get_data(source)就會得到value。

是不是很簡單?而且完全不必編寫同步的代碼。比如網絡請求數據的模式,就可以這樣來寫:

dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_global_queue(0, 0));

dispatch_source_set_event_handler(source, ^{

dispatch_sync(dispatch_get_main_queue(), ^{

    //更新UI

});

});

dispatch_resume(source);

dispatch_async(dispatch_get_global_queue(0, 0), ^{

   //網絡請求

dispatch_source_merge_data(source, 1); //通知隊列

});

dispatch源還支持其它一些系統源,包括定時器、監控文件的讀寫、監控文件系統、監控信號或進程等,基本上調用的方式原理和上面相同,只是有可能是系統自動觸發事件。比如dispatch定時器:

dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), 10*NSEC_PER_SEC, 1*NSEC_PER_SEC); //每10秒觸發timer,誤差1秒

dispatch_source_set_event_handler(timer, ^{

  //定時處理

});

dispatch_resume(timer);

其它情況的dispatch源就不再一一舉例,可參看官網有具體文檔: https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/GCDWorkQueues/GCDWorkQueues.html#//apple_ref/doc/uid/TP40008091-CH103-SW1

最後,dispatch源的其它一些函數大致羅列如下:

uintptr_t dispatch_source_get_handle(dispatch_source_t source); //得到dispatch源創建,即調用dispatch_source_create的第二個參數

unsignedlong dispatch_source_get_mask(dispatch_source_t source); //得到dispatch源創建,即調用dispatch_source_create的第三個參數

void dispatch_source_cancel(dispatch_source_t source); //取消dispatch源的事件處理--即不再調用block。如果調用dispatch_suspend只是暫停dispatch源。

long dispatch_source_testcancel(dispatch_source_t source); //檢測是否dispatch源被取消,如果返回非0值則表明dispatch源已經被取消

void dispatch_source_set_cancel_handler(dispatch_source_t source, dispatch_block_t cancel_handler); //dispatch源取消時調用的block,一般用於關閉文件或socket等,釋放相關資源

void dispatch_source_set_registration_handler(dispatch_source_t source, dispatch_block_t registration_handler); //可用於設置dispatch源啟動時調用block,調用完成後即釋放這個block。也可在dispatch源運行當中隨時調用這個函數。


iOS多線程的初步研究(八)-- dispatch隊列


GCD編程的核心就是dispatch隊列,dispatch block的執行最終都會放進某個隊列中去進行,它類似NSOperationQueue但更復雜也更強大,並且可以嵌套使用。所以說,結合block實現的GCD,把函數閉包(Closure)的特性發揮得淋漓盡致。

dispatch隊列的生成可以有這幾種方式:

1. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.serial", DISPATCH_QUEUE_SERIAL); //生成一個串行隊列,隊列中的block按照先進先出(FIFO)的順序去執行,實際上為單線程執行。第一個參數是隊列的名稱,在調試程序時會非常有用,所有盡量不要重名了。

2. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.concurrent", DISPATCH_QUEUE_CONCURRENT); //生成一個並發執行隊列,block被分發到多個線程去執行

3. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //獲得程序進程缺省產生的並發隊列,可設定優先級來選擇高、中、低三個優先級隊列。由於是系統默認生成的,所以無法調用dispatch_resume()和dispatch_suspend()來控制執行繼續或中斷。需要注意的是,三個隊列不代表三個線程,可能會有更多的線程。並發隊列可以根據實際情況來自動產生合理的線程數,也可理解為dispatch隊列實現了一個線程池的管理,對於程序邏輯是透明的。

官網文檔解釋說共有三個並發隊列,但實際還有一個更低優先級的隊列,設置優先級為DISPATCH_QUEUE_PRIORITY_BACKGROUND。Xcode調試時可以觀察到正在使用的各個dispatch隊列。

4. dispatch_queue_t queue = dispatch_get_main_queue(); //獲得主線程的dispatch隊列,實際是一個串行隊列。同樣無法控制主線程dispatch隊列的執行繼續或中斷。

接下來我們可以使用dispatch_async或dispatch_sync函數來加載需要運行的block。

dispatch_async(queue, ^{

  //block具體代碼

}); //異步執行block,函數立即返回

dispatch_sync(queue, ^{

  //block具體代碼

}); //同步執行block,函數不返回,一直等到block執行完畢。編譯器會根據實際情況優化代碼,所以有時候你會發現block其實還在當前線程上執行,並沒用產生新線程。

實際編程經驗告訴我們,盡可能避免使用dispatch_sync,嵌套使用時還容易引起程序死鎖。

如果queue1是一個串行隊列的話,這段代碼立即產生死鎖:

dispatch_sync(queue1, ^{

dispatch_sync(queue1, ^{

    ......

  });

  ......

 });

不妨思考下,為什麼下面代碼也肯定死鎖:

dispatch_sync(dispatch_get_main_queue(), ^{

  ......

});

那實際運用中,一般可以用dispatch這樣來寫,常見的網絡請求數據多線程執行模型:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

  //子線程中開始網絡請求數據

  //更新數據模型

  dispatch_sync(dispatch_get_main_queue(), ^{

    //在主線程中更新UI代碼

  });

});

程序的後台運行和UI更新代碼緊湊,代碼邏輯一目了然。

dispatch隊列是線程安全的,可以利用串行隊列實現鎖的功能。比如多線程寫同一數據庫,需要保持寫入的順序和每次寫入的完整性,簡單地利用串行隊列即可實現:

dispatch_queue_t queue1 = dispatch_queue_create("com.dispatch.writedb", DISPATCH_QUEUE_SERIAL);

- (void)writeDB:(NSData *)data

{

  dispatch_async(queue1, ^{

    //write database

  });

}

下一次調用writeDB:必須等到上次調用完成後才能進行,保證writeDB:方法是線程安全的。

dispatch隊列還實現其它一些常用函數,包括:

void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t)); //重復執行block,需要注意的是這個方法是同步返回,也就是說等到所有block執行完畢才返回,如需異步返回則嵌套在dispatch_async中來使用。多個block的運行是否並發或串行執行也依賴queue的是否並發或串行。

void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block); //這個函數可以設置同步執行的block,它會等到在它加入隊列之前的block執行完畢後,才開始執行。在它之後加入隊列的block,則等到這個block執行完畢後才開始執行。

void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block); //同上,除了它是同步返回函數

void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block); //延遲執行block

最後再來看看dispatch隊列的一個很有特色的函數:

void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);

它會把需要執行的任務對象指定到不同的隊列中去處理,這個任務對象可以是dispatch隊列,也可以是dispatch源(以後博文會介紹)。而且這個過程可以是動態的,可以實現隊列的動態調度管理等等。比如說有兩個隊列dispatchA和dispatchB,這時把dispatchA指派到dispatchB:

dispatch_set_target_queue(dispatchA, dispatchB);

那麼dispatchA上還未運行的block會在dispatchB上運行。這時如果暫停dispatchA運行:

dispatch_suspend(dispatchA);

則只會暫停dispatchA上原來的block的執行,dispatchB的block則不受影響。而如果暫停dispatchB的運行,則會暫停dispatchA的運行。

這裡只簡單舉個例子,說明dispatch隊列運行的靈活性,在實際應用中你會逐步發掘出它的潛力。

dispatch隊列不支持cancel(取消),沒有實現dispatch_cancel()函數,不像NSOperationQueue,不得不說這是個小小的缺憾。

iOS多線程的初步研究(十)-- dispatch同步


GCD提供兩種方式支持dispatch隊列同步,即dispatch組和信號量。

一、dispatch組(dispatch group

1. 創建dispatch組

dispatch_group_t group = dispatch_group_create();

2. 啟動dispatch隊列中的block關聯到group中

dispatch_group_async(group, queue, ^{

  // 。。。

});

3. 等待group關聯的block執行完畢,也可以設置超時參數

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

4. 為group設置通知一個block,當group關聯的block執行完畢後,就調用這個block。類似dispatch_barrier_async。

dispatch_group_notify(group, queue, ^{

  // 。。。

});

5. 手動管理group關聯的block的運行狀態(或計數),進入和退出group次數必須匹配

dispatch_group_enter(group);

dispatch_group_leave(group);

所以下面的兩種調用其實是等價的,

A)

dispatch_group_async(group, queue, ^{

  // 。。。

});

B)

dispatch_group_enter(group);

dispatch_async(queue, ^{

  //。。。

  dispatch_group_leave(group);

});

所以,可以利用dispatch_group_enter、 dispatch_group_leave和dispatch_group_wait來實現同步,具體例子:http://stackoverflow.com/questions/10643797/wait-until-multiple-operations-executed-including-completion-block-afnetworki/10644282#10644282。

二、dispatch信號量(dispatch semaphore

1. 創建信號量,可以設置信號量的資源數。0表示沒有資源,調用dispatch_semaphore_wait會立即等待。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

2. 等待信號,可以設置超時參數。該函數返回0表示得到通知,非0表示超時。

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

3. 通知信號,如果等待線程被喚醒則返回非0,否則返回0。

dispatch_semaphore_signal(semaphore);

最後,還是回到生成消費者的例子,使用dispatch信號量是如何實現同步:

dispatch_semaphore_t sem = dispatch_semaphore_create(0);

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //消費者隊列

while (condition) {

    if (dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, 10*NSEC_PER_SEC))) //等待10秒

      continue;

    //得到數據

  }

});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //生產者隊列

while (condition) {

    if (!dispatch_semaphore_signal(sem))

    {

      sleep(1); //wait for a while

      continue;

    }

    //通知成功

  }

});


  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved