網站首頁 編程語言 正文
引言
在iOS開發過程中,繞不開網絡請求、下載圖片之類的耗時操作,這些操作放在主線程中處理會造成卡頓現象,所以我們都是放在子線程進行處理,處理完成后再返回到主線程進行展示。
多線程貫穿了我們整個的開發過程,iOS的多線程操作有NSThread、GCD、NSOperation,其中我們最常用的就是GCD。
進程與線程
在了解GCD之前我們先來了解一下進程和線程,及他們的聯系與區別
1.進程的定義
- 進程是指在系統中正在運行的一個應用程序
- 每個進程之間是獨立的,每個進程均運行在其專用的且受保護的內存空間內
- 進程間通信一般使用URL Scheme、UIPasteboard、Keychain、UIActivityViewController等
2.線程的定義
- 線程是進程的基本執行單元,一個進程的所有任務都在線程中執行
- 進程想要執行任務,必須得有線程,進程至少要有一條線程
- 程序啟動會默認開啟一條線程,這條線程被成為主線程
- 線程之間的通信一般使用performSelector
3、 進程和線程的關系
- 1.線程是進程的執行單元,進程的所有任務都在線程中執行
- 2.線程是 CPU 分配資源和調度的最小單位
- 3.手機中一個程序對應一個進程,一個進程中可有多個線程,但至少要有一條線程
- 4.同一個進程內的線程共享進程資源
4、 多線程
同一時間,CPU只能處理1條線程,只有1條線程在執行。多線程并發執行,其實是CPU快速地在多條線程之間調度(切換)。如果CPU調度線程的時間足夠快,就造成了多線程并發執行的假象
如果線程非常非常多,CPU會在N多線程之間調度,消耗大量的CPU資源,每條線程被調度執行的頻次會降低(線程的執行效率降低)
多線程的優點:
- 1.能適當提高程序的執行效率;
- 2.能適當提高資源利用率(CPU、內存利用率) 3.線程上的任務執行完成后,線程會自動銷毀
多線程的缺點:
- 1.開啟線程需要占用一定的內存空間(默認情況下,主線程占用1M,子線程占用512KB),如果開啟大量的線程,會占用大量的內存空間,降低程序的性能,開啟線程大概需要90微秒的時間
- 2.線程越多,每個線程被調度的次數就越低,線程的執行效率就越低,CPU在調度線程上的開銷越大
- 3.程序設計更加復雜,比如線程之間的通信、多線程的數據共享
多線程的生命周期是:新建 - 就緒 - 運行 - 阻塞 - 死亡
- 新建:實例化線程對象
- 就緒:向線程對象發送start消息,線程對象被加入可調度線程池等待CPU調度。
- 運行:CPU 負責調度可調度線程池中線程的執行。線程執行完成之前,狀態可能會在就緒和運行之間來回切換。就緒和運行之間的狀態變化由CPU負責,程序員不能干預。
- 阻塞:當滿足某個預定條件時,可以使用休眠或鎖,阻塞線程執行。sleepForTimeInterval(休眠指定時長),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥鎖)。
- 死亡:正常死亡,線程執行完畢。非正常死亡,當滿足某個條件后,在線程內部中止執行/在主線程中止線程對象
5、 時間片
時間片:CPU在多個任務直接進行快速的切換,這個時間間隔就是時間片
設備并發執行的數量是有限的,使用[NSProcessInfo processInfo].activeProcessorCount可以查看當前設備能夠支持線程的最大并發數量,比如說最大并發數是8,代表8核cpu,如果同時開啟了10個線程,則會有CPU通過時間片輪轉的方式讓某一個或者某兩個線程分別執行一段時間。
6、 線程池
GCD在內部維護了一個線程池,目的是為了復用線程,需要開啟線程時,其會先在線程池中查詢已開辟的空閑線程緩存,達到節省內存空間和時間的目的。
- 核心線程是否都在執行任務 - 沒有 - 創建新的工作線程去執行
- 線程池工作隊列是否飽和 - 沒有 - 將任務存儲在工作隊列
- 線程池的線程都處于執行狀態 - 沒有 - 安排線程去執行
- 交給飽和策略去處理 GCD的線程池中緩存64條線程,就是同時可以有64條線程正在執行,最大并發執行的線程根據CPU決定。
GCD
GCD全稱是Grand Central Dispatch,它是純 C 語言,并且提供了非常多強大的函數
GCD的優勢:
- GCD 是蘋果公司為多核的并行運算提出的解決方案
- GCD 會自動利用更多的CPU內核(比如雙核、四核)
- GCD 會自動管理線程的生命周期(創建線程、調度任務、銷毀線程)
- 程序員只需要告訴 GCD 想要執行什么任務,不需要編寫任何線程管理代碼 GCD的核心——將任務添加到隊列,并且指定執行任務的函數
1、任務
就是執行操作的意思,也就是在線程中執行的那段代碼。在 GCD 中是放在 block 中的。執行任務有兩種方式:同步執行(sync)和異步執行(async)
- 同步(Sync) :同步添加任務到指定的隊列中,在添加的任務執行結束之前,會一直等待,直到隊列里面的任務完成之后再繼續執行,即會阻塞線程。只能在當前線程中執行任務(是當前線程,不一定是主線程),不具備開啟新線程的能力。
- 異步(Async) :線程會立即返回,無需等待就會繼續執行下面的任務,不阻塞當前線程。可以在新的線程中執行任務,具備開啟新線程的能力(并不一定開啟新線程)。如果不是添加到主隊列上,異步會在子線程中執行任務
2、隊列
隊列(Dispatch Queue):這里的隊列指執行任務的等待隊列,即用來存放任務的隊列。隊列是一種特殊的線性表,采用 FIFO(先進先出)的原則,即新任務總是被插入到隊列的末尾,而讀取任務的時候總是從隊列的頭部開始讀取。隊列的作用就是存儲任務,和線程沒有任何關系。每讀取一個任務,則從隊列中釋放一個任務
在 GCD 中有兩種隊列:串行隊列和并發隊列。兩者都符合 FIFO(先進先出)的原則。兩者的主要區別是:執行順序不同,以及開啟線程數不同。
- 串行隊列(Serial Dispatch Queue) :
同一時間內,隊列中只能執行一個任務,只有當前的任務執行完成之后,才能執行下一個任務。(只開啟一個線程,一個任務執行完畢后,再執行下一個任務)。主隊列是主線程上的一個串行隊列,是系統自動為我們創建的 - 并發隊列(Concurrent Dispatch Queue) :
同時允許多個任務并發執行。(可以開啟多個線程,并且同時執行任務)。并發隊列的并發功能只有在異步(dispatch_async)函數下才有效
3、死鎖
在串行隊列中添加任務的block中含有另一個向同一隊列添加的同步任務就會發生死鎖,因為同步任務立即執行,隊列需要遵守FIFO(先進先出)的原則,串行隊列需要等待前一個任務執行結束才會執行下一個任務,導致互相等待造成死鎖。
接下來我們從源碼中了解一下GCD的串行隊列和并發隊列都做了什么,有什么區別。
dispatch_queue_t main = dispatch_get_main_queue(); //主隊列
dispatch_queue_t global = dispatch_get_global_queue(0, 0); //全局隊列
dispatch_queue_t serial = dispatch_queue_create("WT", DISPATCH_QUEUE_SERIAL); // 串行隊列
dispatch_queue_t concurrent = dispatch_queue_create("WT", DISPATCH_QUEUE_CONCURRENT); //并發隊列
// 主隊列源碼
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,
};
// 全局隊列源碼
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,
),
};
// 串行隊列源碼
#define DISPATCH_QUEUE_SERIAL NULL
// 并發隊列源碼
#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) {
// 串行隊列dqai為{}
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
...省略部分 - 根據dqai規范化參數...
dispatch_lane_t dq = _dispatch_object_alloc(vtable, sizeof(struct dispatch_lane_s));
// 初始化隊列 并發隊列 DISPATCH_QUEUE_WIDTH_MAX 串行隊列 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 = { };
// 串行隊列直接返回 {}
if (!dqa) return dqai; //
#if DISPATCH_VARIANT_STATIC
if (dqa == &_dispatch_queue_attr_concurrent) { // 并發隊列
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); // 串行隊列DQF_WIDTH(1) -- 主隊列 -- 串行隊列
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;
}
我們可以看到初始化的時候,主隊列及串行隊列初始化隊列都是DQF_WIDTH(1),全局并發隊列DQF_WIDTH(0x1000ull - 1 = 15),并發隊列DQF_WIDTH(0x1000ull - 2 = 14);
主隊列的number = 1,全局隊列number = 4-15,其他number也可以在Dispatch Source/init.c文件里查找到。
總結
串行隊列就類似單行道,并發隊列相當于多車道,雖然都是FIFO的數據結構,但是串行隊列只能往一個隊列中添加任務,一定會按照放入隊列的順序進行順序執行;
并發隊列可以往多個隊列中添加任務,等待線程執行隊列中的任務,線程的調度隊列的情況和任務的復雜度決定了任務的執行順序。
原文鏈接:https://juejin.cn/post/7101507334395936776
相關推薦
- 2023-01-10 redis中Could?not?get?a?resource?from?the?pool異常及解決方
- 2022-06-22 Android用SharedPreferences實現登錄注冊注銷功能_Android
- 2021-12-02 C++11標準庫bind函數應用教程_C 語言
- 2022-05-10 golang使用zookeeper進行增刪改查
- 2023-07-02 Python+streamlit實現輕松創建人事系統_python
- 2022-06-29 python版單鏈表反轉_python
- 2022-08-10 C#中通過Command模式實現Redo/Undo方案_C#教程
- 2022-10-05 C#?獲取文件夾里所有文件名的詳細代碼_C#教程
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支