iOS調(diào)度隊列

GCD調(diào)度隊列是執(zhí)行任務(wù)的強大工具畔规。調(diào)度隊列允許您相對于調(diào)度者異步或者同步的執(zhí)行任意代碼塊。您能夠使用調(diào)度隊列來執(zhí)行幾乎所有在單獨線程上執(zhí)行的任務(wù)筋栋。調(diào)度隊列的優(yōu)點是它們比線程代碼更簡單且更高效撇叁。

下面提供了調(diào)度隊列的簡介窖认,以及在應(yīng)用程序中怎么使用調(diào)度隊列執(zhí)行一般的任務(wù)。如果您想用使用調(diào)度隊列替換已經(jīng)存在的線程代碼锌钮,請參閱線程遷移桥温。

關(guān)于調(diào)度隊列

調(diào)度隊列是在應(yīng)用程序中異步并發(fā)執(zhí)行任務(wù)的一種簡單方法。任務(wù)通常是應(yīng)用程序需要執(zhí)行的一些工作梁丘。例如策治,您可能定義一個任務(wù)來執(zhí)行一些計算,創(chuàng)建和修改數(shù)據(jù)結(jié)構(gòu)兰吟,處理從文件中讀取的數(shù)據(jù)通惫,或者任意數(shù)量的事情。通過放置相應(yīng)的代碼到函數(shù)或者塊對象中來定義任務(wù)混蔼,并把他們放到調(diào)度隊列中履腋。

調(diào)度隊列是一個類似對象的結(jié)構(gòu)體,它管理您提交給它的任務(wù)惭嚣。所有的調(diào)度隊列都是先進先出的數(shù)據(jù)結(jié)構(gòu)遵湖。因此,添加到隊列的任務(wù)始終以添加他們的相同順序開始執(zhí)行晚吞。GCD自動為您提供一些調(diào)度隊列延旧,但您可以為特定目的創(chuàng)建其他隊列。下表列出了應(yīng)用程序中可以使用的調(diào)度隊列以及怎么使用它們槽地。

類型 描述
串行 串行隊列(也稱為私有調(diào)度隊列)以添加它們到隊列的順序每次執(zhí)行一個任務(wù)迁沫。當前執(zhí)行的任務(wù)運行在一個被調(diào)度隊列管理的不同線程上(可以隨任務(wù)變化)。串行隊列經(jīng)常用于同步訪問特殊資源捌蚊。 您可以根據(jù)需要創(chuàng)建盡可能多的串行隊列集畅,每個隊列相對于其他隊列并行運行。也就是說缅糟,如果你創(chuàng)建四個串行隊列挺智,每個隊列同一時間只執(zhí)行一個任務(wù),但是仍然可以有多達四個任務(wù)同時執(zhí)行窗宦,每個隊列一個赦颇。有關(guān)怎么創(chuàng)建串行隊列二鳄,請參閱創(chuàng)建串行調(diào)度隊列
并行 并發(fā)隊列(也稱為一類全局調(diào)度隊列)同時執(zhí)行一個或者多個任務(wù)媒怯,但任務(wù)仍然以被添加到隊列的順序開始執(zhí)行订讼。當前執(zhí)行的任務(wù)運行在被調(diào)度隊列管理的不同線程上。在給定的時間點執(zhí)行的任務(wù)數(shù)量是可變的沪摄,并且取決于系統(tǒng)調(diào)節(jié)。在iOS5及以后纱烘,你可以通過指定隊列類型為DISPATCH_QUEUE_CONCURRENT來自己創(chuàng)建并發(fā)隊列杨拐。此外,還有四個預(yù)定義的全局并發(fā)隊列供應(yīng)用程序使用擂啥。有關(guān)怎么回去全局并發(fā)隊列哄陶,請參閱獲取全局并發(fā)隊列
主調(diào)度隊列 主調(diào)隊隊列是一個全局可用的串行隊列哺壶,它在應(yīng)用程序的主線程上執(zhí)行任務(wù)屋吨。這個隊列與應(yīng)用程序的RunLoop(如果存在)交錯處理排隊的任務(wù)以及添加到RunLoop的其他事件源。因為它運行在程序的主線程上山宾,所以主隊列經(jīng)常作為應(yīng)用程序的關(guān)鍵同步點至扰。雖然您不需要創(chuàng)建主調(diào)度隊列,但您需要確保您的應(yīng)用程序適當?shù)尼尫潘拭獭S嘘P(guān)如何管理此隊列敢课,請參閱在主線程中執(zhí)行任務(wù)

當向應(yīng)用程序添加并發(fā)時绷杜,調(diào)度隊列提供了優(yōu)于線程的幾個優(yōu)點直秆。最直接的優(yōu)點是工作隊列編程的簡單性。使用線程鞭盟,您必須編寫執(zhí)行的工作以及創(chuàng)建和管理線程的代碼圾结。調(diào)度隊列使您專注于您實際想要執(zhí)行的工作,而不用擔心線程的創(chuàng)建和管理齿诉。相反筝野,系統(tǒng)會為您處理所有的線程創(chuàng)建和管理。優(yōu)點是粤剧,系統(tǒng)能夠比任何單個應(yīng)用更高效的管理線程遗座。系統(tǒng)可以根據(jù)可用資源和當前系統(tǒng)的情況動態(tài)調(diào)整線程數(shù)量。另外俊扳,系統(tǒng)通常能夠比您自己創(chuàng)線程更快的開始運行您的任務(wù)途蒋。

