iOS-多線程(二)-GCD基礎

多線程(一)-原理
多線程(二)-GCD基礎
多線程(三)-GCD函數
多線程(四)-GCD定時器程序員

簡介

什麼是GCD?

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

    1. 同步函數串行隊列:
    • 不會開啓線程,在當前線程執行任務
    • 任務串行執行,任務一個接着一個
    • 會產生堵塞
    1. 同步函數併發隊列
    • 不會開啓線程,在當前線程執行任務
    • 任務一個接着一個
    1. 異步函數串行隊列
    • 開啓線程一條新線程
    • 任務一個接着一個
    1. 異步函數併發隊列
    • 開啓線程,在當前線程執行任務
    • 任務異步執行,沒有順序,和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: 2
  • DISPATCH_QUEUE_PRIORITY_DEFAULT: 0
  • DISPATCH_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的使用主要是任務和隊列的配合使用,咱們知道了任務的執行分爲同步和異步,隊列又有串行和併發之分。

1. 同步函數串行隊列:

根據代碼能夠看出:

  • 全部的任務默認在主線程執行
  • 同步任務不具有開啓新線程的能力,只能在當前線程執行任務
  • 串行隊列中的任務只能一個接着一個按順序執行
  • 若是存在耗時任務,會產生堵塞

因爲主隊列也是一種串行隊列,咱們再來看看同步函數搭配主隊列會如何執行:

咱們發現程序在第一個同步任務的地方崩潰了,這是由於此處發生了死鎖。那是由於咱們在主線程中執行syncTaskMainQueue方法,即把syncTaskMainQueue任務放到了主隊列中。當咱們把「打印1」這個同步任務追加到主隊列中,「打印1」須要等待syncTaskMainQueue的執行,而syncTaskMainQueue須要等待「打印1」執行完畢,才能接着執行。這就造成了死鎖。

那麼咱們在其餘線程執行該syncTaskMainQueue會死鎖嗎?調用以下方法:

[NSThread detachNewThreadSelector:@selector(syncTaskMainQueue) toTarget:self withObject:nil];
複製代碼

程序正常執行,由於此時syncTaskMainQueue在其餘線程執行,而咱們的打印任務都在主線程執行。

2. 同步函數併發隊列

  • 全部的任務默認在主線程執行
  • 雖然是併發任務,可是同步任務不具有開啓新線程的能力,因此只能在當前線程執行任務
  • 任務一個接一個按順序執行
  • 若是存在耗時任務,會產生堵塞

能夠得出結論,不管是串行隊列仍是併發隊列,只要是同步任務,都不會開啓新線程,只能在當前線程執行任務,並且任務是一個接一個按順序執行的,而且若是存在耗時任務會發生堵塞。

3. 異步函數串行隊列

  • 開啓了一條新線程
  • 任務一個接着一個,按順序執行

4. 異步函數併發隊列

  • 開啓其餘線程執行任務
  • 任務異步執行,沒有順序,和CPU調度有關

能夠得出結論,執行異步任務的時候,若是是串行隊列,只會開啓一條新的線程,任務會在新線程中一個接一個按順序執行,並且可能會發生堵塞;若是是併發隊列,有多少任務就會建立多少新的線程,任務異步執行,和CPU調度有關,沒有特定順序。

總結

GCD的核心就是將任務添加到隊列,而且指定函數執行任務。任務分爲同步任務和異步任務,而隊列又分爲串行隊列和併發隊列。主隊列dispatch_get_main_queue()是常見的串行隊列,全局隊列dispatch_get_global_queue(0, 0)是常見的併發隊列。

任務和隊列的配合使用分爲同步函數串行隊列、同步函數併發隊列、異步函數串行隊列、異步函數併發隊列。

執行同步任務的時候,不管是串行隊列仍是併發隊列,都不會開啓新線程,只能在當前線程執行任務,並且任務是一個接一個按順序執行的,而且若是存在耗時任務會發生堵塞。須要注意的是,在主隊列中加入同步任務,可能會致使死鎖。

執行異步任務的時候,若是是串行隊列,只會開啓一條新的線程,任務會在新線程中一個接一個按順序執行,並且可能會發生堵塞;若是是併發隊列,有多少任務就會建立多少新的線程,任務異步執行,和CPU調度有關,沒有特定順序。

相關文章
相關標籤/搜索