GCD Basic

Dispatch queues

Dispatch queue有兩種類型,一種是線性queue,線性queue一個一個的執(zhí)行queue上的任務遇西,如果當前任務正在執(zhí)行,queue一直等待在那里直它完成才會開始新的任務杂瘸;另一種是并發(fā)queue,它并不等待正在執(zhí)行的任務的完成伙菊,只要有資源讓它開啟任務败玉,它就會分配queue上任務的執(zhí)行。

提交到線性queue上的Blocks是按先進先出(FIFO)的順序執(zhí)行的镜硕。但要注意的是运翼,不同線性queue上的Block的執(zhí)行是相互獨立的,所以互相之間是可能并發(fā)執(zhí)行的兴枯。提交到并發(fā)queue上的Block也是按FIFO的順序出隊列的血淌,因為并發(fā)queue并不等待任務的完成,所以只要有資源可運行并發(fā)queue就會以FIFO的順序開啟queue上的block的執(zhí)行财剖,block之間可能是并發(fā)的悠夯。

當你把任務提交到并發(fā)queue上,可并發(fā)執(zhí)行的任務數(shù)是由系統(tǒng)的當前狀態(tài)決定的躺坟。iOS或MacOS根據(jù)當前的狀態(tài)(比如并發(fā)queue上的任務數(shù)沦补、CPU核數(shù)和CPU的使用情況)來決定并發(fā)數(shù)。
如果你的任務應該有序地執(zhí)行咪橙,或者不應該并發(fā)的執(zhí)行夕膀,那么你應該用線性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)建了一個新的線性queue产舞。 dispatch_queue_create, 有兩個參數(shù):

參數(shù) 描述
label label 唯一標識你創(chuàng)建的queue,label在debug時(Instruments, sample, stackshots, crash reports)會有用.因為系統(tǒng)庫或其它框架也會創(chuàng)建queue菠剩,為了保證label的唯一易猫,請DNS的反轉(zhuǎn)格式(com.example.myqueue)。這個參數(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);

另外嘴办,當一個線性queue被創(chuàng)建且有任務提交上去瞬场,系統(tǒng)會為每個線性queue創(chuàng)建一個線程。如果你創(chuàng)建了2000個線性queue涧郊,就會有相應的2000個線程被創(chuàng)建贯被。太多的線程會增加內(nèi)存的消耗,以及線程間的切換都會大大降低系統(tǒng)性能妆艘。所以不要濫用線性queue彤灶,只有當任務之間的執(zhí)行是有序的或者為了避免多線程并發(fā)而引起的資源競爭時,才使用線性queue批旺。而且線性queue的個數(shù)應該和你需求的一至幌陕,不要創(chuàng)建多余的線性queue。 如是任務沒有數(shù)據(jù)一至性問題汽煮,且可以并發(fā)搏熄,請用并發(fā)queue,而不是用多個線性queue來并發(fā)暇赤。 因為并發(fā)queue使用的線程是由系統(tǒng)內(nèi)核非常有效地管理的心例,用并發(fā)queue更高效。

雖然鞋囊,編譯器有強大的內(nèi)存自動管理(ARC)止后,但是GCD中創(chuàng)建的實例(這里不是Objective-C對象,而是GCD中一些結(jié)構體實例)須由我們自己手動釋放溜腐,因為這些實例(如dispatch queue)不像Block那樣被當成Objective-C對象译株。當你使用完這些實例時,應用dispatch_release釋放它們挺益,用dispatch_retain來擁有它們歉糜。 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提交了任務之后黍檩,馬上就釋放它叉袍。這樣做安全嗎?

這樣沒任何問題刽酱。當一個Block被提交到一個queue上時喳逛,這個Block會擁有這個queue(dispatch_retain, 即,使queue引用記數(shù)加1)棵里。當Block執(zhí)行結(jié)束時它會釋放對queue的擁有權(dispatch_release, 即润文,使queue引用記數(shù)減1)姐呐。所以,即使提交block之后立馬釋放queue典蝌,queue也不會被系統(tǒng)回收曙砂,而Block也可以被執(zhí)行。當Block執(zhí)行完并釋放queue的擁有權時骏掀,queue才真正被回收鸠澈。

