GCD Basic

Dispatch queues

Dispatch queue有兩種類型,一種是線性queue,線性queue一個(gè)一個(gè)的執(zhí)行queue上的任務(wù),如果當(dāng)前任務(wù)正在執(zhí)行,queue一直等待在那里直它完成才會(huì)開始新的任務(wù);另一種是并發(fā)queue,它并不等待正在執(zhí)行的任務(wù)的完成,只要有資源讓它開啟任務(wù),它就會(huì)分配queue上任務(wù)的執(zhí)行。

提交到線性queue上的Blocks是按先進(jìn)先出(FIFO)的順序執(zhí)行的。但要注意的是,不同線性queue上的Block的執(zhí)行是相互獨(dú)立的,所以互相之間是可能并發(fā)執(zhí)行的。提交到并發(fā)queue上的Block也是按FIFO的順序出隊(duì)列的,因?yàn)椴l(fā)queue并不等待任務(wù)的完成,所以只要有資源可運(yùn)行并發(fā)queue就會(huì)以FIFO的順序開啟queue上的block的執(zhí)行,block之間可能是并發(fā)的。

當(dāng)你把任務(wù)提交到并發(fā)queue上,可并發(fā)執(zhí)行的任務(wù)數(shù)是由系統(tǒng)的當(dāng)前狀態(tài)決定的。iOS或MacOS根據(jù)當(dāng)前的狀態(tài)(比如并發(fā)queue上的任務(wù)數(shù)、CPU核數(shù)和CPU的使用情況)來決定并發(fā)數(shù)。
如果你的任務(wù)應(yīng)該有序地執(zhí)行,或者不應(yīng)該并發(fā)的執(zhí)行,那么你應(yīng)該用線性queue。

獲取 Dispatch Queues

獲取Dispatch queue有兩種方法

  1. dispatch_queue_create
  2. dispatch_get_main_queue/dispatch_get_global_queue

dispatch_queue_create

用此方法創(chuàng)建新的dispatch queue。

dispatch_queue_t serialDispatchQueue = dispatch_queue_create("myUniqueIdentity", NULL);

上面你創(chuàng)建了一個(gè)新的線性queue。 dispatch_queue_create, 有兩個(gè)參數(shù):

參數(shù) 描述
label label 唯一標(biāo)識(shí)你創(chuàng)建的queue,label在debug時(shí)(Instruments, sample, stackshots, crash reports)會(huì)有用.因?yàn)橄到y(tǒng)庫(kù)或其它框架也會(huì)創(chuàng)建queue,為了保證label的唯一,請(qǐng)DNS的反轉(zhuǎn)格式(com.example.myqueue)。這個(gè)參數(shù)是可選的,可以為NULL
attr OS X v10.7 , iOS 4.3及其之后版本, DISPATCH_QUEUE_SERIAL (或者 NULL) 來創(chuàng)建線性queue;DISPATCH_QUEUE_CONCURRENT 來創(chuàng)建并發(fā)queue. 更早版本, 此參數(shù)只能為NULL.

所以你可以如下所示來創(chuàng)建并發(fā)queue:

dispatch_queue_t concurrentQueue = dispatch_queue_create("myAnotherUniqueIdentify", DISPATCH_QUEUE_CONCURRENT);

另外,當(dāng)一個(gè)線性queue被創(chuàng)建且有任務(wù)提交上去,系統(tǒng)會(huì)為每個(gè)線性queue創(chuàng)建一個(gè)線程。如果你創(chuàng)建了2000個(gè)線性queue,就會(huì)有相應(yīng)的2000個(gè)線程被創(chuàng)建。太多的線程會(huì)增加內(nèi)存的消耗,以及線程間的切換都會(huì)大大降低系統(tǒng)性能。所以不要濫用線性queue,只有當(dāng)任務(wù)之間的執(zhí)行是有序的或者為了避免多線程并發(fā)而引起的資源競(jìng)爭(zhēng)時(shí),才使用線性queue。而且線性queue的個(gè)數(shù)應(yīng)該和你需求的一至,不要?jiǎng)?chuàng)建多余的線性queue。 如是任務(wù)沒有數(shù)據(jù)一至性問題,且可以并發(fā),請(qǐng)用并發(fā)queue,而不是用多個(gè)線性queue來并發(fā)。 因?yàn)椴l(fā)queue使用的線程是由系統(tǒng)內(nèi)核非常有效地管理的,用并發(fā)queue更高效。

