GCD
的全稱是Grand Central Dispatch
,它是Apple
開發的一個多核編程的解決方法,由純C
語言實現,提供了很是強大的函數,用來對多線程進行相關的操做。編程
GCD
的優點GCD
會自動利用更多的CPU
內核(好比雙核、四核)GCD
會自動管理線程的生命週期(建立線程、調度任務、銷燬線程) 程序員只須要告訴GCD
想要執行什麼任務,不須要編寫任何線程管理代碼GCD
當中,加入了兩個比較重要的概念:任務(task)
和隊列(queue)
。任務就是咱們要執行的操做,而隊列則代表了多個操做的執行方法。簡而言之,GCD
的核心就是將任務添加到隊列,而且指定函數執行任務。數組
任務使用block
封裝,該block
沒有參數也沒有返回值。bash
typedef void (^dispatch_block_t)(void);
dispatch_block_t block = ^{
};
複製代碼
執行任務有兩種方式:同步和異步。二者的主要區別是:是否等待隊列的任務執行結束,以及是否具有開啓新線程的能力。多線程
(sync)
:
dispatch_sync(, { ()-> Void in
})
複製代碼
(async)
:
dispatch_async(, { ()-> Void in
})
複製代碼
因此同步任務會阻塞當前線程並等待block
中的任務執行完畢,纔會執行下一個任務;而異步任務則不用等待當前語句執行完畢,就能夠執行下一條語句,並不會出現先後任務互相阻塞等待的狀況。併發
須要注意的是異步(async)
雖然具備開啓新線程的能力,可是並不必定開啓新線程。這跟任務所指定的隊列類型有關。app
隊列:用於存聽任務。GCD
中有兩種隊列:串行隊列和並行隊列。異步
(Serial Dispatch Queue)
: 每次只有一個任務被執行。讓任務一個接着一個地執行。(只開啓一個線程,一個任務執行完畢後,再執行下一個任務)(Concurrent Dispatch Queue)
: 可讓多個任務併發(同時)執行。(能夠開啓多個線程,而且同時執行任務)因爲隊列和任務時搭配使用,因此產生了下面四種方法:async
CPU
調度有關GCD
的概念GCD
的使用步驟很簡單,首先建立一個隊列(串行隊列或併發隊列),而後將任務追加到任務的等待隊列中執行任務(同步執行或異步執行)。ide
#define DISPATCH_TARGET_QUEUE_DEFAULT NULL
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);
}
複製代碼
其參數以下:
const char *label
: 隊列的惟一標識符,能夠傳空值。dispatch_queue_attr_t attr
: 標識隊列的類型,區分是串行隊列仍是併發隊列。
DISPATCH_QUEUE_SERIAL
: 串行隊列DISPATCH_QUEUE_CONCURRENT
: 併發隊列串行隊列的建立方法
dispatch_queue_t queue = dispatch_queue_create("serial_queue", DISPATCH_QUEUE_SERIAL);
複製代碼
經常使用的主隊列就是串行隊列,dispatch_get_main_queue()
UI
隊列其實主隊列其實並不特殊。只是默認狀況下,若是沒有開別的線程,程序都是放在主隊列中的,而主隊列又都會放到主線程中去執行,因此才形成了主隊列特殊的現象。
併發隊列的建立方法
dispatch_queue_t queue = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
複製代碼
經常使用的全局隊列就是併發隊列dispatch_get_global_queue(long identifier, unsigned long flags)
,它能夠直接執行異步任務。該方法第一個參數是優先級,全局隊列的優先級爲DISPATCH_QUEUE_PRIORITY_DEFAULT
,這個值是一個爲0的宏,因此也能夠傳0。unsigned long flags
: 蘋果官方文檔的解釋是Flags that are reserved for future use
。標記這個參數是爲了將來使用保留的,如今傳0便可。
此處引入線程的優先級概念,優先級越高越先執行。
DISPATCH_QUEUE_PRIORITY_HIGH
: 2DISPATCH_QUEUE_PRIORITY_DEFAULT
: 0DISPATCH_QUEUE_PRIORITY_LOW
: (-2)DISPATCH_QUEUE_PRIORITY_BACKGROUND
: INT16_MIN
咱們接着看看隊列究竟是如何建立的?
首先咱們生成如下代碼,並在控制檯輸出:
而後咱們跟蹤代碼,去看看建立的過程。當咱們建立一個併發或者串行隊列的時候,最終會進入如下代碼:
DISPATCH_NOINLINE
static dispatch_queue_t
_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,
dispatch_queue_t tq, bool legacy)
{
// 經過dqai.dqai_concurrent來判斷
// dqai若是是空的結構體就是串行隊列,若是有值就是併發隊列
// 無論串行或者併發,tq都是DISPATCH_TARGET_QUEUE_DEFAULT == NULL
// 串行隊列的 dqa == NULL,併發有值
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
// 串行隊列的 dqai = {},併發有值
......
_dispatch_queue_attr_overcommit_t overcommit = dqai.dqai_overcommit;
......
if (overcommit == _dispatch_queue_attr_overcommit_unspecified) {
// 給overcommit賦值
// 併發隊列的overcommit爲_dispatch_queue_attr_overcommit_disabled
// 串行隊列的overcommit爲_dispatch_queue_attr_overcommit_enabled
overcommit = dqai.dqai_concurrent ?
_dispatch_queue_attr_overcommit_disabled :
_dispatch_queue_attr_overcommit_enabled;
}
if (!tq) {
// 設置tq
// DISPATCH_QOS_UNSPECIFIED = 0 DISPATCH_QOS_DEFAULT = 4
// 建立的時候qos = 0
// _dispatch_get_root_queue(dispatch_qos_t qos, bool overcommit)
// 第一個參數固定是4 第二個參數串行隊列爲true、併發隊列爲false
tq = _dispatch_get_root_queue(
qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos, // 4
overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq; // 0 1
if (unlikely(!tq)) {
DISPATCH_CLIENT_CRASH(qos, "Invalid queue attribute");
}
}
// 開闢內存 - 生成響應的對象 queue
dispatch_lane_t dq = _dispatch_object_alloc(vtable,
sizeof(struct dispatch_lane_s));
// 構造方法
_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
(dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0));
// 標籤
dq->dq_label = label;
// 優先級
dq->dq_priority = _dispatch_priority_make((dispatch_qos_t)dqai.dqai_qos,
dqai.dqai_relpri);
if (overcommit == _dispatch_queue_attr_overcommit_enabled) {
dq->dq_priority |= DISPATCH_PRIORITY_FLAG_OVERCOMMIT;
}
if (!dqai.dqai_inactive) {
_dispatch_queue_priority_inherit_from_target(dq, tq);
_dispatch_lane_inherit_wlh_from_target(dq, tq);
}
_dispatch_retain(tq);
// 將tq的值賦給targetq
dq->do_targetq = tq;
_dispatch_object_debug(dq, "%s", __func__);
return _dispatch_trace_queue_create(dq)._dq;
}
// 經過dqa獲取到dqai
dispatch_queue_attr_info_t
_dispatch_queue_attr_to_info(dispatch_queue_attr_t dqa)
{
dispatch_queue_attr_info_t dqai = { };
// 串行隊列傳的是NULL,會直接返回一個空的結構體
if (!dqa) return dqai;
// 給併發隊列作相關的賦值操做
......
return dqai;
}
// 第一個參數固定是4 第二個參數串行隊列爲true、併發隊列爲false
DISPATCH_ALWAYS_INLINE DISPATCH_CONST
static inline dispatch_queue_global_t
_dispatch_get_root_queue(dispatch_qos_t qos, bool overcommit)
{
// 4-1= 3
// 2*3+0/1 = 6/7
// 串行取到的是_dispatch_root_queues這個數組裏面下標爲7的元素
// 併發取到的是下標爲6的元素
return &_dispatch_root_queues[2 * (qos - 1) + overcommit];
}
// 給隊列賦值
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_state |= DISPATCH_QUEUE_INACTIVE + DISPATCH_QUEUE_NEEDS_ACTIVATION;
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 & DISPATCH_QUEUE_ROLE_MASK);
dq->do_next = DISPATCH_OBJECT_LISTLESS;
dqf |= DQF_WIDTH(width);
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;
}
複製代碼
根據代碼能夠獲取到併發隊列和串行隊列的相關數據以下:
_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT,DISPATCH_PRIORITY_FLAG_FALLBACK,
.dq_label = "com.apple.root.default-qos",
.dq_serialnum = 10,
)
複製代碼
_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT,
DISPATCH_PRIORITY_FLAG_FALLBACK | DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
.dq_label = "com.apple.root.default-qos.overcommit",
.dq_serialnum = 11,
)
複製代碼
這個結果和打印的結果是徹底相同的,這樣也就走了一遍建立的過程。
同理,也能夠根據打印的數據獲得主隊列的信息以下:
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,
};
複製代碼
看完了GCD
隊列的相關信息,咱們再來看看的隊列和任務的配合使用。
GCD
的使用GCD
的使用主要是任務和隊列的配合使用,咱們知道了任務的執行分爲同步和異步,隊列又有串行和併發之分。
根據代碼能夠看出:
因爲主隊列也是一種串行隊列,咱們再來看看同步函數搭配主隊列會如何執行:
咱們發現程序在第一個同步任務的地方崩潰了,這是由於此處發生了死鎖。那是由於咱們在主線程中執行syncTaskMainQueue
方法,即把syncTaskMainQueue
任務放到了主隊列中。當咱們把「打印1」這個同步任務追加到主隊列中,「打印1」須要等待syncTaskMainQueue
的執行,而syncTaskMainQueue
須要等待「打印1」執行完畢,才能接着執行。這就造成了死鎖。
那麼咱們在其餘線程執行該syncTaskMainQueue
會死鎖嗎?調用以下方法:
[NSThread detachNewThreadSelector:@selector(syncTaskMainQueue) toTarget:self withObject:nil];
複製代碼
程序正常執行,由於此時syncTaskMainQueue
在其餘線程執行,而咱們的打印任務都在主線程執行。
能夠得出結論,不管是串行隊列仍是併發隊列,只要是同步任務,都不會開啓新線程,只能在當前線程執行任務,並且任務是一個接一個按順序執行的,而且若是存在耗時任務會發生堵塞。
CPU
調度有關能夠得出結論,執行異步任務的時候,若是是串行隊列,只會開啓一條新的線程,任務會在新線程中一個接一個按順序執行,並且可能會發生堵塞;若是是併發隊列,有多少任務就會建立多少新的線程,任務異步執行,和CPU
調度有關,沒有特定順序。
GCD
的核心就是將任務添加到隊列,而且指定函數執行任務。任務分爲同步任務和異步任務,而隊列又分爲串行隊列和併發隊列。主隊列dispatch_get_main_queue()
是常見的串行隊列,全局隊列dispatch_get_global_queue(0, 0)
是常見的併發隊列。
任務和隊列的配合使用分爲同步函數串行隊列、同步函數併發隊列、異步函數串行隊列、異步函數併發隊列。
執行同步任務的時候,不管是串行隊列仍是併發隊列,都不會開啓新線程,只能在當前線程執行任務,並且任務是一個接一個按順序執行的,而且若是存在耗時任務會發生堵塞。須要注意的是,在主隊列中加入同步任務,可能會致使死鎖。
執行異步任務的時候,若是是串行隊列,只會開啓一條新的線程,任務會在新線程中一個接一個按順序執行,並且可能會發生堵塞;若是是併發隊列,有多少任務就會建立多少新的線程,任務異步執行,和CPU
調度有關,沒有特定順序。