雖然您可能認為編寫調(diào)度隊列代碼可能是困難的,但是通常編寫調(diào)度隊列比編寫線程更簡單馋记。編碼的關(guān)鍵是設(shè)計獨立的且可以異步運行的任務(wù)号坡。(這實際上對線程和調(diào)度隊列都是真的懊烤。)但是調(diào)度隊列有可預(yù)見性的優(yōu)點。如果您有兩個任務(wù)來訪問相同的共享資源宽堆,但是運行在不同的線程上腌紧,每個線程都可以首先修改資源,您可能需要使用鎖畜隶,以確保這兩個任務(wù)不能同時修改該資源壁肋。使用調(diào)度隊列,您可以添加兩個任務(wù)到一個串行隊列籽慢,以確保在任何給定時間只有一個任務(wù)修改資源浸遗。這種基于隊列的同步比鎖更高效,因為鎖在有競爭和無競爭的情況下總是需要一個昂貴的內(nèi)核陷阱箱亿,而調(diào)度隊列主要在應(yīng)用程序的進程空間中工作跛锌,只有在絕對必要時才調(diào)用內(nèi)核。

雖然您可能指出届惋,串行隊列中的兩個任務(wù)不是并發(fā)運行髓帽,但您必須記住,如果兩個線程同時使用鎖脑豹,線程提供的任何并發(fā)都會丟失或者顯著減少郑藏。更重要的,線程模型需要創(chuàng)建兩個線程瘩欺,這兩個線程都占用內(nèi)核和用戶內(nèi)存空間译秦。調(diào)度隊列不需要為他們的線程支付相同的內(nèi)存損失,并且使用的線程保持忙碌且不被阻塞击碗。