雖然,編譯器有強(qiáng)大的內(nèi)存自動(dòng)管理(ARC),但是GCD中創(chuàng)建的實(shí)例(這里不是Objective-C對(duì)象,而是GCD中一些結(jié)構(gòu)體實(shí)例)須由我們自己手動(dòng)釋放,因?yàn)檫@些實(shí)例(如dispatch queue)不像Block那樣被當(dāng)成Objective-C對(duì)象。當(dāng)你使用完這些實(shí)例時(shí),應(yīng)用dispatch_release釋放它們,用dispatch_retain來?yè)碛兴鼈儭?GCD函數(shù)中含有"create"的,往往都需要我們dispatch_release

dispatch_release(mySerialDispatchQueue);
dispatch_retain(myConcurrentDispatchQueue);

那么,看下面代碼:

dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create( "com.example.gcd.MyConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(myConcurrentDispatchQueue, ^{NSLog(@"block on myConcurrentDispatchQueue");});
dispatch_release(myConcurrentDispatchQueue);

上例中,往并發(fā)queue提交了任務(wù)之后,馬上就釋放它。這樣做安全嗎?

這樣沒任何問題。當(dāng)一個(gè)Block被提交到一個(gè)queue上時(shí),這個(gè)Block會(huì)擁有這個(gè)queue(dispatch_retain, 即,使queue引用記數(shù)加1)。當(dāng)Block執(zhí)行結(jié)束時(shí)它會(huì)釋放對(duì)queue的擁有權(quán)(dispatch_release, 即,使queue引用記數(shù)減1)。所以,即使提交block之后立馬釋放queue,queue也不會(huì)被系統(tǒng)回收,而Block也可以被執(zhí)行。當(dāng)Block執(zhí)行完并釋放queue的擁有權(quán)時(shí),queue才真正被回收。

Main Dispatch Queue / Global Dispatch Queue

Main Dispatch Queue / Global Dispatch Queue 由系統(tǒng)提供。
Main dispatch queue, 由名字也可以知道, 它將任務(wù)分配到主線程上執(zhí)行。Main dispatch queue是線性queue,且總共只有一個(gè)。因?yàn)镸ain dispatch queue在主線程上執(zhí)行任務(wù),所以你應(yīng)該把那些必須由主線程執(zhí)行的任務(wù)(如UI更新)提交到此queue上。performSelectorOnMainThread也有相似的功能。

如果不是有特殊需求,一般而言,你不需需用dispatch_queue_create創(chuàng)建自己的并發(fā)queue,系統(tǒng)提供的全局queue(gloabl queue)足已。 Global queue有四種分別對(duì)應(yīng)四個(gè)不同優(yōu)先級(jí): high, default, low, and background.

Types of dispatch queues

Name Type Description
Main dispatch queue Serial dispatch queue Executed on the main thread
Global dispatch queue (High priority) Concurrent dispatch queue Priority: High (Utmost priority)
Global dispatch queue (Default priority) Concurrent dispatch queue Priority: Normal
Global dispatch queue (Low priority) Concurrent dispatch queue Priority: Low
Global dispatch queue (Background priority) Concurrent dispatch queue Priority: Background

下面展示了不同queue的獲取方法:

/*
* How to get the main dispatch queue */
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
/*
* How to get a global dispatch queue of high priority 
*/
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
/*
* How to get a global dispatch queue of default priority 
*/
dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*
* How to get a global dispatch queue of low priority 
*/
dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
/*
* How to get a global dispatch queue of background priority 
*/
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

隨帶提一下, 如果你對(duì)系統(tǒng)提供的queue,進(jìn)行 dispatch_retain 或者 dispatch_release, 什么都不會(huì)發(fā)生,并不會(huì)增加或減少queue的引用計(jì)數(shù). 毫無疑問,使用系統(tǒng)提供的queue,比你自己創(chuàng)建queue理方便。
使用dispatch queue例子:

  /*
  * Execute a Block on a global dispatch queue of default priority. 
  */
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    /*
    * some tasks here to be executed concurrently 
    */
    /*
    * Then, execute a Block on the main dispatch queue 
    */
    dispatch_async(dispatch_get_main_queue(), ^{
      /*
      * Here, some tasks that can work only on the main thread. 
      */
    }); 
  });