Main Dispatch Queue / Global Dispatch Queue

Main Dispatch Queue / Global Dispatch Queue 由系統(tǒng)提供。
Main dispatch queue, 由名字也可以知道, 它將任務分配到主線程上執(zhí)行截驮。Main dispatch queue是線性queue笑陈,且總共只有一個。因為Main dispatch queue在主線程上執(zhí)行任務葵袭,所以你應該把那些必須由主線程執(zhí)行的任務(如UI更新)提交到此queue上涵妥。performSelectorOnMainThread也有相似的功能。

如果不是有特殊需求坡锡,一般而言蓬网,你不需需用dispatch_queue_create創(chuàng)建自己的并發(fā)queue,系統(tǒng)提供的全局queue(gloabl queue)足已娜氏。 Global queue有四種分別對應四個不同優(yōu)先級: 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);

隨帶提一下, 如果你對系統(tǒng)提供的queue拳缠,進行 dispatch_retain 或者 dispatch_release, 什么都不會發(fā)生,并不會增加或減少queue的引用計數(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上的任務绵疲。
讓我們一個一個查看這些API有多么強大哲鸳!

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設置一個target queue。這主要用在給你新建的queue指定優(yōu)先級盔憨。無論你用dispatch_queue_create創(chuàng)建的是線性queue(serial queue)還是并發(fā)queue(concurrent queue)徙菠,它的優(yōu)先級都與global queue的默認優(yōu)先級相同。在創(chuàng)建queue之后郁岩,你就可以用這個函數(shù)來改變它的優(yōu)先級婿奔。下面代碼展示了如何讓一個線性queue擁有background優(yōu)先級。

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的第一個參數(shù)是你要設置的queue问慎,第二個參數(shù)是target queue萍摊,這兩個參數(shù)都不能為NULL。它使mySerialDispatchQueue的優(yōu)先級和target queue(globalDispatchQueueBackground)一樣如叼。不要任何系統(tǒng)的queue(main queue和global queue)作為第一個參數(shù)傳進去冰木,這樣做的結(jié)果是不可預料的!
dispatch_set_target_queue不僅可以用來改變queue的優(yōu)先級,還可以創(chuàng)建queue 的層級關系踊沸。如果你把一個任務(block)提交到一個線性queue(A)上歇终,同時這個線性queue的target queue是另一個線性queue(B)。那么這個任務(block)將不會和提交到B或其它以B為target queue的queue上的block 逼龟,并發(fā)執(zhí)行评凝。

Queue 的層級

如圖所示,底下三個dispatch queue上的任務(blocks)將會線性執(zhí)行审轮。當你的各個任務不應該并發(fā)執(zhí)行肥哎,同時又必須放在不同的queue上時;你就可以通過設置這些queue的target都為某個線性queue疾渣,來阻止并發(fā)。實際上崖飘,我自己一直想不出有類似需求的場景榴捡。

dispatch_after

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

dispatch_after來設置queue上某個任務(block)的啟動時間。下面代碼做的是朱浴,延遲三秒后將一個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語言中指定unsigned long long類型的吊圾。注意的是,此函數(shù)并不是在指定時間之后執(zhí)行block翰蠢,而是在指定時間之后將block添加到目標queue中项乒。你可以把該函數(shù)看作是增強版的dispatch_async,當指定延遲時間是DISPATCH_TIME_NOW時梁沧,此函數(shù)功能上與dispatch_async等價檀何,不過蘋果官方文檔建議你應該用dispatch_async,而延遲時間為DISPATCH_TIME_FOREVER時廷支,結(jié)果是不可預料的:

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ù)的三個參數(shù)中恋拍, 這里只講第一個參數(shù)time吧垛孔。首先你要用dispatch_timedispatch_walltime來創(chuàng)建time。此函數(shù)接收納秒數(shù)施敢,所以你應該用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)建相對時間僵娃,而dispatch_walltime則用來創(chuàng)建絕對時刻概作。比如,你用dispatch_walltime來創(chuàng)建2016年11月11號11點11分11秒時刻悯许∑袜拢可以用dispatch_walltime來實現(xiàn)鬧鐘,不過此方法精度不高先壕。dispatch_walltime根據(jù)結(jié)構體timespec來創(chuàng)建時刻的瘩扼,如下面例子:

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 用來對block分組谆甜。當你有個任務,并且些任務要求其它任務都完成時才能開始集绰,這時你就可以和Dispatch Group來實現(xiàn)规辱。當然簡單的情況下,你可以把任務都放進一個線性queue中栽燕,在queue尾放入那個任務罕袋。但是如果遇到并發(fā)隊列或有多個隊列情況時,就變得很復雜碍岔,dispatch group應此而生浴讯。下因代碼例子將三個block分到一個組,并在這些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é)果將是(前三個輸出順序不定):
blk1
blk2
blk0
done
首先我們用dispatch_group_create創(chuàng)建dispatch_group_t實例榆纽。因為函數(shù)中有"create",當不再需要它時你必須release它, GCD中所有的release都用dispatch_release捏肢。函數(shù)dispatch_group_async就像dispatch_async把block添加到隊列中奈籽,唯一的區(qū)別是多了一個參數(shù)dispatch_group_t,將block和dispatch group關聯(lián)起來鸵赫。當block和某個dispatch group關聯(lián)之后衣屏,block會擁有這個dispatch group,(dispatch_retain辩棒,就如同block被添加到queue中時會retain這個queue一樣)狼忱,當block執(zhí)行完時,block會釋放它擁有的dispatch group(dispatch_release). 當所有與dispatch group相關聯(lián)的block執(zhí)行完時盗温,dispatch_group_notify會將block("done")添加到main queue上藕赞。dispatch_group_notify并不會阻塞當前線程,它監(jiān)聽指定的dispach group卖局,當該group上所有block都執(zhí)行時斧蜕,它將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的第二個參數(shù)是timeout(dispatch_time_t)指定等待的時長, 如果在timeout時長內(nèi)group上的任務都完成則返回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相關聯(lián)的任務都已完成
} else {
  //與group相關聯(lián)的任務中還有在運行
}