謹記以下關(guān)于調(diào)度隊列的一些其他關(guān)鍵點:

  • 調(diào)度隊列相對于其他調(diào)度隊列并發(fā)執(zhí)行任務(wù)筑悴。任務(wù)的串行限于單個調(diào)度隊列中的任務(wù)。
  • 在任何時候系統(tǒng)決定執(zhí)行任務(wù)的數(shù)量稍途。因此阁吝,在100個不同隊列中有100個任務(wù)的應(yīng)用程序可能不會同時執(zhí)行這些任務(wù)(除非它具有100個或者更多有效的內(nèi)核)。
  • 在選擇要啟動的新任務(wù)時械拍,系統(tǒng)會考慮隊列優(yōu)先級突勇。有關(guān)如何設(shè)置串行隊列的優(yōu)先級,請參閱為隊列提供清理功能坷虑。
  • 當任務(wù)被添加到隊列時甲馋,任務(wù)必須準備好執(zhí)行。(如果您之前使用過Cocoa操作對象迄损,請注意此行為與操作使用的模型不同)定躏。
  • 私有調(diào)度隊列是引用計數(shù)對象。除了在您自己的代碼中保留隊列之外,請注意調(diào)度源也可以附加到隊列痊远,并且增加其保留計數(shù)垮抗。因此,您必須確保所有調(diào)度源都被取消碧聪,并且所有的保留調(diào)用(retain call)都通過適當?shù)尼尫耪{(diào)用(release call)進行平衡冒版。有關(guān)保留和釋放隊列,請參閱調(diào)度隊列的內(nèi)存管理逞姿。有關(guān)調(diào)度源的更多信息辞嗡,請參閱關(guān)于調(diào)度源

有關(guān)操作調(diào)度隊列的接口滞造,請參閱大中央調(diào)度(GCD)參考续室。

隊列相關(guān)技術(shù)

除了調(diào)度隊列,GCD提供了幾種使用隊列來幫助管理代碼的技術(shù)断部。下表列出了這些技術(shù)猎贴,并提供了找到關(guān)于它們更多信息的鏈接班缎。

技術(shù) 描述
Dispatch Group 調(diào)度組是一種用來監(jiān)視一組塊對象完成的方法(您可以根據(jù)需要同步或者異步監(jiān)視)蝴光。組為依賴于其他任務(wù)完成的代碼提供一種有用的同步機制。更多有關(guān)使用組的信息达址,請參閱等待排隊任務(wù)組蔑祟。
Dispatch semaphores 調(diào)度信號量類似于傳統(tǒng)的信號量,但通常更高效沉唠。只有當調(diào)用線程需要被阻塞時疆虚,調(diào)度信號量才調(diào)用內(nèi)核,因為信號量不可用满葛。如果信號量可用径簿,則不進行內(nèi)核調(diào)用。有關(guān)如何使用調(diào)度信號量的例子嘀韧,請參閱使用調(diào)度信號量來調(diào)節(jié)有限資源的使用篇亭。
Dispatch sources 調(diào)度源生成通知以響應(yīng)特定類型的系統(tǒng)事件。您可以使用調(diào)度源來監(jiān)視事件锄贷,例如進程通信译蒂,信號和描述符事件等。當事件發(fā)生時谊却,調(diào)度源異步的將您的任務(wù)代碼提交到指定的調(diào)度隊列進行處理柔昼。有關(guān)創(chuàng)建和使用調(diào)度源的更多信息,請參閱調(diào)度源炎辨。

使用塊實現(xiàn)任務(wù)

塊對象(Block Object)是基于C語言的功能捕透,可以使用C,Objective-C和C++代碼。塊使定義獨立的工作單元變的簡單激率。雖然他們可能看起來類似函數(shù)指針咳燕,但塊實際上是底層數(shù)據(jù)結(jié)構(gòu)的表現(xiàn),類似于對象乒躺,由編譯器創(chuàng)建和管理招盲。編譯器將您提供的代碼(以及任何相關(guān)數(shù)據(jù))打包,并將其封裝成可以存在于堆中并傳遞給應(yīng)用程序的形式嘉冒。

塊的一個關(guān)鍵優(yōu)點是它們能夠使用自己的詞匯作用域之外的變量曹货。當您在函數(shù)或者方法中定義塊時,塊在某些方法充當傳統(tǒng)代碼塊讳推。例如顶籽,塊可以讀取定義在父作用域的變量值。由塊訪問的變量將被復(fù)制到堆上的塊數(shù)據(jù)結(jié)構(gòu)中银觅,因此塊可以稍后訪問它們礼饱。當塊被添加到調(diào)度隊列時,這些值通常必須以只讀格式保留究驴。然而镊绪,被同步執(zhí)行的的塊也可以使用具有__block關(guān)鍵字的變量來返回數(shù)據(jù)到父作用域。

使用類似于函數(shù)指針語法的代碼聲明內(nèi)聯(lián)塊洒忧。塊和函數(shù)指針最大的不同是蝴韭,在塊名字之前使用脫字符(^)代替星號(*)。像函數(shù)指針一樣熙侍,可以傳遞參數(shù)給塊榄鉴,從其接收返回值。下面代碼展示如何聲明和同步執(zhí)行塊蛉抓。變量aBlock被聲明為塊庆尘,接收一個整型參數(shù),沒返回值巷送。然后將與該原型匹配的實際塊分配給aBlock并聲明為內(nèi)聯(lián)驶忌。最后一行立即執(zhí)行塊,將指定證書打印到標準輸出惩系。

int x = 123;
int y = 456;
 
// Block declaration and assignment
void (^aBlock)(int) = ^(int z) {
    printf("%d %d %d\n", x, y, z);
};
 
// Execute the block
aBlock(789);   // prints: 123 456 789

下面是設(shè)計塊時需要注意的一些主要指南的摘要:

  • 對于打算使用調(diào)度隊列異步執(zhí)行的塊位岔,可以安全的從父函數(shù)或者方法中獲取標量變量并在塊中使用它們。然而堡牡,不應(yīng)該試圖獲取由調(diào)用上下文分配和刪除的大型結(jié)構(gòu)或者其他基于指針的變量抒抬。當塊執(zhí)行時,被該指針引用的內(nèi)存可能消失晤柄。當然擦剑,可以自己分配內(nèi)存(或者對象)并明確的將該內(nèi)存的所有權(quán)交給塊。
  • 調(diào)度隊列復(fù)制添加給它們的塊,并且當它們結(jié)束執(zhí)行時釋放塊惠勒。換句話說赚抡,在添加它們到隊列之前,您不需要顯式的復(fù)制塊纠屋。
  • 雖然隊列在執(zhí)行小任務(wù)時比原始線程更高效涂臣,但仍然有創(chuàng)建塊和在隊列上執(zhí)行它們的開銷。如果塊的工作太少售担,內(nèi)聯(lián)的執(zhí)行可能比調(diào)度到隊列成本更低赁遗。判斷塊是否工作太少的方法是使用性能工具收集每個路徑的指標,然后進行比較族铆。
  • 不要緩存和底層線程相關(guān)的數(shù)據(jù)岩四,并希望從不同的塊訪問數(shù)據(jù)。如果同一隊列中的任務(wù)需要共享數(shù)據(jù)哥攘,使用調(diào)度隊列的上下文指針來存儲數(shù)據(jù)剖煌。 有關(guān)如何訪問調(diào)度隊列的上下文數(shù)據(jù),請參閱使用隊列存儲自定義上下文信息 逝淹。
  • 如果隊列創(chuàng)建多個Objective-C對象耕姊,則可能需要將塊代碼的一部分包含在@autorelease塊中,以處理這些對象的內(nèi)存管理创橄。 雖然GCD調(diào)度隊列具有自己的自動釋放池箩做,但它們不能保證何時drain這些池莽红。 如果您的應(yīng)用程序受內(nèi)存限制妥畏,創(chuàng)建自己的自動釋放池允許您以定期的時間間隔釋放自動釋放對象的內(nèi)存。

有關(guān)塊的更多信息安吁,包括如何聲明和使用它們醉蚁,請參閱塊編程。有關(guān)怎么添加塊到調(diào)度隊列鬼店,請參閱添加任務(wù)到隊列网棍。

創(chuàng)建和管理調(diào)度隊列

在將任務(wù)添加到隊列之前,必須確定要使用的隊列類型以及如何使用它妇智。調(diào)度隊列可以串行或并發(fā)執(zhí)行任務(wù)滥玷。此外,如果您對隊列有特殊用途巍棱,您可以相應(yīng)地配置隊列屬性惑畴。 以下各節(jié)介紹如何創(chuàng)建調(diào)度隊列并對其進行配置。

獲得全局并發(fā)調(diào)度隊列

當有多個任務(wù)并行運行時航徙,并發(fā)調(diào)度隊列很有用如贷。并發(fā)隊列仍然是一個隊列,它以先進先出的順序?qū)θ蝿?wù)進行出隊,然而杠袱,在前面任何任務(wù)結(jié)束之前并發(fā)隊列可能出隊另外的任務(wù)尚猿。在任何給定時刻,并發(fā)隊列執(zhí)行任務(wù)的實際數(shù)量是可變的楣富,并且隨應(yīng)用程序情況的變化而變化凿掂。許多因素影響并發(fā)隊列執(zhí)行的任務(wù)數(shù),包括可用核心數(shù)纹蝴,其他進程正在完成的工作量缠劝,其他串行調(diào)度隊列中任務(wù)數(shù)量和優(yōu)先級。

系統(tǒng)為每個應(yīng)用程序提供四個并發(fā)調(diào)度隊列骗灶。這些隊列對應(yīng)用程序是全局的惨恭,并且僅通過優(yōu)先級來區(qū)分。因為它們是全局的耙旦,所以不需要顯式的創(chuàng)建它們脱羡。相反,使用dispatch_get_global_queue函數(shù)來獲取其中一個隊列免都,如下所示:

dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

除了獲取默認并發(fā)隊列锉罐,您可以通過傳入DISPATCH_QUEUE_PRIORITY_HIGHDISPATCH_QUEUE_PRIORITY_LOW常量到函數(shù)來獲取高優(yōu)先級和低優(yōu)先級的隊列,或者傳入DISPATCH_QUEUE_PRIORITY_BACKGROUND常量來獲取后臺隊列绕娘。正如您所期望的脓规,高優(yōu)先級并發(fā)隊列中的任務(wù)在默認優(yōu)先級和低優(yōu)先級隊列中的任務(wù)之前執(zhí)行。類似的险领,默認隊列中的任務(wù)在低優(yōu)先級隊列中的任務(wù)之前執(zhí)行侨舆。