Controlling Dispatch Queues

GCD提供了大量有用的API讓我們管理dispatch queue上的任務(wù)。
讓我們一個(gè)一個(gè)查看這些API有多么強(qiáng)大!

dispatch_set_target_queue

void dispatch_after( dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);

函數(shù)dispatch_set_target_queue可以給你的queue設(shè)置一個(gè)target queue。這主要用在給你新建的queue指定優(yōu)先級(jí)。無論你用dispatch_queue_create創(chuàng)建的是線性queue(serial queue)還是并發(fā)queue(concurrent queue),它的優(yōu)先級(jí)都與global queue的默認(rèn)優(yōu)先級(jí)相同。在創(chuàng)建queue之后,你就可以用這個(gè)函數(shù)來改變它的優(yōu)先級(jí)。下面代碼展示了如何讓一個(gè)線性queue擁有background優(yōu)先級(jí)。

dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);

dispatch_set_target_queue的第一個(gè)參數(shù)是你要設(shè)置的queue,第二個(gè)參數(shù)是target queue,這兩個(gè)參數(shù)都不能為NULL。它使mySerialDispatchQueue的優(yōu)先級(jí)和target queue(globalDispatchQueueBackground)一樣。不要任何系統(tǒng)的queue(main queue和global queue)作為第一個(gè)參數(shù)傳進(jìn)去,這樣做的結(jié)果是不可預(yù)料的!
dispatch_set_target_queue不僅可以用來改變queue的優(yōu)先級(jí),還可以創(chuàng)建queue 的層級(jí)關(guān)系。如果你把一個(gè)任務(wù)(block)提交到一個(gè)線性queue(A)上,同時(shí)這個(gè)線性queue的target queue是另一個(gè)線性queue(B)。那么這個(gè)任務(wù)(block)將不會(huì)和提交到B或其它以B為target queue的queue上的block ,并發(fā)執(zhí)行。

Queue 的層級(jí)

如圖所示,底下三個(gè)dispatch queue上的任務(wù)(blocks)將會(huì)線性執(zhí)行。當(dāng)你的各個(gè)任務(wù)不應(yīng)該并發(fā)執(zhí)行,同時(shí)又必須放在不同的queue上時(shí);你就可以通過設(shè)置這些queue的target都為某個(gè)線性queue,來阻止并發(fā)。實(shí)際上,我自己一直想不出有類似需求的場(chǎng)景。

dispatch_after

void dispatch_after( dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);

dispatch_after來設(shè)置queue上某個(gè)任務(wù)(block)的啟動(dòng)時(shí)間。下面代碼做的是,延遲三秒后將一個(gè)block添加到main queue上:

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC); 
dispatch_after(time, dispatch_get_main_queue(), ^{
  NSLog(@"waited at least three seconds."); 
});

ull是C語(yǔ)言中指定unsigned long long類型的。注意的是,此函數(shù)并不是在指定時(shí)間之后執(zhí)行block,而是在指定時(shí)間之后將block添加到目標(biāo)queue中。你可以把該函數(shù)看作是增強(qiáng)版的dispatch_async,當(dāng)指定延遲時(shí)間是DISPATCH_TIME_NOW時(shí),此函數(shù)功能上與dispatch_async等價(jià),不過蘋果官方文檔建議你應(yīng)該用dispatch_async,而延遲時(shí)間為DISPATCH_TIME_FOREVER時(shí),結(jié)果是不可預(yù)料的:

Passing DISPATCH_TIME_NOW as the when parameter is supported, but is not as optimal as calling dispatch_async instead. Passing DISPATCH_TIME_FOREVER is undefined.

dispatch_after并不適合執(zhí)行高精度要求的延時(shí)任務(wù),它對(duì)于那些精度不是那么高的延時(shí)任務(wù)是非常有用的。函數(shù)的三個(gè)參數(shù)中, 這里只講第一個(gè)參數(shù)time吧。首先你要用dispatch_timedispatch_walltime來創(chuàng)建time。此函數(shù)接收納秒數(shù),所以你應(yīng)該用NSEC_PER_SEC(一秒的納秒數(shù))或NSEC_PER_MSEC, 類似的常量如:

#define NSEC_PER_SEC 1000000000ull //一秒的納秒數(shù)
#define USEC_PER_SEC 1000000ull //一秒的微秒數(shù)
#define NSEC_PER_USEC 1000ull //一微秒的納秒數(shù)
#define NSEC_PER_MSEC 1000000ull //一毫秒的納秒數(shù)

要延遲一秒,你可以這樣創(chuàng)建:

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);

要延遲150毫秒,你可以這樣:

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 150ull * NSEC_PER_MSEC);

dispatch_time主要用來創(chuàng)建相對(duì)時(shí)間,而dispatch_walltime則用來創(chuàng)建絕對(duì)時(shí)刻。比如,你用dispatch_walltime來創(chuàng)建2016年11月11號(hào)11點(diǎn)11分11秒時(shí)刻。可以用dispatch_walltime來實(shí)現(xiàn)鬧鐘,不過此方法精度不高。dispatch_walltime根據(jù)結(jié)構(gòu)體timespec來創(chuàng)建時(shí)刻的,如下面例子:

dispatch_time_t getDispatchTimeByDate(NSDate *date) {
  NSTimeInterval interval;
  double second, subsecond;
  struct timespec time;
  dispatch_time_t milestone;
  
  interval = [date timeIntervalSince1970];
  subsecond = modf(interval, &second);
  time.tv_sec = second;
  time.tv_nsec = subsecond * NSEC_PER_SEC;
  milestone = dispatch_walltime(&time, 0);
  
  return milestone;
}

Dispatch Group

Dispatch group 用來對(duì)block分組。當(dāng)你有個(gè)任務(wù),并且些任務(wù)要求其它任務(wù)都完成時(shí)才能開始,這時(shí)你就可以和Dispatch Group來實(shí)現(xiàn)。當(dāng)然簡(jiǎn)單的情況下,你可以把任務(wù)都放進(jìn)一個(gè)線性queue中,在queue尾放入那個(gè)任務(wù)。但是如果遇到并發(fā)隊(duì)列或有多個(gè)隊(duì)列情況時(shí),就變得很復(fù)雜,dispatch group應(yīng)此而生。下因代碼例子將三個(gè)block分到一個(gè)組,并在這些block都被執(zhí)行之后,執(zhí)行main queue上的block:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{NSLog(@"blok0");});
dispatch_group_async(group, queue, ^{NSLog(@"blok1");});
dispatch_group_async(group, queue, ^{NSLog(@"blok2");});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"done")});
dispatch_release(group);

結(jié)果將是(前三個(gè)輸出順序不定):
blk1
blk2
blk0
done
首先我們用dispatch_group_create創(chuàng)建dispatch_group_t實(shí)例。因?yàn)楹瘮?shù)中有"create",當(dāng)不再需要它時(shí)你必須release它, GCD中所有的release都用dispatch_release。函數(shù)dispatch_group_async就像dispatch_async把block添加到隊(duì)列中,唯一的區(qū)別是多了一個(gè)參數(shù)dispatch_group_t,將block和dispatch group關(guān)聯(lián)起來。當(dāng)block和某個(gè)dispatch group關(guān)聯(lián)之后,block會(huì)擁有這個(gè)dispatch group,(dispatch_retain,就如同block被添加到queue中時(shí)會(huì)retain這個(gè)queue一樣),當(dāng)block執(zhí)行完時(shí),block會(huì)釋放它擁有的dispatch group(dispatch_release). 當(dāng)所有與dispatch group相關(guān)聯(lián)的block執(zhí)行完時(shí),dispatch_group_notify會(huì)將block("done")添加到main queue上。dispatch_group_notify并不會(huì)阻塞當(dāng)前線程,它監(jiān)聽指定的dispach group,當(dāng)該group上所有block都執(zhí)行時(shí),它將block添加到指定queue上。如果你要等待group上的所有block完成,你可以使用dispatch_group_wait,如下面例子:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{NSLog(@"blk0");});
dispatch_group_async(group, queue, ^{NSLog(@"blk1");});
dispatch_group_async(group, queue, ^{NSLog(@"blk2");});