dispatch_group_wait被調(diào)用時染坯,并不會從該函數(shù)立即返回均芽,當前線程阻塞在此函數(shù)上,等待group上所有任務完成单鹿,或者timeout掀宋。

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

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

在關聯(lián)block和group時,dispatch_group_async并不是唯一的函數(shù),還有dispatch_group_enter(與之成對的是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)測group1湃鹊。當blk0,blk1,blk3都完成時,添加block到queue
*/
dispatch_group_notify(group1, queue, ^{
  NSLog("blk0, blk1, blk3, all have finished!");
});

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

注意dispatch_group_enterdispatch_group_leave必須保持平衡(成對出現(xiàn))币呵。如果你需要把一個block關聯(lián)到不同的group上,你只能使用dispatch_group_enter函數(shù)對侨颈,否則使用dispatch_group_async更方便一點余赢。

下面看代碼是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ù)只能兩個參數(shù),一個是queue哈垢,一個block妻柒。作用是把block加到queue上,特殊的地方在于温赔,被加queue上的block稱為barrier block蛤奢。barrier block 把queue上的任務的執(zhí)行分成三個階段:

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

這里的參數(shù)queue陶贼,應該是你通過dispatch_queue_create 創(chuàng)建的并發(fā)queue,如果你傳入的是線性queue或者全局并發(fā)queue待秃,函數(shù)作用就和dispatch_queue_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_async(queue, blk0_wash_body);
dispatch_async(queue, blk1_eat_breakfast);
dispatch_async(queue, blk2_goout_by_bike);
dispatch_async(queue, blk2_enjoy_music);

我們要實現(xiàn)這樣的功能章郁,在洗澡的時候同時洗臉刷牙洗臉枉氮。然后做完上述這些事之后(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);