重要提示:傳入dispatch_get_global_queue函數(shù)的第二個參數(shù)是為將來擴展保留的。現(xiàn)在绢陌,您應(yīng)該總是為此參數(shù)傳0.

雖然調(diào)度隊列是引用計數(shù)對象挨下,但您不需要保留和釋放全局并發(fā)隊列。因為它們對于應(yīng)用程序來說是全局的脐湾,所以忽略這些隊列的保留和釋放調(diào)用臭笆。因此,您不需要保存對這些隊列的引用秤掌。任何時候您需要引用他們中的一個愁铺,只需要調(diào)用dispatch_get_global_queue函數(shù)。

創(chuàng)建串行調(diào)度隊列

當想要任務(wù)按照特定的順序執(zhí)行時闻鉴,串行隊列非常有用茵乱。串行隊列每次只執(zhí)行一個任務(wù),并且總是從隊列首獲取任務(wù)椒拗。您可以使用串行隊列代替鎖來保護共享資源或者可變數(shù)據(jù)結(jié)構(gòu)似将。與鎖不同的是获黔,串行隊列能夠確保任務(wù)按照可預(yù)見的順序執(zhí)行。只要以異步方式提交任務(wù)到串行隊列在验,隊列就永遠不會死鎖玷氏。

與已經(jīng)為您創(chuàng)建好的并發(fā)隊列不同,您必須顯式的創(chuàng)建和管理任何您想要使用的串行隊列腋舌。您可以為您的應(yīng)用程序創(chuàng)建任意數(shù)量的串行隊列盏触,但應(yīng)避免創(chuàng)建大量的串行隊列來盡可能多的同時執(zhí)行任務(wù)。如果您想同時執(zhí)行大量任務(wù)块饺,提交他們到全局并發(fā)隊列赞辩。當創(chuàng)建串行隊列時,請確定每個隊列的用途授艰,例如保護資源或者同步應(yīng)用程序的某些關(guān)鍵行為辨嗽。

下面代碼顯示了創(chuàng)建自定義串行隊列所需的步驟。dispatch_queue_create函數(shù)有兩個參數(shù):隊列名稱和一組隊列屬性淮腾。調(diào)試器和性能工具顯示隊列名稱糟需,幫助您跟蹤任務(wù)如何執(zhí)行。隊列屬性是為將來使用預(yù)留的谷朝,應(yīng)該總是NULL洲押。

dispatch_queue_t queue;
queue = dispatch_queue_create("com.example.MyQueue", NULL);

除了您創(chuàng)建的自定義隊列,系統(tǒng)自動創(chuàng)建串行隊列并將其綁定到您應(yīng)用程序的主線程圆凰。有關(guān)獲取主線程隊列的更多信息杈帐,請參閱在運行時獲取常見隊列

注意:dispatch_queue_create函數(shù)第二個參數(shù)隊列屬性专钉,傳入?yún)?shù)來決定隊列類型:

  • DISPATCH_QUEUE_SERIAL挑童,串行隊列,也就是NULL驶沼,
  • DISPATCH_QUEUE_SERIAL_INACTIVE炮沐,也是串行隊列争群,不活躍的(調(diào)度隊列可能以一種不活躍的狀態(tài)被創(chuàng)建回怜,在這種狀態(tài)下的隊列,隊列中blocks 被調(diào)用之前换薄,隊列必須被激活玉雾。調(diào)用dispatch_activate函數(shù)是隊列活躍。)
  • DISPATCH_QUEUE_CONCURRENT轻要,并發(fā)隊列
  • DISPATCH_QUEUE_CONCURRENT_INACTIVE复旬,并發(fā)隊列,不活躍的

在運行時獲取常見隊列

GCD提供函數(shù)允許您從應(yīng)用程序中訪問幾個常見的調(diào)度隊列:

  • 使用dispatch_get_current_queue函數(shù)調(diào)試或者測試當前隊列的標示冲泥。在塊對象內(nèi)部調(diào)用這個函數(shù)驹碍,返回塊被提交到的隊列(并且現(xiàn)在可能正在運行)壁涎。在塊外部調(diào)用此函數(shù),將返回應(yīng)用程序的默認并發(fā)隊列志秃。
  • 使用dispatch_get_main_queue函數(shù)獲取關(guān)聯(lián)到應(yīng)用程序主線程的串行調(diào)度隊列怔球。對于Cocoa應(yīng)用程序和調(diào)用dispatch_main函數(shù)或在主線程上配置RunLoop(使用CFRunLoopRef類型或者NSRunLoop對象)的應(yīng)用程序,此隊列自動被創(chuàng)建。
  • 使用dispatch_get_global_queue函數(shù)獲取任意共享的并發(fā)隊列。更多信息局扶,請參閱獲得全局并發(fā)調(diào)度隊列内斯。

調(diào)度隊列的內(nèi)存管理

調(diào)度隊列和其他調(diào)度對象是引用計數(shù)的數(shù)據(jù)類型。當創(chuàng)建串行調(diào)度隊列時睦擂,他初始引用計數(shù)為1。可以根據(jù)需要使用dispatch_retaindispatch_release函數(shù)來增加和減少引用計數(shù)崭歧。當隊列的引用計數(shù)為0時,系統(tǒng)異步的釋放(dealloc)隊列撞牢。

保留(retain)和釋放(release)調(diào)度對象(如隊列)非常重要驾荣,以確保它們在被使用時保留在內(nèi)存中。與Cocoa對象的內(nèi)存管理一樣普泡,基本規(guī)則是播掷,如果您打算使用傳遞給您代碼的隊列,在使用之前應(yīng)當保留隊列撼班,在不再需要時釋放隊列歧匈。這個基本模式確保只要您使用隊列,它就在內(nèi)存中砰嘁。