dispatch_group_wait(group, GISPATCH_TIME_FOREVER);
dispatch_release(group);

dispatch_group_wait的第二個(gè)參數(shù)是timeout(dispatch_time_t)指定等待的時(shí)長(zhǎng), 如果在timeout時(shí)長(zhǎng)內(nèi)group上的任務(wù)都完成則返回0(success), 否則返回非零(failed)。timeout參數(shù)也像dispatch_after那樣:

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
if (result == 0) {
  //所有與group相關(guān)聯(lián)的任務(wù)都已完成
} else {
  //與group相關(guān)聯(lián)的任務(wù)中還有在運(yùn)行
}

當(dāng)dispatch_group_wait被調(diào)用時(shí),并不會(huì)從該函數(shù)立即返回,當(dāng)前線程阻塞在此函數(shù)上,等待group上所有任務(wù)完成,或者timeout。

你可以用DISPATCH_TIME_NOW來查看group上的任務(wù)是否都已完成:

long result = dispatch_group_wait(group, DISPATCH_TIME_NOW)
if (result == 0) {
  //此時(shí)group上任務(wù)都已完成
} else {
  //此時(shí)group上還有任務(wù)在運(yùn)行
}

在關(guān)聯(lián)block和group時(shí),dispatch_group_async并不是唯一的函數(shù),還有dispatch_group_enter(與之成對(duì)的是dispatch_group_leave)。先看例子:

dispatch_group_t group1 = dispatch_group_create();
dispatch_group_t group2 = dispatch_group_create();

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t mainQueue= dispatch_get_main_queue();

dispatch_async(queue, ^{
  dispatch_group_enter(group1);
  NSLog("group1 queue blk0");
  dispatch_group_leave(group1);
});

dispatch_async(queue, ^{
  dispatch_group_enter(group1);
  dispatch_group_enter(group2);
  NSLog("group1 group2 queue blk1");
  dispatch_group_leave(group1);
  dispatch_group_leave(group2);
});

dispatch_async(mainQueue, ^{
  dispatch_group_enter(group2);
  NSLog("group2 mainQueue, blk2");
  dispatch_group_leave(group2);
});

dispatch_async(mainQueue, ^{
  dispatch_group_enter(group1);
  dispatch_group_enter(group2);
  NSLog("group1 group2 mainQueue blk3");
  dispatch_group_leave(group1);
  dispatch_group_leave(group2);
});

/*
* 監(jiān)測(cè)group1。當(dāng)blk0,blk1,blk3都完成時(shí),添加block到queue
*/
dispatch_group_notify(group1, queue, ^{
  NSLog("blk0, blk1, blk3, all have finished!");
});

/*
*  監(jiān)測(cè)group2, 當(dāng)blk1, blk2, blk3 都完成時(shí)添加block到mainQueue
*/
dispatch_group_notify(group2, mainQueue, ^{
  NSLog("blk1, blk2, blk3, all have finished!");
});

注意dispatch_group_enterdispatch_group_leave必須保持平衡(成對(duì)出現(xiàn))。如果你需要把一個(gè)block關(guān)聯(lián)到不同的group上,你只能使用dispatch_group_enter函數(shù)對(duì),否則使用dispatch_group_async更方便一點(diǎn)。

下面看代碼是AFNetWorking中的例子:

- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                              failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
    // completionBlock is manually nilled out in AFURLConnectionOperation to break the retain cycle.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"
    self.completionBlock = ^{
        if (self.completionGroup) {
            dispatch_group_enter(self.completionGroup);
        }

        dispatch_async(http_request_operation_processing_queue(), ^{
            if (self.error) {
                if (failure) {
                    dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                        failure(self, self.error);
                    });
                }
            } else {
                id responseObject = self.responseObject;
                if (self.error) {
                    if (failure) {
                        dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                            failure(self, self.error);
                        });
                    }
                } else {
                    if (success) {
                        dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                            success(self, responseObject);
                        });
                    }
                }
            }

            if (self.completionGroup) {
                dispatch_group_leave(self.completionGroup);
            }
        });
    };