對數(shù)據(jù)庫或文件進行寫操作時惹悄,我們也可以用此函數(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ù)字的排列順序是不定的。
再看一個例子:

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ù)等待所有任務的完成
  */
  dispatch_apply([array count], queue, ^(size_t index) {
    /*
    * 異步地對array上的對象進行操作 */
    NSLog(@"%zu: %@", index, [array objectAtIndex:index]); 
  });
  /*
  *  dispatch_apply 所有的任務已完成*/
  /*
  * 在 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);

用來確保在整個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;』。此時線程A的執(zhí)行權被剝奪,線程B獲得執(zhí)行權箫柳,執(zhí)行這塊代碼時手形,initailized仍舊為NO,所以又進行了一次初始化滞时。所以當多線程情況下叁幢,第一種方法可能會被執(zhí)行多次。而dispatch_once就沒問題坪稽,它是線程安全的曼玩。dispatch_once經(jīng)常被用于創(chuàng)建單例。

dispatch_suspend/dispatch_resume

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

dispatch_suspend(queue);

然后resume:

dispatch_resume(queue);

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

Dispatch Semaphore

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

/創(chuàng)建信號量變量
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.
//把信號量計數(shù)加1,如果之前信號量值小于等于0則喚醒一個由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.
//把信號量計數(shù)減1葵硕,如果減1后值小于0眉抬,此函數(shù)以先進先出FIFO的順序阻塞等待singal。
//如果成功則返回0懈凹; 否則返回非0蜀变,表示timeout(timeout的時間過去了,還是沒收到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.
  */
}

應用場景大致可歸為么兩類:

  • 多個線程訪問有限資源

  • 兩個線程之間相對某事件的同步
    比如有兩個線程A和B介评,B的某一任務mession1執(zhí)行的前提條件是A完成A的任務mession2库北。當執(zhí)行到B的mession1時爬舰,如果A的mession2沒有完成,B就被阻塞在那里直到A完成mession1.

多個線程訪問有限資源

先看代碼:

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寒瓦,即多個線程同時修改array情屹,這就會使array變臟(corrupted)數(shù)據(jù)一至性問題,導至程序crash杂腰。我們可以用dispatch semaphore來防止多個線程同時修改array垃你。
使用dispatch semaphore后,上面代碼應是這樣的:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//你的資源數(shù)只有一個(即array)喂很,所以參數(shù)傳1惜颇;參數(shù)值應該等于你的資源個數(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]];
    
    //完成任務少辣,釋放資源的控制凌摄,使別的線程也能訪問。
    dispatch_semaphore_signal(semaphore);
  }); 
}
dispatch_release(semaphore);
兩個線程之間相對某事件的同步

看代碼

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"應該在"Mum give a life to me"之后發(fā)生锨亏。但是上述代碼這兩個事件的執(zhí)行順序是不確定的。
使用semaphore代碼應該是這樣:

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);
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末弓乙,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子钧惧,更是在濱河造成了極大的恐慌暇韧,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浓瞪,死亡現(xiàn)場離奇詭異懈玻,居然都是意外死亡,警方通過查閱死者的電腦和手機乾颁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門涂乌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人英岭,你說我怎么就攤上這事湾盒。” “怎么了诅妹?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵罚勾,是天一觀的道長毅人。 經(jīng)常有香客問我,道長尖殃,這世上最難降的妖魔是什么丈莺? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮送丰,結(jié)果婚禮上缔俄,老公的妹妹穿的比我還像新娘。我一直安慰自己器躏,他們只是感情好俐载,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著邀桑,像睡著了一般瞎疼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上壁畸,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天贼急,我揣著相機與錄音,去河邊找鬼捏萍。 笑死太抓,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的令杈。 我是一名探鬼主播走敌,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼逗噩!你這毒婦竟也來了掉丽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤异雁,失蹤者是張志新(化名)和其女友劉穎捶障,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纲刀,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡项炼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了示绊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锭部。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖面褐,靈堂內(nèi)的尸體忽然破棺而出拌禾,到底是詐尸還是另有隱情,我是刑警寧澤盆耽,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布蹋砚,位于F島的核電站扼菠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏坝咐。R本人自食惡果不足惜循榆,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望墨坚。 院中可真熱鬧秧饮,春花似錦、人聲如沸泽篮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽帽撑。三九已至泼各,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間亏拉,已是汗流浹背扣蜻。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留及塘,地道東北人莽使。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像笙僚,于是被迫代替她去往敵國和親芳肌。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351

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

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