章節(jié)目錄
- 什么是GCD感猛?
- 如何在多條路徑中執(zhí)行CPU命令列唱遭?
- 即使多線程存在很多問題(如數(shù)據(jù)競爭拷泽、死鎖、線程過多消耗大量內(nèi)存)拆吆,為何還要使用多線程枣耀?
- Dispatch Queue
- dispatch_set_target_queue
- dispatch_after
- dispatch_time
- dispatch_walltime
- Dispatch Group
- dispatch_group_wait
- dispatch_barrier_async
- dispatch_sync 與 dispatch_async
- dispatch_apply
- dispatch_suspend / dispatch_resume
- Dispatch Semaphore
- dispatch_once
- Dispatch I/0
- GCD實現(xiàn)
- 參考資料
正文:
-
什么是GCD捞奕?
GCD是異步執(zhí)行任務(wù)的技術(shù)之一颅围。
一般應(yīng)用程序中記述的線程管理用的代碼在系統(tǒng)中實現(xiàn)恨搓。
所以我們只需定義想執(zhí)行的任務(wù)并追加到適當(dāng)?shù)腄ispatch Queue中斧抱,GCD就能生成必要的線程并計劃執(zhí)行任務(wù)辉浦。
在導(dǎo)入GCD之前,Cocoa框架提供了NSThread等簡單的多線程編程技術(shù)眉睹,但通過源碼對比可知GCD更為簡潔竹海,執(zhí)行效率也更高丐黄。
一個CPU一次只能執(zhí)行一個命令,不能執(zhí)行某處分開的并列的兩個命令坏瞄,因此通過CPU執(zhí)行的CPU命令列就好比一條無分叉的大道鸠匀,其執(zhí)行不會出現(xiàn)分歧逾柿。
而在OC的if等控制語句或函數(shù)調(diào)用的情況下机错,執(zhí)行命令列的地址會遠離當(dāng)前的位置(位置遷移)
一個CPU執(zhí)行的CPU命令列為一條無分叉路徑弱匪,即為線程萧诫。
多線程即為多條路徑。多線程中哑诊,1個CPU核執(zhí)行多條不同路徑上的不同命令。
一個64核的CPU芯片有64個CPU提茁。
-
一個CPU核一次能夠執(zhí)行的CPU命令始終為1茴扁,那如何在多條路徑中執(zhí)行CPU命令列汪疮?
OSX和iOS的核心XNU內(nèi)核在發(fā)生操作系統(tǒng)事件時會切換執(zhí)行路徑(如每隔一定時間智嚷,喚起系統(tǒng)調(diào)用等情況)盏道。
執(zhí)行中路徑的狀態(tài),如CPU的寄存器等信息保存到各自路徑專用的內(nèi)存塊中嫁艇,從切換目標路徑專用的內(nèi)存塊中弦撩,復(fù)原CPU寄存器等信息益楼,繼續(xù)執(zhí)行切換路徑的CPU命令列偏形。這被稱為”上下文切換”。
由于使用多線程的程序可以在某個線程和其他線程之間反復(fù)多次進行上下文切換队橙,因此看上去就好像一個CPU核能夠并列地執(zhí)行多個線程一樣捐康。
但具有多個CPU核時解总,就不是看上去像了花枫,而是真的多個CPU核并行執(zhí)行多個線程的技術(shù)掏膏。
這種利用多線程編程的技術(shù)就被稱為多線程編程馒疹。
-
即使多線程存在很多問題(如數(shù)據(jù)競爭颖变、死鎖腥刹、線程過多消耗大量內(nèi)存),為何還要使用多線程漓雅?
多線程編程可保證應(yīng)用程序的響應(yīng)性能邻吞。
主線程是應(yīng)用程序啟動時最先執(zhí)行的線程抱冷,負責(zé)描繪用戶界面旺遮、處理觸摸屏幕的事件等。
若在主線程進行長時間的處理边翼,就會妨礙主線程的執(zhí)行组底,從而導(dǎo)致不能應(yīng)用程序的畫面沒有更新而長時間停滯等問題债鸡。
Dispatch Queue : 執(zhí)行處理的等待隊列厌均。根據(jù)FIFO執(zhí)行操作的處理告唆。
在執(zhí)行處理時存在兩種Dispatch Queue擒悬,一種是等待現(xiàn)在執(zhí)行中的處理的Serial Dispatch Queue(同步)茄螃,另一種是不等待現(xiàn)在執(zhí)行中處理的Concurrent Dispatch Queue(異步)归苍。
并行執(zhí)行拼弃,使用多個線程同時執(zhí)行多個處理吻氧。
但是并行執(zhí)行的處理數(shù)量取決于當(dāng)前系統(tǒng)的狀態(tài)(Concurrent Dispatch Queue)盯孙。
iOS和OS X的核心 —— XNU內(nèi)核決定應(yīng)當(dāng)使用的線程數(shù)振惰,并只生成所需的線程執(zhí)行處理骑晶。
當(dāng)處理結(jié)束,應(yīng)當(dāng)執(zhí)行的處理數(shù)減少時匙头,XNU內(nèi)核會結(jié)束不再需要的線程(中間會有個放入線程緩存池的操作)蹂析。
-
如何才能得到Dispatch Queue识窿?
兩種方法喻频。
第一種
通過CGD的API: dispatch_queue_create函數(shù)生成Dispatch Queue
一個Serial Dispatch Queue 同時只能執(zhí)行一個追加處理甥温,但使用 dispatch_queue_create函數(shù)可生成任意多個Dispatch Queue姻蚓。
當(dāng)生成多個Serial Dispatch Queue時狰挡,各個Serial Dispatch Queue將并行執(zhí)行释涛。雖然一個隊列只能生成一個處理唇撬,但可以有多個隊列同時進行一個處理窖认,即為同時執(zhí)行多個處理。
但不可生成太多燕偶,否則會消耗大量內(nèi)存杭跪,引起大量的上下文切換涧尿,大幅降低系統(tǒng)的響應(yīng)性能姑廉。
dispatch_queue_create函數(shù)桥言,第一個參數(shù)指定Serial Dispatch Queue的名稱号阿。
第二個參數(shù)若為NULL則生成的是Serial Dispatch Queue鸳粉,若為DISPATCH_QUEUE_CONCURRENT則生成的是Concurrent Dispatch Queue届谈。
生成的Dispatch Queue必須由程序員負責(zé)釋放艰山,通過dispatch_release(隊列名)釋放曙搬∽葑埃可
通過dispatch_retain(隊列名)增加引用搂擦。
即Dispatch Queue也像OC的引用計數(shù)式內(nèi)存管理一樣瀑踢,需通過dispatch_retain函數(shù)和dispatch_release函數(shù)的引用計數(shù)來管理內(nèi)存橱夭。
如圖所示棘劣,在dispatch_async函數(shù)中追加Block到Concurrent Dispatch Queue首昔,并立即通過dispatch_release函數(shù)進行釋放勒奇。則該Block通過dispatch_retain函數(shù)持有Dispatch Queue赊颠。無論Serial Dispatch Queue還是Concurrent Dispatch Queue都一樣劈彪。
于是在dispatch_async函數(shù)中追加Block到Dispatch Queue后竣蹦,即使立即釋放Dispatch Queue,該Dispatch Queue由于被Block所持有也不會被廢棄沧奴,因而Block能夠執(zhí)行痘括。Block執(zhí)行結(jié)束后會釋放Dispatch Queue,此時誰都不持有Dispatch Queue滔吠,它也因此會被廢棄远寸。
第二種
獲取系統(tǒng)標準提供的Dispatch Queue(Main Dispatch Queue 和 Global Dispatch Queue)
Main Dispatch Queue是在主線程中執(zhí)行的Dispatch Queue。因主線程只有一個屠凶,所以其自然是Serial Dispatch Queue。
追加到Main Dispatch Queue的處理在主線程的RunLoop中執(zhí)行矗愧。所以將用戶界面的界面更新等一些必須在主線程中執(zhí)行的處理追加到Main Dispatch Queue使用灶芝。
Global Dispatch Queue 是所有應(yīng)用程序都能夠使用的Concurrent Dispatch Queue。
其有四個執(zhí)行優(yōu)先級唉韭,分別是高夜涕、默認、低属愤、后臺女器。
通過XNU內(nèi)核管理的用于Global Dispatch Queue的線程,將各自使用的Global Dispatch Queue的執(zhí)行優(yōu)先級作為線程的優(yōu)先級使用住诸。
所以在使用時驾胆,應(yīng)注意優(yōu)先級的選擇涣澡,但通過XNU內(nèi)核用于Global Dispatch Queue的線程并不能保證實時性,因此執(zhí)行優(yōu)先級只是大致的判斷丧诺。
列如在處理內(nèi)容的執(zhí)行可有可無時入桂,使用后臺優(yōu)先級的這種。
Main Dispatch Queue的獲炔笛帧:
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
Global Dispatch Queue(高優(yōu)先級)的獲取方法
dispatch queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HEIGH,0);
Global Dispatch Queue(默認優(yōu)先級)的獲取方法
dispatch queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
Global Dispatch Queue(低優(yōu)先級)的獲取方法
dispatch queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_Low,0);
Global Dispatch Queue(后臺優(yōu)先級)的獲取方法
dispatch queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);
對Main Dispatch Queue 和 Global Dispatch Queue執(zhí)行dispatch_retain 和 dispatch_release函數(shù)不會引起任何變化抗愁,也不會有任何問題。
這也是使用其會更輕松的原因呵晚。
dispatch_get_global_queue的第二個參數(shù)蜘腌,官方解釋為留待未來使用,非0就可能返回NULL
-
dispatch_set_target_queue
dispatch_queue_create函數(shù)生成的Dispatch Queue 不管是Serial Dispatch Queue 還是 Concurrent Dispatch Queue饵隙,其使用的線程優(yōu)先級都與Global Dispatch Queue的優(yōu)先級相同逢捺。而變更優(yōu)先級就使用dispatch_set_target_queue函數(shù)。
代碼演示:
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create(“DrunkenMouse”,NULL);
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(mySerialDispatchQueue,globalDispatchQueueBackground);
第一個參數(shù)的Dispatch Queue的優(yōu)先級會修改為與第二個Dispatch Queue的優(yōu)先級相同癞季。
但第一個參數(shù)若為Main Dispatch Queue 和 Global Dispatch Queue則不知道會出現(xiàn)什么狀況劫瞳,所以不可指定。
-
dispatch_after
指定多少時間后追加操作到Dispatch Queue绷柒,而不是指定時間后執(zhí)行處理志于。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,3ull * NSEC_PER_SEC);
dispatch_after(time,dispatch_get_main_queue(),^{
NSLog(@“wait at least 3 seconds”);
});
此源碼意為3秒后追加Block操作到Main Dispatch Queue。
-
dispatch_time
dispatch_time函數(shù)獲取從第一個參數(shù)指定的時間開始到第二個參數(shù)指定的毫微妙單位時間后的時間废睦。
第一個參數(shù)是指定時間用的dispatch_time_t類型的值伺绽,使用dispatch_time函數(shù)或dispatch_walltime函數(shù)生成
第二個參數(shù)中ull * NSEC_PER_SEC代表秒,使用NSEC_PER_MSEC代表毫秒
如150毫秒:150ull * NSEC_PER_MESC
ull是C語言的數(shù)值字面量嗜湃,表示unsigned long long
-
dispatch_walltime
dispatch_walltime函數(shù)用于計算絕對時間奈应。比如想指定在X年X月X日X時X分X秒這一絕對時間。
-
Dispatch Group
用于將多個Dispatch Queue 添加到同一個Dispatch Group, 待Dispatch Queue全部執(zhí)行完畢后執(zhí)行某項操作购披。(Serial Dispatch Queue或 Concurrent Dispatch Queue皆可)
dispatch_group_t group = dispatch_group_create(); 生成group
dispatch_group_async(group, queue, ^{NSLog(@“DrunkenMouse”)杖挣;}); 將操作添加到隊列queue刚陡,將隊列queue添加到組group中
dispatch_group_async(group, queue, ^{NSLog(@“DrunkenMouse2”)惩妇;}); 組中可添加多個queue
dispatch_group_notify(group,dispatch_get_main_queue(),^{NSLog(@“done”);}); 通過notify監(jiān)聽group筐乳,在group中所有操作執(zhí)行完畢后歌殃,將第三個參數(shù)的Block添加到第二個參數(shù)的Dispatch Queue。
dispatch_release(group);釋放組
dispatch_group_create函數(shù)生成dispatch_group_t類型的Dispatch Group蝙云。
該Dispatch Group與Dispatch Queue相同氓皱,在使用結(jié)束后通過dispatch_release函數(shù)釋放。
dispatch_group_async函數(shù)將Block追加到指定的Dispatch Queue,Block屬于指定的Dispatch Group波材。
所以Block通過dispatch_retain函數(shù)持有Dispatch Group股淡,于是Block執(zhí)行結(jié)束,該Block就通過dispatch_release函數(shù)釋放持有的Dispatch Group各聘。
一旦Dispatch Group使用結(jié)束,不用考慮Block抡医,立即通過Dispatch_release函數(shù)釋放即可躲因。
-
dispatch_group_wait
在Dispatch Group中使用dispatch_group_wait函數(shù)僅等待全部處理執(zhí)行結(jié)束。
dispatch_group_t group = dispatch_group_create(); 生成group
dispatch_group_async(group, queue, ^{NSLog(@“DrunkenMouse”)忌傻;})大脉; 將操作添加到隊列queue,將隊列queue添加到組group中
dispatch_group_async(group, queue, ^{NSLog(@“DrunkenMouse2”)水孩;})镰矿; 組中可添加多個queue
dispatch_group_wait(group,DISPATCH_TIME_FOREVER);
第二個參數(shù)為等待的時間,屬于dispatch_time_t類型的值俘种。DISPATCH_TIME_FOREVER意味永久等待秤标,只要group中的處理尚未結(jié)束就一直等待。
等待的時間也可以為1秒
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,1ull * NSEC_PER_SEC);
long result = dispatch_group_wait(group,time);
if(result == 0) {
//屬于Dispatch Group的全部處理執(zhí)行結(jié)束
}else {
//屬于Dispatch Group的某一個處理還在執(zhí)行中
}
等待意味著一旦調(diào)用dispatch_group_wait函數(shù)宙刘,該函數(shù)就處于調(diào)用的狀態(tài)而不返回苍姜。
在經(jīng)過指定時間或Dispatch Group的處理全部執(zhí)行結(jié)束之前,執(zhí)行該函數(shù)的線程停止悬包。
若不等待則可以使用DISPATCH_TIME_NOW:
long result = dispatch_group_wait(group,DISPATCH_TIME_NOW);
-
dispatch_barrier_async
dispatch_barrier_async 柵欄衙猪,可保證寫入時不會讀取,讀取時不會寫入布近。
進而可將寫入操作放到Serial Dispatch Queue中垫释,讀取操作放入Serial Dispatch Queue,以提高性能撑瞧。
dispatch_async(queue,blk1_reading);
dispatch_async(queue,blk2_reading);
dispatch_barrier_async(queue,blk3_writing);
dispatch_async(queue,blk4_reading);
dispatch_barrier_async函數(shù)會等到目前為止的并行都處理結(jié)束后棵譬,再去執(zhí)行此操作,而在執(zhí)行該寫入操作時禁止再執(zhí)行別的操作预伺。
-
dispatch_sync 與 dispatch_async
dispatch_async 異步茫船,將指定的Block”非同步”的追加到指定的Dispatch Queue,dispatch_async函數(shù)不做任何等待扭屁。
dispatch_sync 同步算谈,將指定的Block”同步”追加到指定的Dispatch Queue中,在追加Block結(jié)束之前料滥,dispatch_sync函數(shù)會一直等待然眼。
等待的意思同dispatch_group_wait,該線程直到處理結(jié)束才會返回葵腹。
同步主隊列操作會導(dǎo)致死鎖高每。也就是dispatch_sync(dispatch_get_main_queue(),blk1);
死鎖原因:Main Dispatch Queue會等待主線程中操作執(zhí)行執(zhí)行完畢再執(zhí)行該操作屿岂。于是主線程等待該操作結(jié)束才繼續(xù)執(zhí)行,該操作又在等待主線程操作結(jié)束才能執(zhí)行鲸匿。
同一個隊列爷怀,異步操作里嵌套同步操作會發(fā)生Crash。(XCode8 測試)
-
dispatch_apply
該函數(shù)按指定的次數(shù)將指定的Block追加到指定的Dispatch Queue中带欢,并等待全部處理執(zhí)行結(jié)束运授。
第二個參數(shù)為重復(fù)次數(shù),第二個參數(shù)為添加到的隊列乔煞,第三個參數(shù)為追加的處理
dispatch_apply(10,queue,blk1);
NSLog(@“DrunkenMouse”);
blk1添加10此到隊列queue吁朦,待操作全都執(zhí)行完畢后輸出DrunkenMouse
第三個參數(shù)Block為帶有參數(shù)的Block,這是為了區(qū)分添加的Block所用渡贾。
由于dispatch_apply函數(shù)與dispatch_sync函數(shù)相同逗宜,會等待處理執(zhí)行結(jié)束。
因此推薦在dispatch_async函數(shù)非同步的執(zhí)行dispatch_apply函數(shù)空骚。
-
dispatch_suspend / dispatch_resume
dispatch_suspend(queue)將指定的Dispatch Queue掛起纺讲。
即不執(zhí)行追加到該Dispatch Queue中未執(zhí)行的處理,但已經(jīng)執(zhí)行的處理不受影響囤屹。
dispatch_resume(queue)恢復(fù)指定的Dispatch Queue
-
Dispatch Semaphore
數(shù)據(jù)信號燈刻诊。當(dāng)計數(shù)為0時等待,計數(shù)為1或大于1時牺丙,減去1而不等待则涯。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
參數(shù)表示計數(shù)的初始值,由create可看出冲簿,同樣需dispatch_release函數(shù)釋放粟判。可通過dispatch_retain函數(shù)持有峦剔。
dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
該函數(shù)等待Dispatch Semaphore的計數(shù)值大于或等于1档礁。當(dāng)滿足時計數(shù)減一并從dispatch_semaphore_wait函數(shù)返回。
返回0代表計數(shù)值大于或等于1吝沫,計數(shù)減一呻澜。否則就是沒返回值。
第二個參數(shù)與dispatch_group_wait函數(shù)等相同惨险,由dispatch_time_t類型值指定等待時間羹幸。
計數(shù)值的加1通過dispatch_semaphore_signal函數(shù)
-
dispatch_once
一次執(zhí)行。在程序運行階段辫愉,其函數(shù)只會執(zhí)行一次栅受。
-
Dispatch I/0
在讀取較大文件時,可將文件分成合適的大小并使用Global Dispatch Queue并列讀取來提升速度。實現(xiàn)這一功能的就是Dispatch I/O 和 Dispatch Data
-
GCD實現(xiàn)
編程人員所使用GCD的API全部為包含在libdispatch 庫中的C語言函數(shù)屏镊。
Dispatch Queue通過結(jié)構(gòu)體和鏈表依疼,被實現(xiàn)為FIFO隊列。
FIFO隊列管理是通過dispatch_async等函數(shù)所追加的Block而芥。
Block并不是直接加入FIFO隊列律罢,而是先加入Dispatch Continuation這一dispatch_continuation_t類型結(jié)構(gòu)體中,然后再加入FIFO隊列棍丐。
該DispatchContinuation用于記憶Block所屬的DispatchGroup和其他一些信息误辑,相當(dāng)于一般常說的執(zhí)行上下文。
Global Dispatch Queue有8種:
HighPriority DefaultPriority LowPriority BackgroundPriority
HighOvercommitPriority DefaultOvercommitPriority LowOvercommitPriority BackgroundOvercommitPriority
優(yōu)先級中附有Overcommit的Global Dispatch Queue使用在SerialDispatchQueue中骄酗。
如Overcommit這個名稱所示稀余,不管系統(tǒng)狀態(tài)如何悦冀,都會強制生成線程的DispatchQueue
這八種Global Dispatch Queue各使用一個pthread_workqueue.
GCD初始化時趋翻,使用pthread_workqueue_create_np函數(shù)生成pthread_workqueue.
pthread_workqueue包含在Libc提供的Pthreads API中。其使用bsdthread_register和workq_open系統(tǒng)調(diào)用盒蟆,在初始化XNU內(nèi)核的work queue之后獲取workqueue信息
XNU內(nèi)核持有4種work queue
WORKQUEUE_HIGH_PRIOQUEUE
WORKQUEUE_DEFAULT_PRIOQUEUE
WORKQUEUE_LOW_PRIOQUEUE
WORKQUEUE_BG_PRIOQUEUE
Dispatch Queue中執(zhí)行Block的過程踏烙。當(dāng)在Global Dispatch Queue 中執(zhí)行Block時,libdispatch從Global Dispatch Queue自身的FIFO隊列中取出Dispatch Continuation历等,調(diào)用pthread_workqueue_additem_np函數(shù)讨惩。將該Global Dispatch Queue自身、符合其優(yōu)先級的work queue信息以及為執(zhí)行Dispatch Continuation的回調(diào)函數(shù)等傳遞給參數(shù)寒屯。
pthread_workqueue_additem_np函數(shù)使用workq_kernreturn系統(tǒng)調(diào)用荐捻,通知work queue增加應(yīng)當(dāng)執(zhí)行的項目。根據(jù)該通知寡夹,XNU內(nèi)核基于系統(tǒng)狀態(tài)判斷是否要生成線程处面,如果是Overcommit優(yōu)先級的Global Dispatch Queue,workqueue則始終生成線程菩掏。
work queue的線程執(zhí)行pthread_workqueue函數(shù)魂角,該函數(shù)調(diào)用libdispatch的回調(diào)函數(shù)。在該回調(diào)函數(shù)中執(zhí)行加入到DispatchContinuation的Block
Block執(zhí)行結(jié)束后智绸,進行通知DispatchGroup結(jié)束野揪、釋放DispatchContinuation等處理,開始準備執(zhí)行加入到GlobalDispatchQueue中的下一個Block瞧栗。
參考
- Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理
- 簡書博客