#pragma clang diagnostic pop
}

dispatch_barrier_async

void dispatch_barrier_async( dispatch_queue_t queue, dispatch_block_t block);

函數(shù)只能兩個(gè)參數(shù),一個(gè)是queue,一個(gè)block。作用是把block加到queue上,特殊的地方在于,被加queue上的block稱為barrier block。barrier block 把queue上的任務(wù)的執(zhí)行分成三個(gè)階段:

  1. 將先于barrier block前提交到queue上的所有block執(zhí)行完
  2. 執(zhí)行barrier block
  3. 只有當(dāng)barrier block執(zhí)行好后,才會(huì)執(zhí)行后于barrier block添加到queue上的block

這里的參數(shù)queue,應(yīng)該是你通過dispatch_queue_create 創(chuàng)建的并發(fā)queue,如果你傳入的是線性queue或者全局并發(fā)queue,函數(shù)作用就和dispatch_queue_async一樣。
來個(gè)例子,具體看看:

dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.moning", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, blk0_brush_tooth);
dispatch_async(queue, blk0_wash_face);
dispatch_async(queue, blk0_wash_body);
dispatch_async(queue, blk1_eat_breakfast);
dispatch_async(queue, blk2_goout_by_bike);
dispatch_async(queue, blk2_enjoy_music);

我們要實(shí)現(xiàn)這樣的功能,在洗澡的時(shí)候同時(shí)洗臉?biāo)⒀老茨槨H缓笞鐾晟鲜鲞@些事之后(blk0),吃早飯(blk1),吃完早飯騎自行車出去(邊騎邊聽音樂).很明顯,上述代碼并不符合需求,它將所有事件都并發(fā)了。
有了dispatch_barrier_async我們可以這樣做:

dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.moning", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, blk0_brush_tooth);
dispatch_async(queue, blk0_wash_face);
dispatch_barrier_async(queue, blk0_wash_body);
dispatch_async(queue, blk1_eat_breakfast);
dispatch_async(queue, blk2_goout_by_bike);
dispatch_async(queue, blk2_enjoy_music);

對(duì)數(shù)據(jù)庫(kù)或文件進(jìn)行寫操作時(shí),我們也可以用此函數(shù)來完成:

// read operation added to queue before barrier.(May added on different thread)
dispatch_async(queue, blk0_read1);
dispatch_async(queue, blk0_read2);
dispatch_async(queue, blk0_read3);
...

dispatch_barrier_async(queue, blk1_write);

// read operation added to queue after barrier.(May added on different thread)
dispatch_async(queue, blk2_read1);
dispatch_async(queue, blk2_read2);
dispatch_async(queue, blk2_read3);
...

上面兩處示例代碼的執(zhí)行順序是這樣的:blk0 -> blk1 -> blk2(blk0, blk2為前綴的block各自之間分別異步)。

dispatch_apply

void dispatch_apply( size_t iterations, dispatch_queue_t queue, void (^block)( size_t));

把block添加到queue上iterations次,并且等待所有添加的block執(zhí)行完成。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index) {
  NSLog(@"%zu, index);
});

NSLog(@"done");

結(jié)果可能是:

4
1
0
3
5
7
8
9
2
6
done

上面結(jié)果數(shù)字的排列順序是不定的。
再看一個(gè)例子:

dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*
* 在 global dispatch queue 上異步執(zhí)行 */
dispatch_async(queue, ^{
  /*
  * 在 global dispatch queue上, dispatch_apply 函數(shù)等待所有任務(wù)的完成
  */
  dispatch_apply([array count], queue, ^(size_t index) {
    /*
    * 異步地對(duì)array上的對(duì)象進(jìn)行操作 */
    NSLog(@"%zu: %@", index, [array objectAtIndex:index]); 
  });
  /*
  *  dispatch_apply 所有的任務(wù)已完成*/
  /*
  * 在 main dispatch queue 上異步執(zhí)行 */
  dispatch_async(dispatch_get_main_queue(), ^{
    /*
    * Executed on the main dispatch queue.
    * Something like updating userface, etc. */
    NSLog(@"done");
  });
});

dispatch_once

void dispatch_once( dispatch_once_t *predicate, dispatch_block_t block);