重要提示:您不需要保留或釋放任何全局調(diào)度隊列件炉,包括并發(fā)調(diào)度隊列或主調(diào)度隊列。任何保留和釋放這些隊列的試圖都將被忽略矮湘。

即使您實現(xiàn)一個垃圾回收的應(yīng)用程序斟冕,您也必須保留和釋放您的調(diào)度隊列和其他調(diào)度對象。GCD不支持用于回收內(nèi)存的垃圾回收模型缅阳。

使用隊列存儲自定義上下文信息

所有調(diào)度對象(包括調(diào)度隊列)允許您將自定義上下文數(shù)據(jù)與調(diào)度對象關(guān)聯(lián)磕蛇。要在調(diào)度對象上設(shè)置和獲取這些數(shù)據(jù),可以使用dispatch_set_contextdispatch_get_context函數(shù)十办。系統(tǒng)不會以任何方式使用您的自定義數(shù)據(jù)秀撇,并且由您決定在適當?shù)臅r候分配和釋放數(shù)據(jù)。

對于隊列向族,您可以使用上下文數(shù)據(jù)存儲指向Objective-C對象的指針或者其他數(shù)據(jù)結(jié)構(gòu)呵燕,用來幫助標示隊列或者對代碼的預(yù)期用途。您可以在隊列釋放之前使用隊列的finalizer(終結(jié)器/清理器)函數(shù)將上下文數(shù)據(jù)從隊列中釋放(或者取消關(guān)聯(lián))件相。有關(guān)如何寫finalizer函數(shù)來清理隊列的上下文數(shù)據(jù)再扭,請參閱為隊列提供清理功能氧苍。

為隊列提供清理功能

創(chuàng)建串行調(diào)度隊列后,您可以附加一個finalizer函數(shù)泛范,當隊列釋放時執(zhí)行任意自定義清理候引。調(diào)度隊列是引用計數(shù)對象,您可以使用dispatch_set_finalizer_f函數(shù)來指定一個函數(shù)敦跌,當隊列的引用計數(shù)為0時執(zhí)行澄干。可以使用這個函數(shù)來清理關(guān)聯(lián)到隊列的上下文數(shù)據(jù)柠傍,只要上下文指針不為NULL麸俘,這個函數(shù)就被調(diào)用。

下面代碼展示了一個自定義finalizer函數(shù)和一個創(chuàng)建隊列并設(shè)置finalizer的函數(shù)惧笛。隊列使用finalizer函數(shù)釋放存儲在隊列上下文指針中的數(shù)據(jù)从媚。(代碼中的myInitializeDataContextFunctionmyCleanUpDataContextFunction函數(shù)是自定義函數(shù),提供初始化和清理數(shù)據(jù)結(jié)構(gòu)內(nèi)容功能患整。)傳遞給finalizer函數(shù)的上下文指針包含關(guān)聯(lián)到隊列的數(shù)據(jù)對象拜效。

void myFinalizerFunction(void *context)
{
    MyDataContext* theData = (MyDataContext*)context;
 
    // Clean up the contents of the structure
    myCleanUpDataContextFunction(theData);
 
    // Now release the structure itself.
    free(theData);
}
 
dispatch_queue_t createMyQueue()
{
    MyDataContext*  data = (MyDataContext*) malloc(sizeof(MyDataContext));
    myInitializeDataContextFunction(data);
 
    // Create the queue and set the context data.
    dispatch_queue_t serialQueue = dispatch_queue_create("com.example.CriticalTaskQueue", NULL);
    if (serialQueue)
    {
        dispatch_set_context(serialQueue, data);
        dispatch_set_finalizer_f(serialQueue, &myFinalizerFunction);
    }
 
    return serialQueue;
}

添加任務(wù)到隊列

