iOS開發(fā)探索多線程GCD隊(duì)列示例詳解
引言
在iOS開發(fā)過(guò)程中,繞不開網(wǎng)絡(luò)請(qǐng)求、下載圖片之類的耗時(shí)操作,這些操作放在主線程中處理會(huì)造成卡頓現(xiàn)象,所以我們都是放在子線程進(jìn)行處理,處理完成后再返回到主線程進(jìn)行展示。
多線程貫穿了我們整個(gè)的開發(fā)過(guò)程,iOS的多線程操作有NSThread、GCD、NSOperation,其中我們最常用的就是GCD。
進(jìn)程與線程
在了解GCD之前我們先來(lái)了解一下進(jìn)程和線程,及他們的聯(lián)系與區(qū)別
1.進(jìn)程的定義
- 進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序
- 每個(gè)進(jìn)程之間是獨(dú)立的,每個(gè)進(jìn)程均運(yùn)行在其專用的且受保護(hù)的內(nèi)存空間內(nèi)
- 進(jìn)程間通信一般使用URL Scheme、UIPasteboard、Keychain、UIActivityViewController等
2.線程的定義
- 線程是進(jìn)程的基本執(zhí)行單元,一個(gè)進(jìn)程的所有任務(wù)都在線程中執(zhí)行
- 進(jìn)程想要執(zhí)行任務(wù),必須得有線程,進(jìn)程至少要有一條線程
- 程序啟動(dòng)會(huì)默認(rèn)開啟一條線程,這條線程被成為主線程
- 線程之間的通信一般使用performSelector
3、 進(jìn)程和線程的關(guān)系
- 1.線程是進(jìn)程的執(zhí)行單元,進(jìn)程的所有任務(wù)都在線程中執(zhí)行
- 2.線程是 CPU 分配資源和調(diào)度的最小單位
- 3.手機(jī)中一個(gè)程序?qū)?yīng)一個(gè)進(jìn)程,一個(gè)進(jìn)程中可有多個(gè)線程,但至少要有一條線程
- 4.同一個(gè)進(jìn)程內(nèi)的線程共享進(jìn)程資源
4、 多線程
同一時(shí)間,CPU只能處理1條線程,只有1條線程在執(zhí)行。多線程并發(fā)執(zhí)行,其實(shí)是CPU快速地在多條線程之間調(diào)度(切換)。如果CPU調(diào)度線程的時(shí)間足夠快,就造成了多線程并發(fā)執(zhí)行的假象
如果線程非常非常多,CPU會(huì)在N多線程之間調(diào)度,消耗大量的CPU資源,每條線程被調(diào)度執(zhí)行的頻次會(huì)降低(線程的執(zhí)行效率降低)
多線程的優(yōu)點(diǎn):
- 1.能適當(dāng)提高程序的執(zhí)行效率;
- 2.能適當(dāng)提高資源利用率(CPU、內(nèi)存利用率) 3.線程上的任務(wù)執(zhí)行完成后,線程會(huì)自動(dòng)銷毀
多線程的缺點(diǎn):
- 1.開啟線程需要占用一定的內(nèi)存空間(默認(rèn)情況下,主線程占用1M,子線程占用512KB),如果開啟大量的線程,會(huì)占用大量的內(nèi)存空間,降低程序的性能,開啟線程大概需要90微秒的時(shí)間
- 2.線程越多,每個(gè)線程被調(diào)度的次數(shù)就越低,線程的執(zhí)行效率就越低,CPU在調(diào)度線程上的開銷越大
- 3.程序設(shè)計(jì)更加復(fù)雜,比如線程之間的通信、多線程的數(shù)據(jù)共享
多線程的生命周期是:新建 - 就緒 - 運(yùn)行 - 阻塞 - 死亡
- 新建:實(shí)例化線程對(duì)象
- 就緒:向線程對(duì)象發(fā)送start消息,線程對(duì)象被加入可調(diào)度線程池等待CPU調(diào)度。
- 運(yùn)行:CPU 負(fù)責(zé)調(diào)度可調(diào)度線程池中線程的執(zhí)行。線程執(zhí)行完成之前,狀態(tài)可能會(huì)在就緒和運(yùn)行之間來(lái)回切換。就緒和運(yùn)行之間的狀態(tài)變化由CPU負(fù)責(zé),程序員不能干預(yù)。
- 阻塞:當(dāng)滿足某個(gè)預(yù)定條件時(shí),可以使用休眠或鎖,阻塞線程執(zhí)行。sleepForTimeInterval(休眠指定時(shí)長(zhǎng)),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥鎖)。
- 死亡:正常死亡,線程執(zhí)行完畢。非正常死亡,當(dāng)滿足某個(gè)條件后,在線程內(nèi)部中止執(zhí)行/在主線程中止線程對(duì)象
5、 時(shí)間片
時(shí)間片:CPU在多個(gè)任務(wù)直接進(jìn)行快速的切換,這個(gè)時(shí)間間隔就是時(shí)間片
設(shè)備并發(fā)執(zhí)行的數(shù)量是有限的,使用[NSProcessInfo processInfo].activeProcessorCount可以查看當(dāng)前設(shè)備能夠支持線程的最大并發(fā)數(shù)量,比如說(shuō)最大并發(fā)數(shù)是8,代表8核cpu,如果同時(shí)開啟了10個(gè)線程,則會(huì)有CPU通過(guò)時(shí)間片輪轉(zhuǎn)的方式讓某一個(gè)或者某兩個(gè)線程分別執(zhí)行一段時(shí)間。
6、 線程池
GCD在內(nèi)部維護(hù)了一個(gè)線程池,目的是為了復(fù)用線程,需要開啟線程時(shí),其會(huì)先在線程池中查詢已開辟的空閑線程緩存,達(dá)到節(jié)省內(nèi)存空間和時(shí)間的目的。
- 核心線程是否都在執(zhí)行任務(wù) - 沒(méi)有 - 創(chuàng)建新的工作線程去執(zhí)行
- 線程池工作隊(duì)列是否飽和 - 沒(méi)有 - 將任務(wù)存儲(chǔ)在工作隊(duì)列
- 線程池的線程都處于執(zhí)行狀態(tài) - 沒(méi)有 - 安排線程去執(zhí)行
- 交給飽和策略去處理 GCD的線程池中緩存64條線程,就是同時(shí)可以有64條線程正在執(zhí)行,最大并發(fā)執(zhí)行的線程根據(jù)CPU決定。
GCD
GCD全稱是Grand Central Dispatch,它是純 C 語(yǔ)言,并且提供了非常多強(qiáng)大的函數(shù)
GCD的優(yōu)勢(shì):
- GCD 是蘋果公司為多核的并行運(yùn)算提出的解決方案
- GCD 會(huì)自動(dòng)利用更多的CPU內(nèi)核(比如雙核、四核)
- GCD 會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程)
- 程序員只需要告訴 GCD 想要執(zhí)行什么任務(wù),不需要編寫任何線程管理代碼 GCD的核心——將任務(wù)添加到隊(duì)列,并且指定執(zhí)行任務(wù)的函數(shù)
1、任務(wù)
就是執(zhí)行操作的意思,也就是在線程中執(zhí)行的那段代碼。在 GCD 中是放在 block 中的。執(zhí)行任務(wù)有兩種方式:同步執(zhí)行(sync)和異步執(zhí)行(async)
- 同步(Sync) :同步添加任務(wù)到指定的隊(duì)列中,在添加的任務(wù)執(zhí)行結(jié)束之前,會(huì)一直等待,直到隊(duì)列里面的任務(wù)完成之后再繼續(xù)執(zhí)行,即會(huì)阻塞線程。只能在當(dāng)前線程中執(zhí)行任務(wù)(是當(dāng)前線程,不一定是主線程),不具備開啟新線程的能力。
- 異步(Async) :線程會(huì)立即返回,無(wú)需等待就會(huì)繼續(xù)執(zhí)行下面的任務(wù),不阻塞當(dāng)前線程。可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力(并不一定開啟新線程)。如果不是添加到主隊(duì)列上,異步會(huì)在子線程中執(zhí)行任務(wù)
2、隊(duì)列
隊(duì)列(Dispatch Queue):這里的隊(duì)列指執(zhí)行任務(wù)的等待隊(duì)列,即用來(lái)存放任務(wù)的隊(duì)列。隊(duì)列是一種特殊的線性表,采用 FIFO(先進(jìn)先出)的原則,即新任務(wù)總是被插入到隊(duì)列的末尾,而讀取任務(wù)的時(shí)候總是從隊(duì)列的頭部開始讀取。隊(duì)列的作用就是存儲(chǔ)任務(wù),和線程沒(méi)有任何關(guān)系。每讀取一個(gè)任務(wù),則從隊(duì)列中釋放一個(gè)任務(wù)
在 GCD 中有兩種隊(duì)列:串行隊(duì)列和并發(fā)隊(duì)列。兩者都符合 FIFO(先進(jìn)先出)的原則。兩者的主要區(qū)別是:執(zhí)行順序不同,以及開啟線程數(shù)不同。
- 串行隊(duì)列(Serial Dispatch Queue) :
同一時(shí)間內(nèi),隊(duì)列中只能執(zhí)行一個(gè)任務(wù),只有當(dāng)前的任務(wù)執(zhí)行完成之后,才能執(zhí)行下一個(gè)任務(wù)。(只開啟一個(gè)線程,一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù))。主隊(duì)列是主線程上的一個(gè)串行隊(duì)列,是系統(tǒng)自動(dòng)為我們創(chuàng)建的 - 并發(fā)隊(duì)列(Concurrent Dispatch Queue) :
同時(shí)允許多個(gè)任務(wù)并發(fā)執(zhí)行。(可以開啟多個(gè)線程,并且同時(shí)執(zhí)行任務(wù))。并發(fā)隊(duì)列的并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效
3、死鎖
在串行隊(duì)列中添加任務(wù)的block中含有另一個(gè)向同一隊(duì)列添加的同步任務(wù)就會(huì)發(fā)生死鎖,因?yàn)橥饺蝿?wù)立即執(zhí)行,隊(duì)列需要遵守FIFO(先進(jìn)先出)的原則,串行隊(duì)列需要等待前一個(gè)任務(wù)執(zhí)行結(jié)束才會(huì)執(zhí)行下一個(gè)任務(wù),導(dǎo)致互相等待造成死鎖。
接下來(lái)我們從源碼中了解一下GCD的串行隊(duì)列和并發(fā)隊(duì)列都做了什么,有什么區(qū)別。
dispatch_queue_t main = dispatch_get_main_queue(); //主隊(duì)列 dispatch_queue_t global = dispatch_get_global_queue(0, 0); //全局隊(duì)列 dispatch_queue_t serial = dispatch_queue_create("WT", DISPATCH_QUEUE_SERIAL); // 串行隊(duì)列 dispatch_queue_t concurrent = dispatch_queue_create("WT", DISPATCH_QUEUE_CONCURRENT); //并發(fā)隊(duì)列 // 主隊(duì)列源碼 dispatch_get_main_queue(void) { return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q); } struct dispatch_queue_static_s _dispatch_main_q = { DISPATCH_GLOBAL_OBJECT_HEADER(queue_main), #if !DISPATCH_USE_RESOLVERS .do_targetq = _dispatch_get_default_queue(true), #endif .dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) | DISPATCH_QUEUE_ROLE_BASE_ANON, .dq_label = "com.apple.main-thread", .dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1), .dq_serialnum = 1, }; // 全局隊(duì)列源碼 dispatch_get_global_queue(intptr_t priority, uintptr_t flags) { dispatch_qos_t qos = _dispatch_qos_from_queue_priority(priority); …… return _dispatch_get_root_queue(qos, flags & DISPATCH_QUEUE_OVERCOMMIT); } #define DISPATCH_QUEUE_WIDTH_FULL 0x1000ull #define DISPATCH_QUEUE_WIDTH_POOL (DISPATCH_QUEUE_WIDTH_FULL - 1) struct dispatch_queue_global_s _dispatch_root_queues[] = { #define _DISPATCH_ROOT_QUEUE_IDX(n, flags) \ ((flags & DISPATCH_PRIORITY_FLAG_OVERCOMMIT) ? \ DISPATCH_ROOT_QUEUE_IDX_##n##_QOS_OVERCOMMIT : \ DISPATCH_ROOT_QUEUE_IDX_##n##_QOS) #define _DISPATCH_ROOT_QUEUE_ENTRY(n, flags, ...) \ [_DISPATCH_ROOT_QUEUE_IDX(n, flags)] = { \ DISPATCH_GLOBAL_OBJECT_HEADER(queue_global), \ .dq_state = DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE, \ .do_ctxt = _dispatch_root_queue_ctxt(_DISPATCH_ROOT_QUEUE_IDX(n, flags)), \ .dq_atomic_flags = DQF_WIDTH(DISPATCH_QUEUE_WIDTH_POOL), \ .dq_priority = flags | ((flags & DISPATCH_PRIORITY_FLAG_FALLBACK) ? \ _dispatch_priority_make_fallback(DISPATCH_QOS_##n) : \ _dispatch_priority_make(DISPATCH_QOS_##n, 0)), \ __VA_ARGS__ \ } _DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, 0, .dq_label = "com.apple.root.maintenance-qos", .dq_serialnum = 4, ), ...省略部分... _DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT, .dq_label = "com.apple.root.user-interactive-qos.overcommit", .dq_serialnum = 15, ), }; // 串行隊(duì)列源碼 #define DISPATCH_QUEUE_SERIAL NULL // 并發(fā)隊(duì)列源碼 #define DISPATCH_QUEUE_CONCURRENT DISPATCH_GLOBAL_OBJECT(dispatch_queue_attr_t, _dispatch_queue_attr_concurrent) dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr) { return _dispatch_lane_create_with_target(label, attr, DISPATCH_TARGET_QUEUE_DEFAULT, true); } #if OS_OBJECT_USE_OBJC #define DISPATCH_GLOBAL_OBJECT(type, object) ((OS_OBJECT_BRIDGE type)&(object))
#define DISPATCH_QUEUE_WIDTH_MAX (DISPATCH_QUEUE_WIDTH_FULL - 2) static dispatch_queue_t _dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa, dispatch_queue_t tq, bool legacy) { // 串行隊(duì)列dqai為{} dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa); ...省略部分 - 根據(jù)dqai規(guī)范化參數(shù)... dispatch_lane_t dq = _dispatch_object_alloc(vtable, sizeof(struct dispatch_lane_s)); // 初始化隊(duì)列 并發(fā)隊(duì)列 DISPATCH_QUEUE_WIDTH_MAX 串行隊(duì)列 1 _dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ? DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER | (dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0)); ...省略部分... return _dispatch_trace_queue_create(dq)._dq; } _dispatch_queue_attr_to_info(dispatch_queue_attr_t dqa) { dispatch_queue_attr_info_t dqai = { }; // 串行隊(duì)列直接返回 {} if (!dqa) return dqai; // #if DISPATCH_VARIANT_STATIC if (dqa == &_dispatch_queue_attr_concurrent) { // 并發(fā)隊(duì)列 dqai.dqai_concurrent = true; return dqai; } #endif ...一系列操作... return dqai; } static inline dispatch_queue_class_t _dispatch_queue_init(dispatch_queue_class_t dqu, dispatch_queue_flags_t dqf, uint16_t width, uint64_t initial_state_bits) { uint64_t dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(width); dispatch_queue_t dq = dqu._dq; dispatch_assert((initial_state_bits & ~(DISPATCH_QUEUE_ROLE_MASK | DISPATCH_QUEUE_INACTIVE)) == 0); if (initial_state_bits & DISPATCH_QUEUE_INACTIVE) { dq->do_ref_cnt += 2; // rdar://8181908 see _dispatch_lane_resume if (dx_metatype(dq) == _DISPATCH_SOURCE_TYPE) { dq->do_ref_cnt++; // released when DSF_DELETED is set } } dq_state |= initial_state_bits; dq->do_next = DISPATCH_OBJECT_LISTLESS; dqf |= DQF_WIDTH(width); // 串行隊(duì)列DQF_WIDTH(1) -- 主隊(duì)列 -- 串行隊(duì)列 os_atomic_store2o(dq, dq_atomic_flags, dqf, relaxed); dq->dq_state = dq_state; dq->dq_serialnum = os_atomic_inc_orig(&_dispatch_queue_serial_numbers, relaxed); return dqu; }
我們可以看到初始化的時(shí)候,主隊(duì)列及串行隊(duì)列初始化隊(duì)列都是DQF_WIDTH(1),全局并發(fā)隊(duì)列DQF_WIDTH(0x1000ull - 1 = 15),并發(fā)隊(duì)列DQF_WIDTH(0x1000ull - 2 = 14);
主隊(duì)列的number = 1,全局隊(duì)列number = 4-15,其他number也可以在Dispatch Source/init.c文件里查找到。
總結(jié)
串行隊(duì)列就類似單行道,并發(fā)隊(duì)列相當(dāng)于多車道,雖然都是FIFO的數(shù)據(jù)結(jié)構(gòu),但是串行隊(duì)列只能往一個(gè)隊(duì)列中添加任務(wù),一定會(huì)按照放入隊(duì)列的順序進(jìn)行順序執(zhí)行;
并發(fā)隊(duì)列可以往多個(gè)隊(duì)列中添加任務(wù),等待線程執(zhí)行隊(duì)列中的任務(wù),線程的調(diào)度隊(duì)列的情況和任務(wù)的復(fù)雜度決定了任務(wù)的執(zhí)行順序。
以上就是iOS開發(fā)探索多線程GCD隊(duì)列示例詳解的詳細(xì)內(nèi)容,更多關(guān)于iOS開發(fā)多線程GCD隊(duì)列的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
iOS實(shí)現(xiàn)自定義起始時(shí)間選擇器視圖
本篇文章主要介紹了iOS實(shí)現(xiàn)自定義起始時(shí)間選擇器視圖,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-06-06分享一個(gè)iOS下實(shí)現(xiàn)基本繪畫板功能的簡(jiǎn)單方法
這篇文章主要介紹了iOS下實(shí)現(xiàn)基本繪畫板功能的簡(jiǎn)單方法,代碼基于傳統(tǒng)的Objective-C,需要的朋友可以參考下2015-10-10iOS實(shí)現(xiàn)步驟進(jìn)度條功能實(shí)例代碼
這篇文章主要給大家介紹了關(guān)于iOS實(shí)現(xiàn)步驟進(jìn)度條功能的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11詳解iOS之關(guān)于double/float數(shù)據(jù)計(jì)算精度問(wèn)題
本篇文章主要介紹了iOS之關(guān)于double/float數(shù)據(jù)計(jì)算精度問(wèn)題,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-02-02iOS滑動(dòng)解鎖、滑動(dòng)獲取驗(yàn)證碼效果的實(shí)現(xiàn)代碼
這篇文章主要介紹了iOS滑動(dòng)解鎖、滑動(dòng)獲取驗(yàn)證碼效果的實(shí)現(xiàn)代碼,小編認(rèn)為還不錯(cuò),非常實(shí)用,所有分享給大家,感興趣的朋友可以參考下2016-05-05