用來確保在整個(gè)app的生命周期內(nèi)block只執(zhí)行一次。
不用dispatch_once:

static int initialized = NO;
if (initialized == NO) {
  // do initializing
  initialized = YES;
}

使用dispatch_once顯得更優(yōu)雅:

static dispatch_once_t pred;
dispatch_once(&pred, ^{
  // do the initializing.
});

第一種方法雖然在大多數(shù)情況下是沒問題的,但它是線程不安全的。比如A線程執(zhí)行到行3,即將執(zhí)行行4『initialized = YES;』。此時(shí)線程A的執(zhí)行權(quán)被剝奪,線程B獲得執(zhí)行權(quán),執(zhí)行這塊代碼時(shí),initailized仍舊為NO,所以又進(jìn)行了一次初始化。所以當(dāng)多線程情況下,第一種方法可能會(huì)被執(zhí)行多次。而dispatch_once就沒問題,它是線程安全的。dispatch_once經(jīng)常被用于創(chuàng)建單例。

dispatch_suspend/dispatch_resume

這兩個(gè)方法用來suspend或者resume dispatch queue的執(zhí)行。一個(gè)dispatch queue可以這樣被suspend(休眠):

dispatch_suspend(queue);

然后resume:

dispatch_resume(queue);

suspend并不會(huì)影響那些已經(jīng)在執(zhí)行的任務(wù)。它只會(huì)阻止還在queue的task的執(zhí)行。resume之后,queue上的任務(wù)又可以繼續(xù)被調(diào)度執(zhí)行。
你可以多次調(diào)用dispatch_suspend(queue);每調(diào)用一次queue的計(jì)數(shù)加一,中要此計(jì)數(shù)大于0,queue就一直處于休眠中。所以請(qǐng)保持dispatch_suspend(queue);dispatch_resume(queue);的平衡。

Dispatch Semaphore

Dispatch semaphore 相比傳統(tǒng)的信號(hào)量機(jī)制性能上更優(yōu)秀;只有當(dāng)線程需要阻塞時(shí),dispatch semaphore才會(huì)調(diào)用系統(tǒng)內(nèi)核;如果不會(huì)阻塞線程,則不會(huì)進(jìn)行系統(tǒng)內(nèi)核的調(diào)用。Dispatch semaphore也是通過計(jì)數(shù)來實(shí)現(xiàn)多線程中的控制的。當(dāng)counter大于0時(shí),程序繼續(xù)執(zhí)行;當(dāng)counter小于等于0,程序就等待在那里直到counter大于0,才繼續(xù)執(zhí)行。
相關(guān)函數(shù)就三個(gè):

/創(chuàng)建信號(hào)量變量
dispatch_semaphore_t dispatch_semaphore_create( long value);/

//Increment the counting semaphore. If the previous value was less than zero, this function wakes a thread currently waiting in dispatch_semaphore_wait.
//把信號(hào)量計(jì)數(shù)加1,如果之前信號(hào)量值小于等于0則喚醒一個(gè)由dispatch_semaphore_wait阻塞的線程
long dispatch_semaphore_signal( dispatch_semaphore_t dsema);

//Decrement the counting semaphore. If the resulting value is less than zero, this function waits in FIFO order for a signal to occur before returning.
//把信號(hào)量計(jì)數(shù)減1,如果減1后值小于0,此函數(shù)以先進(jìn)先出FIFO的順序阻塞等待singal。
//如果成功則返回0; 否則返回非0,表示timeout(timeout的時(shí)間過去了,還是沒收到singal)
long dispatch_semaphore_wait( dispatch_semaphore_t dsema, dispatch_time_t timeout);

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_semaphore_wait(semaphore, time); 
if (result == 0) {
  /*
  * The counter of the dispatch semaphore was more than one.
  * Or it became one and more before the specified timeout.
  * The counter is automatically decreased by one.
  *
  * Here, you can execute a task that needs a concurrency control. */
} else {
  /*
  * Because the counter of the dispatch semaphore was zero, * it has waited until the specified timeout.
  */
}

