OC 底層原理(16)— 多線程二(GCD初探、建立原理、根隊列分析)

GCD 初探

什麼是GCD?

GCD 全稱是 Grand Central Dispatch, 純 C 語言,提供了很是多強大的函數程序員

GCD 的優點

  1. GCD 是蘋果公司爲多核的並行運算提出的解決方案
  2. GCD 會自動利用更多的 CPU 內核(好比雙核,四核)
  3. GCD 會自動管理線程的生命週期(建立線程、調度任務、銷燬線程)程序員只須要告訴 GCD 想要執行什麼任務,不須要編寫任何線程管理代碼

函數

使用例子bash

- (void)syncTest{
    // 任務 隊列 函數
    dispatch_block_t block = ^{
        NSLog(@"hello word");
    };
    dispatch_queue_t queue = dispatch_queue_create("com.janice.cn", NULL);
    dispatch_async(queue, block);
}
複製代碼

以上即是將任務添加到隊列,而且指定執行任務的函數markdown

詳解:多線程

  1. 任務使用 block 封裝,任務的 block 沒有參數也沒有返回值;
  2. 執行任務的函數
  • 異步 'dispatch_async'
    • 不用等待當前語句執行完畢,就能夠執行下一條語句
    • 會開啓線程執行 block 的任務
    • 異步是多線程的代名詞
  • 同步 'dispatch_sync'
    • 必須等待當前語句執行完畢,纔會執行下一條語句
    • 不會開啓線程
    • 在當前執行 block 的任務

隊列 (FIFO)

串行隊列併發

一次只能執行一個,上一個任務沒有執行完,就沒法繼續執行下一個任務,也就是效率比較低,任務耗時較長,DISPATCH_QUEUE_SERIAL。app

並行隊列異步

併發隊列會開啓多個線程來執行任務,因此能夠同時執行多個任務,任務執行的順序也不會固定,DISPATCH_QUEUE_CONCURRENT。

隊列和函數

  1. 同步函數串行隊列
  • 不會開啓線程,在當前線程執行任務
  • 任務串行執行,任務一個接着一個
  • 會產生堵塞
  1. 同步函數併發隊列
  • 不會開啓線程,在當前線程執行任務
  • 任務一個接着一個
  1. 異步函數串行隊列
  • 開啓線程一條新線程
  • 任務一個接着一個
  1. 異步函數併發隊列
  • 開啓線程,在當前線程執行任務
  • 任務異步執行,沒有順序,CPU 調度有關

執行順序練習

練習一async

- (void)textDemo{
    dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_async(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}
複製代碼

執行順序:1,5,2,4,3函數

練習二oop

- (void)wbinterDemo{
    dispatch_queue_t queue = dispatch_queue_create("com.lg.cooci.cn", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"1");
    });
    dispatch_async(queue, ^{
        NSLog(@"2");
    });
    // 堵塞 - 護犢子
    dispatch_sync(queue, ^{
        NSLog(@"3");
    });
    // **********************
    NSLog(@"0");

    dispatch_async(queue, ^{
        NSLog(@"7");
    });
    dispatch_async(queue, ^{
        NSLog(@"8");
    });
    dispatch_async(queue, ^{
        NSLog(@"9");
    });
}
複製代碼

A: 1230789 B: 1237890 C: 3120798 D: 2137890

正確答案:A、C

練習三

- (void)textDemo2{
    // 串行隊列
    dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1");
    // 異步函數
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}

複製代碼

中間執行任務隊列示意圖:

說明:

  • 按照代碼順序,打印 2 任務拍照在隊列的第一位
  • 而後就是同步函數任務塊
  • 而後就是打印 4 任務
  • 而後調步到同步函數任務塊是,發現裏面還有個打印 3 任務,按照隊列的 FIFO 特性,因而就在打印 4 任務的後面。

執行流程

  1. 打印 2 ;
  2. 執行同步函數。
  3. 由於打印 4 任務以前,是個同步函數,因此就會堵塞,也就是說前面的同步函數不執行完,打印 4 任務就不會被執行
  4. 同步函數裏面的打印 3 任務,又要等前面的打印 4 任務執行了才能被執行
  5. 所以這個流程機會造成一個死鎖,互相等待。打印 4 任務等待前面的同步函數塊執行完;同步函數裏面的打印 3 任務又在等待打印 4 任務執行完。因此永遠都是等待,沒法往下執行。

練習四

- (void)textDemo3{
    // 串行隊列
    dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1");
    // 異步函數
    dispatch_async(queue, ^{
        NSLog(@"2");
        NSLog(@"4");
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
    });
    NSLog(@"5");
}