為了執(zhí)行任務(wù),必須將其分配到適當?shù)恼{(diào)度隊列各谚〗艉叮可以同步或者異步調(diào)度任務(wù),而且可以逐一或分組的調(diào)度它們昌渤。一旦進入隊列赴穗,隊列負責盡快執(zhí)行任務(wù),考慮它們的約束和隊列中已經(jīng)存在的任務(wù)膀息。下面介紹一些將任務(wù)分配到隊列的技術(shù)和它們的優(yōu)點般眉。

添加單個任務(wù)到隊列

有兩種方式添加任務(wù)到隊列:異步和同步。如果有可能的話潜支,使用dispatch_asyncdispatch_async_f函數(shù)異步執(zhí)行優(yōu)先于同步執(zhí)行甸赃。當添加一個塊對象或函數(shù)到隊列,沒有辦法知道代碼什么時候執(zhí)行冗酿。因此埠对,異步添加塊或函數(shù)允許您調(diào)度代碼執(zhí)行并且繼續(xù)在調(diào)用線程中做其他工作。如果從應(yīng)用程序的主線程調(diào)度任務(wù)(可能響應(yīng)一些用戶事件)已烤,這一點尤其重要鸠窗。

雖然應(yīng)該盡可能異步添加任務(wù),但可能仍需要同步添加任務(wù)以防止競爭條件或者其他同步錯誤胯究。在這些情況下,可以使用dispatch_syncdispatch_sync_f函數(shù)添加任務(wù)到隊列躁绸。這些函數(shù)阻塞當前線程執(zhí)行裕循,直到指定的任務(wù)執(zhí)行結(jié)束臣嚣。

重要提示:永遠不要從隊列中執(zhí)行的任務(wù)里調(diào)用dispatch_syncdispatch_sync_f函數(shù) ,且傳遞給函數(shù)同一個隊列剥哑。這對的串行隊列非常重要硅则,它產(chǎn)生了死鎖,對于并發(fā)隊列也應(yīng)該避免株婴。

下面示例展示如何使用基于塊的變量異步和同步調(diào)度任務(wù):

dispatch_queue_t myCustomQueue;
myCustomQueue = dispatch_queue_create("com.example.MyCustomQueue", NULL);
 
dispatch_async(myCustomQueue, ^{
    printf("Do some work here.\n");
});
 
printf("The first block may or may not have run.\n");
 
dispatch_sync(myCustomQueue, ^{
    printf("Do some more work here.\n");
});
printf("Both blocks have completed.\n");

任務(wù)完成時執(zhí)行完成塊

根據(jù)其性質(zhì)怎虫,被調(diào)度到隊列的任務(wù)獨立于創(chuàng)建他們的代碼運行。然而困介,當任務(wù)完成時大审,應(yīng)用程序可能希望被通知該情況,以便它可以合并結(jié)果座哩。對于傳統(tǒng)異步編程徒扶,可能使用回調(diào)機制來做,但對于調(diào)度隊列根穷,可以使用完成塊姜骡。

完成塊是在原始任務(wù)結(jié)束時調(diào)度到隊列的另外一段代碼。當任務(wù)開始時屿良,調(diào)用代碼通常提供完成塊作為參數(shù)圈澈。任務(wù)代碼需要做的是,當它結(jié)束時尘惧,提交指定塊或者函數(shù)到指定隊列士败。

下面代碼展示一個使用塊實現(xiàn)求平均值的函數(shù)。函數(shù)的最后兩個參數(shù)允許調(diào)用者指定隊列和當匯報結(jié)果時用的塊褥伴。求平均值函數(shù)計算其結(jié)果后谅将,傳遞結(jié)果到指定的塊并調(diào)度塊到隊列。為了防止隊列過早的被釋放重慢,在最開始保留隊列并且在完成塊被調(diào)度后釋放隊列是至關(guān)重要的饥臂。

void average_async(int *data, size_t len, dispatch_queue_t queue, void (^block)(int)) {
   // Retain the queue provided by the user to make
   // sure it does not disappear before the completion
   // block can be called.
   dispatch_retain(queue);
 
   // Do the work on the default concurrent queue and then
   // call the user-provided block with the results.
   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      int avg = average(data, len);
      dispatch_async(queue, ^{ block(avg);});
 
      // Release the user-provided queue when done
      dispatch_release(queue);
   });
}

并發(fā)執(zhí)行循環(huán)迭代

在循環(huán)執(zhí)行固定數(shù)量迭代的地方,并發(fā)調(diào)度隊列可能提高其性能似踱。例如隅熙,假設(shè)有個for循環(huán),通過每個循環(huán)迭代做一些工作:

for (i = 0; i < count; i++) {
   printf("%u\n",i);
}

如果每個迭代中執(zhí)行的工作與所有其他迭代中執(zhí)行的工作不同核芽,且循環(huán)完成的順序不重要囚戚。可以使用調(diào)用dispatch_applydispatch_apply_f函數(shù)來替換循環(huán)轧简。這個函數(shù)為每次循環(huán)迭代提交指定塊或函數(shù)到隊列驰坊。當被調(diào)度到并發(fā)隊列時,因此可以同時執(zhí)行多個循環(huán)迭代哮独。

當調(diào)用dispatch_applydispatch_apply_f函數(shù)時拳芙,可以指定一個串行隊列或者并發(fā)隊列察藐。傳入并發(fā)隊列允許您同時執(zhí)行多個循環(huán)迭代,是使用這個函數(shù)最常見的方法舟扎。雖然使用串行隊列是允許的分飞,并為您的代碼做正確的事情,但使用這樣的隊列代替循環(huán)并沒有真正的性能優(yōu)勢睹限。

重要提示:像普通循環(huán)一樣譬猫,dispatch_applydispatch_apply_f函數(shù)不返回,直到所有循環(huán)迭代結(jié)束羡疗。因此染服,當從隊列上下文已經(jīng)執(zhí)行的代碼中調(diào)用它們時,應(yīng)當小心顺囊。如果作為參數(shù)傳遞給函數(shù)的隊列是串行隊列肌索,且與執(zhí)行當前代碼的隊列是同一個隊列,調(diào)用這個方法將會使隊列死鎖特碳。

因為它們直接阻塞當前線程诚亚,所以當從主線程調(diào)用這些函數(shù)時也應(yīng)當小心,它們可能阻止事件處理循環(huán)及時響應(yīng)事件午乓。如果您的代碼需要大量的處理時間站宗,您可能需要從不同的線程調(diào)用這些函數(shù)。

下面代碼顯示如何使用dispath_apply語法替換前面描述的for循環(huán)益愈。傳入到dispath_apply函數(shù)的塊必須包含一個標示當前循環(huán)迭代的參數(shù)梢灭。當塊執(zhí)行時,第一次迭代參數(shù)為0蒸其,第二次為1敏释,等等。最后一次迭代摸袁,參數(shù)的值為count - 1钥顽,count代表迭代的總次數(shù)。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 
dispatch_apply(count, queue, ^(size_t i) {
   printf("%u\n",i);
});

您應(yīng)當確保每次迭代的代碼做合理數(shù)量的工作靠汁。和任何塊或函數(shù)調(diào)度到隊列一樣蜂大,調(diào)度代碼執(zhí)行有開銷。如果每次循環(huán)迭代只執(zhí)行很少量的工作蝶怔,調(diào)度代碼的開銷可能超過調(diào)度到隊列帶來的性能優(yōu)勢奶浦。如果在測試過程中發(fā)現(xiàn)這是真的,您可以使用跨步來增加每次循環(huán)迭代執(zhí)行的工作量踢星。隨著跨步澳叉,將原來循環(huán)的多次迭代組成一個單獨的塊,減少迭代次數(shù)的比例。例如耳高,如果最初執(zhí)行100次迭代扎瓶,但決定使用步幅4所踊,現(xiàn)在每個塊執(zhí)行4次循環(huán)迭代泌枪,迭代次數(shù)是25。有關(guān)如何實現(xiàn)跨步秕岛,請參閱完善循環(huán)代碼