應(yīng)用場(chǎng)景大致可歸為么兩類:

  • 多個(gè)線程訪問有限資源

  • 兩個(gè)線程之間相對(duì)某事件的同步
    比如有兩個(gè)線程A和B,B的某一任務(wù)mession1執(zhí)行的前提條件是A完成A的任務(wù)mession2。當(dāng)執(zhí)行到B的mession1時(shí),如果A的mession2沒有完成,B就被阻塞在那里直到A完成mession1.

多個(gè)線程訪問有限資源

先看代碼:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i = 0; i < 100000; ++i) { 
  dispatch_async(queue, ^{
    [array addObject:[NSNumber numberWithInt:i]];
  }); 
}

上面代碼在并發(fā)queue上更新array,即多個(gè)線程同時(shí)修改array,這就會(huì)使array變臟(corrupted)數(shù)據(jù)一至性問題,導(dǎo)至程序crash。我們可以用dispatch semaphore來防止多個(gè)線程同時(shí)修改array。
使用dispatch semaphore后,上面代碼應(yīng)是這樣的:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//你的資源數(shù)只有一個(gè)(即array),所以參數(shù)傳1;參數(shù)值應(yīng)該等于你的資源個(gè)數(shù)。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i = 0; i < 100000; ++i) { 
  dispatch_async(queue, ^{
    //等待直到semphore大于0
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
    [array addObject:[NSNumber numberWithInt:i]];
    
    //完成任務(wù),釋放資源的控制,使別的線程也能訪問。
    dispatch_semaphore_signal(semaphore);
  }); 
}
dispatch_release(semaphore);
兩個(gè)線程之間相對(duì)某事件的同步

看代碼

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
void (^blk1)(void) = ^{
  NSLog("Mum give a life to me");
  NSLog("My mother and father get together");
};
void (^blk2)(void) = ^{
  NSLog("I grow up");
};
dispatch_async(queue, blk1);
dispatch_async(queue, blk2);

blk2的事件("I grow up")依賴于blk1的事件("Mum give a life to me"),"I grow up"應(yīng)該在"Mum give a life to me"之后發(fā)生。但是上述代碼這兩個(gè)事件的執(zhí)行順序是不確定的。
使用semaphore代碼應(yīng)該是這樣:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
void (^blk1)(void) = ^{
  NSLog("Mum give a life to me");
  //事件發(fā)生,增加semaphore值
  dispatch_semaphore_signal(semaphore);
  NSLog("My mother and father get together");
};
void (^blk2)(void) = ^{
  //如果事件沒有發(fā)生, semaphore為0,一直阻塞等待;如果事件發(fā)生,semaphore為1,繼續(xù)執(zhí)行
  dispatch_semaphore_wait(semaphore);
  NSLog("I grow up");
};
dispatch_async(queue, blk1);
dispatch_async(queue, blk2);
dispatch_release(semaphore);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,619評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,155評(píng)論 3 425
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,635評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,539評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,255評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,646評(píng)論 1 326
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,655評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,838評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,399評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,146評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,338評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,893評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,565評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,983評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,257評(píng)論 1 292
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,059評(píng)論 3 397
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,296評(píng)論 2 376

推薦閱讀更多精彩內(nèi)容

  • 簡(jiǎn)介 GCD(Grand Central Dispatch)是在macOS10.6提出來的,后來在iOS4.0被引...
    sunmumu1222閱讀 882評(píng)論 0 2
  • 背景 擔(dān)心了兩周的我終于輪到去醫(yī)院做胃鏡檢查了!去的時(shí)候我都想好了最壞的可能(胃癌),之前在網(wǎng)上查的癥狀都很相似。...
    Dely閱讀 9,262評(píng)論 21 42
  • 我們知道在iOS開發(fā)中,一共有四種多線程技術(shù):pthread,NSThread,GCD,NSOperation: ...
    請(qǐng)叫我周小帥閱讀 1,500評(píng)論 0 1
  • Dispatch Queues dispatch queues是執(zhí)行任務(wù)的強(qiáng)大工具,允許你同步或異步地執(zhí)行任意代碼...
    YangPu閱讀 660評(píng)論 0 4
  • 3.GCD GCD的全稱是Grand Central Dispatch,提供了非常多的純C語(yǔ)言的函數(shù) GCD的優(yōu)勢(shì)...
    Mario_ZJ閱讀 491評(píng)論 0 0