複製代碼

打印結果:

練習四在練習三的基礎上作了一丟丟改變,將打印 4 任務放在同步函數塊的前面,再來分析一下

中間執行任務隊列示意圖:

說明:

  • 按照代碼順序,打印 2 任務拍照在隊列的第一位
  • 而後就是打印 4 任務
  • 而後就是同步函數任務塊
  • 而後調步到同步函數任務塊是,發現裏面還有個打印 3 任務,按照隊列的 FIFO 特性,因而就在同步函數任務塊的後面。

執行流程

  1. 打印 2 ;
  2. 打印 4 ;
  3. 執行同步函數塊任務,函數塊執行完就意味着要等打印 3 任務執行完,纔算結束。
  4. 由於打印 3 任務以前,是個同步函數塊,因此就會堵塞,也就是說前面的同步函數塊不執行完,打印 3 任務就不會被執行;
  5. 所以這個流程機會造成一個死鎖,互相等待。打印 3 任務等待前面的同步函數塊執行完;同步函數裏面的打印 3 任務又在等待同步函數塊執行完。因此永遠都是等待,沒法往下執行。

打印結果:

**總結:**練習3、練習四 ,若是異步函數裏面嵌同步函數,就會發生死鎖!!!。

死鎖

  • 主線程由於同步函數的緣由等着先執行任務
  • 主隊列等着主線程的任務執行完畢再執行本身的任務
  • 主隊列和主線程相互等待會形成死鎖

隊列建立原理

dispatch_queue_create 是如何建立的?

就須要去探索隊列建立的源碼

獲取地址:連接:pan.baidu.com/s/1Avpzm947… 提取碼:l8ci

建立隊列過程源碼分析

  • 首先找到 dispatch_queue_create 入口

  • 以前咱們在實際建立的隊列的時候,想要串行隊列或併發隊列是對接口的第二個參數進行設置,DISPATCH_QUEUE_SERIAL(串行)、DISPATCH_QUEUE_CONCURRENT(並行),以下

  • DISPATCH_QUEUE_SERIAL 的定義

看到 DISPATCH_QUEUE_SERIAL 賦值爲 NULL

  • DISPATCH_QUEUE_CONCURRENT 的定義

因此在探索源碼的時候咱們也看下源碼是怎麼區分的

_dispatch_lane_create_with_target 源碼

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)
{
	
	dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);

	//
	// Step 1: Normalize arguments (qos, overcommit, tq)
	//

	dispatch_qos_t qos = dqai.dqai_qos;
#if !HAVE_PTHREAD_WORKQUEUE_QOS
	if (qos == DISPATCH_QOS_USER_INTERACTIVE) {
		dqai.dqai_qos = qos = DISPATCH_QOS_USER_INITIATED;
	}
	if (qos == DISPATCH_QOS_MAINTENANCE) {
		dqai.dqai_qos = qos = DISPATCH_QOS_BACKGROUND;
	}