在主線程中執(zhí)行任務(wù)

GCD提供特殊的串行調(diào)度隊列碌燕,可以使用它在應(yīng)用程序的主線程上執(zhí)行任務(wù)。這個隊列被自動的提供給所有應(yīng)用程序继薛,并由在主線程上設(shè)置的運行循環(huán)(被CFRunLoopRef類型或者NSRunLoop對象管理)drain官方文檔上寫的drain修壕,翻譯成銷毀感覺不太恰當,因為drain的意思是"使...流盡","耗盡"的意思遏考,可以理解為使隊列中的任務(wù)全部出隊慈鸠,NSAutoreleasePool也有drain方法,意思相近)灌具。如果您創(chuàng)建的不是Cocoa應(yīng)用程序青团,不要想著顯式的設(shè)置RunLoop,您必須顯式的調(diào)用dispatch_main函數(shù)來drain主調(diào)度隊列咖楣。您仍然可以添加任務(wù)到隊列督笆,但是如果您不調(diào)用此方法,這些任務(wù)永遠不會執(zhí)行诱贿。

可以通過調(diào)用dispatch_get_main_queue函數(shù)獲取應(yīng)用程序主線程的調(diào)度隊列娃肿。添加到這個隊列的任務(wù)在主線程上被串行執(zhí)行。因此珠十,可以使用這個隊列作為應(yīng)用程序其他部分工作執(zhí)行完成的同步點料扰。

在任務(wù)中使用Objective-C對象

GCD提供內(nèi)置的支持Cocoa內(nèi)存管理技術(shù),所以焙蹭,您可以自由的在提交到隊列的塊中使用Objective-C對象晒杈。每個調(diào)度隊列維護自己的自動釋放池來確保自動釋放對象在一些點被釋放;隊列不保證這些對象何時真正釋放壳嚎。

如果您的應(yīng)用程序內(nèi)存不足桐智,且您的塊創(chuàng)建超過幾個自動釋放對象,創(chuàng)建自己的自動釋放池是唯一的方法來確保您的對象被及時釋放烟馅。如果您的塊創(chuàng)建上百個對象说庭,您可能希望創(chuàng)建多個自動釋放池或定期drain自動釋放池。

關(guān)于自動釋放池和Objective-C內(nèi)存管理的更多信息郑趁,請參閱高級內(nèi)存管理編程指南

暫停和恢復(fù)隊列

您可以通過暫停隊列來臨時阻止隊列執(zhí)行塊對象刊驴。使用dispatch_suspend函數(shù)來暫停調(diào)度隊列,使用dispatch_resume函數(shù)來恢復(fù)調(diào)度隊列。調(diào)用dispatch_suspend增加隊列的暫停引用計數(shù)捆憎,調(diào)用dispatch_resume減少引用計數(shù)舅柜。當引用計數(shù)大于0時,隊列保持掛起躲惰。因此致份,為了恢復(fù)處理塊,您必須使用一個配對的恢復(fù)調(diào)用平衡所有暫停調(diào)用础拨。

重要提示:暫停和恢復(fù)調(diào)用是異步的氮块,僅在執(zhí)行塊之間生效。暫停一個隊列不會導(dǎo)致已經(jīng)執(zhí)行的塊停止诡宗。

使用調(diào)度信號量來調(diào)節(jié)有限資源的使用

如果提交到調(diào)度隊列的任務(wù)訪問一些有限的資源滔蝉,您可能需要使用調(diào)度信號來調(diào)節(jié)同時訪問資源的任務(wù)數(shù)量。調(diào)度信號像普通信號一樣工作塔沃,但有一個例外蝠引。當資源可用時,它獲取調(diào)度信號量消耗的時間比獲取傳統(tǒng)系統(tǒng)信號量消耗的時間少蛀柴。這是因為GCD在這種特殊情況下不調(diào)用內(nèi)核螃概。只有當資源不可用且系統(tǒng)需要停駐線程直到向信號量發(fā)出信號時才調(diào)用內(nèi)核。

使用信號量語義如下:

  1. 當創(chuàng)建信號量時(使用dispatch_semaphore_create函數(shù))名扛,您可以指定一個正數(shù)谅年,表示可用資源的數(shù)量。
  2. 在每個任務(wù)中肮韧,調(diào)用dispatch_semaphore_wait函數(shù)等待信號融蹂。
  3. 當?shù)却祷貢r,獲取資源弄企,執(zhí)行工作超燃。
  4. 當資源使用完畢時,釋放資源并調(diào)用dispatch_semaphore_signal函數(shù)向信號量發(fā)出信號拘领。

有關(guān)這些步驟如何工作意乓,例如,考慮在系統(tǒng)上使用文件描述符约素,每個應(yīng)用程序被給予有限數(shù)量的文件描述符來使用届良。如果您有一個處理大量文件的任務(wù),您不想一次打開這么多的文件圣猎,這樣會耗盡文件描述符士葫。您可以在文件處理代碼中使用信號量限制任何時候文件描述符一次使用的數(shù)量∷突冢可能在您任務(wù)中添加的代碼基本片段如下:

// Create the semaphore, specifying the initial pool size
dispatch_semaphore_t fd_sema = dispatch_semaphore_create(getdtablesize() / 2);
 
// Wait for a free file descriptor
dispatch_semaphore_wait(fd_sema, DISPATCH_TIME_FOREVER);
fd = open("/etc/services", O_RDONLY);
 
// Release the file descriptor when done
close(fd);
dispatch_semaphore_signal(fd_sema);