#endif // !HAVE_PTHREAD_WORKQUEUE_QOS

	_dispatch_queue_attr_overcommit_t overcommit = dqai.dqai_overcommit;
	if (overcommit != _dispatch_queue_attr_overcommit_unspecified && tq) {
		if (tq->do_targetq) {
			DISPATCH_CLIENT_CRASH(tq, "Cannot specify both overcommit and "
					"a non-global target queue");
		}
	}

	if (tq && dx_type(tq) == DISPATCH_QUEUE_GLOBAL_ROOT_TYPE) {
		// Handle discrepancies between attr and target queue, attributes win
		if (overcommit == _dispatch_queue_attr_overcommit_unspecified) {
			if (tq->dq_priority & DISPATCH_PRIORITY_FLAG_OVERCOMMIT) {
				overcommit = _dispatch_queue_attr_overcommit_enabled;
			} else {
				overcommit = _dispatch_queue_attr_overcommit_disabled;
			}
		}
		if (qos == DISPATCH_QOS_UNSPECIFIED) {
			qos = _dispatch_priority_qos(tq->dq_priority);
		}
		tq = NULL;
	} else if (tq && !tq->do_targetq) {
		// target is a pthread or runloop root queue, setting QoS or overcommit
		// is disallowed
		if (overcommit != _dispatch_queue_attr_overcommit_unspecified) {
			DISPATCH_CLIENT_CRASH(tq, "Cannot specify an overcommit attribute "
					"and use this kind of target queue");
		}
	} else {
		if (overcommit == _dispatch_queue_attr_overcommit_unspecified) {
			// Serial queues default to overcommit!
			overcommit = dqai.dqai_concurrent ?
					_dispatch_queue_attr_overcommit_disabled :
					_dispatch_queue_attr_overcommit_enabled;
		}
	}
	if (!tq) {
		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");
		}
	}

	//
	// Step 2: Initialize the queue
	//

	if (legacy) {
		// if any of these attributes is specified, use non legacy classes
		if (dqai.dqai_inactive || dqai.dqai_autorelease_frequency) {
			legacy = false;
		}
	}

	const void *vtable;
	dispatch_queue_flags_t dqf = legacy ? DQF_MUTABLE : 0;
	if (dqai.dqai_concurrent) {
		// 經過dqai.dqai_concurrent 來區分併發和串行
		// OS_dispatch_queue_concurrent_class
		vtable = DISPATCH_VTABLE(queue_concurrent);
	} else {
		vtable = DISPATCH_VTABLE(queue_serial);
	}
	switch (dqai.dqai_autorelease_frequency) {
	case DISPATCH_AUTORELEASE_FREQUENCY_NEVER:
		dqf |= DQF_AUTORELEASE_NEVER;
		break;
	case DISPATCH_AUTORELEASE_FREQUENCY_WORK_ITEM:
		dqf |= DQF_AUTORELEASE_ALWAYS;
		break;
	}
	if (label) {
		const char *tmp = _dispatch_strdup_if_mutable(label);
		if (tmp != label) {
			dqf |= DQF_LABEL_NEEDS_FREE;
			label = tmp;
		}
	}
    
	// 開闢內存 - 生成響應的對象 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);
	dq->do_targetq = tq;
	_dispatch_object_debug(dq, "%s", __func__);
	return _dispatch_trace_queue_create(dq)._dq;
}
複製代碼
  • 分析 _dispatch_lane_create_with_target 源碼

外面傳進來的隊列類型參數爲dpa,因而看下這段代碼

看到有個判斷,若是 dpa 爲空,就直接返回 dqai,前面有貼出,當 串行隊列 DISPATCH_QUEUE_SERIAL 的時候的定義就是爲空 ,不然作其餘處理,返回

  • 串行和並行建立的地方

建立關鍵步驟:

  1. 第一步初始化並分配內存空間

  2. 構造方法

    1)第一個參數 dq 對象

    2)dqai.dqai_concurrent 第三參數,若是爲併發隊列便標識,傳 DISPATCH_QUEUE_WIDTH_MAX,不然 1.

DISPATCH_QUEUE_WIDTH_MAX 根據宏定義計算出來 0x1000 - 2 = 0xFFE

  1. 設置標籤
  2. 設置優先級
  3. overcommit:串行爲1 ,併發爲 2。

overcommit 的賦值

  1. 設置target
dq->do_targetq = tq;
複製代碼
  1. tq 的建立

根隊列分析

打印獲取到的主隊列和全局併發隊列,由於是全局靜態結構體

主隊列結構體

全局隊列屬性打印

經過打印可知,全局變量的 width = 0xfff,上面有打印併發隊列的 width = 0xfffe

隊列結構體

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(MAINTENANCE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
		.dq_label = "com.apple.root.maintenance-qos.overcommit",
		.dq_serialnum = 5,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, 0,
		.dq_label = "com.apple.root.background-qos",
		.dq_serialnum = 6,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
		.dq_label = "com.apple.root.background-qos.overcommit",
		.dq_serialnum = 7,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, 0,
		.dq_label = "com.apple.root.utility-qos",
		.dq_serialnum = 8,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
		.dq_label = "com.apple.root.utility-qos.overcommit",
		.dq_serialnum = 9,
	),
	_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,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, 0,
		.dq_label = "com.apple.root.user-initiated-qos",
		.dq_serialnum = 12,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
		.dq_label = "com.apple.root.user-initiated-qos.overcommit",
		.dq_serialnum = 13,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, 0,
		.dq_label = "com.apple.root.user-interactive-qos",
		.dq_serialnum = 14,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
		.dq_label = "com.apple.root.user-interactive-qos.overcommit",
		.dq_serialnum = 15,
	),
};

複製代碼

出來 main queue 其餘指針都是由 root queue 模板建立的

dispatch_object_t

typedef struct dispatch_object_s {
private:
	dispatch_object_s();
	~dispatch_object_s();
	dispatch_object_s(const dispatch_object_s &);
	void operator=(const dispatch_object_s &);
} *dispatch_object_t;
複製代碼
相關文章
相關標籤/搜索