當創(chuàng)建一個信號量時慢显,指定可用資源數(shù)量爪模。這個值將成為信號量計數(shù)的初始值。每次等待信號荚藻,dispatch_semaphore_wait函數(shù)將計數(shù)變量減1屋灌。如果結(jié)果值為負數(shù),函數(shù)告訴內(nèi)核阻塞線程应狱。另外一邊共郭,dispatch_semaphore_signal函數(shù)將計數(shù)變量增加1,指示資源已經(jīng)被釋放侦香。如果有被阻塞且等待資源的任務(wù)落塑,他們其中的一個隨后變?yōu)榉亲枞⒃试S工作纽疟。

等待排隊任務(wù)組

調(diào)度組是阻塞線程直到一個或者多個任務(wù)結(jié)束執(zhí)行的方法罐韩。您可以在不能夠獲取進度直到所有指定任務(wù)結(jié)束的地方使用這種行為。例如污朽,調(diào)度幾個任務(wù)來計算一些數(shù)據(jù)散吵,您可能使用一個組來等待這些任務(wù),然后當它們結(jié)束時處理結(jié)果蟆肆。使用調(diào)度組的另外一種方法是替代線程連接矾睦。您可能添加相應(yīng)的任務(wù)到調(diào)度組且等待整個組,而不是開啟幾個子線程然后連接它們炎功。

下面代碼顯示創(chuàng)建一個組枚冗,調(diào)度任務(wù)給它,并等待結(jié)果蛇损。使用dispatch_group_async函數(shù)赁温,而不是使用dispatch_async函數(shù)調(diào)度任務(wù)到隊列。這個函數(shù)關(guān)聯(lián)任務(wù)到組淤齐,將它們排隊執(zhí)行股囊。為了等待任務(wù)組結(jié)束,稍后使用dispatch_group_wait函數(shù)更啄,傳遞相應(yīng)的組進去稚疹。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
 
// Add a task to the group
dispatch_group_async(group, queue, ^{
   // Some asynchronous work
});
 
// Do some other work while the tasks execute.
 
// When you cannot make any more forward progress,
// wait on the group to block the current thread.
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
 
// Release the group when it is no longer needed.
dispatch_release(group);

注意:可以使用dispatch_group_notify或者dispatch_group_notify_f函數(shù)來通知關(guān)聯(lián)到組的調(diào)度隊列執(zhí)行完畢。也就是說當調(diào)度到隊列的塊都執(zhí)行完畢的時候祭务,會執(zhí)行dispatch_group_notify或者dispatch_group_notify_f函數(shù)内狗。

調(diào)度隊列和線程安全

在調(diào)度隊列的內(nèi)容中討論線程安全可能看起來很奇怪,但線程安全仍然是相關(guān)聯(lián)的話題义锥。任何時候在應(yīng)用程序中實現(xiàn)并發(fā)柳沙,有幾件事情都應(yīng)該知道:

  • 調(diào)度隊列自身是線程安全的。換句話說缨该,您可以從系統(tǒng)的任何線程提交任務(wù)到調(diào)度隊列偎行,而不用先使用鎖或者同步訪問隊列。
  • 不要從隊列中執(zhí)行的任務(wù)里調(diào)用dispatch_sync函數(shù) ,且傳遞給函數(shù)同一個隊列蛤袒。這么做會導(dǎo)致隊列死鎖熄云。如果您需要調(diào)度到當前隊列,異步使用dispatch_async函數(shù)妙真。
  • 避免在提交給調(diào)度隊列的任務(wù)中使用鎖缴允。雖然在任務(wù)中使用鎖是安全的,當您獲取鎖時珍德,如果鎖不可用练般,可能阻塞整個串行隊列。相同的锈候,對于并發(fā)隊列薄料,等待鎖可能阻止其他線程執(zhí)行。如果您需要同步部分代碼泵琳,使用串行調(diào)度隊列代替鎖摄职。
  • 雖然您可以獲取關(guān)于底層線程運行任務(wù)的信息,最好避免這么做获列。有關(guān)調(diào)度隊列和線程兼容性的更多信息谷市,請參閱POSIX線程的兼容性

有關(guān)如何更改現(xiàn)有線程代碼到使用調(diào)度隊列的更多提示,請參閱線程遷移击孩。

參考:

https://developer.apple.com/library/content/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW1

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末迫悠,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子巩梢,更是在濱河造成了極大的恐慌创泄,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件且改,死亡現(xiàn)場離奇詭異验烧,居然都是意外死亡,警方通過查閱死者的電腦和手機又跛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門碍拆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人慨蓝,你說我怎么就攤上這事感混。” “怎么了礼烈?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵弧满,是天一觀的道長。 經(jīng)常有香客問我此熬,道長庭呜,這世上最難降的妖魔是什么滑进? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮募谎,結(jié)果婚禮上扶关,老公的妹妹穿的比我還像新娘。我一直安慰自己数冬,他們只是感情好节槐,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拐纱,像睡著了一般铜异。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上秸架,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天揍庄,我揣著相機與錄音,去河邊找鬼咕宿。 笑死币绩,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的府阀。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼芽突,長吁一口氣:“原來是場噩夢啊……” “哼试浙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起寞蚌,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤田巴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后挟秤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體壹哺,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年艘刚,在試婚紗的時候發(fā)現(xiàn)自己被綠了管宵。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡攀甚,死狀恐怖箩朴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情秋度,我是刑警寧澤炸庞,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站荚斯,受9級特大地震影響埠居,放射性物質(zhì)發(fā)生泄漏查牌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一滥壕、第九天 我趴在偏房一處隱蔽的房頂上張望僧免。 院中可真熱鬧,春花似錦捏浊、人聲如沸懂衩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浊洞。三九已至,卻和暖如春胡岔,著一層夾襖步出監(jiān)牢的瞬間法希,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工靶瘸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留苫亦,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓怨咪,卻偏偏與公主長得像屋剑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子诗眨,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

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