GCD原理分析上篇

這是我參與8月更文挑戰的第3天,活動詳情查看:8月更文挑戰c++

GCD簡介

什麼是GCD?全稱是GrandCentralDispatch 純C語⾔,提供了⾮常多強⼤的函數。總結爲一句話:將任務添加到隊列,而且指定執行任務的函數。 ​程序員

GCD的優點

GCD是蘋果公司爲多核的並⾏運算提出的解決⽅案 GCD會⾃動利⽤更多的CPU內核(⽐如雙核、四核) GCD會⾃動管理線程的⽣命週期(建立線程、調度任務、銷燬線程) 程序員只須要告訴GCD想要執⾏什麼任務,不須要編寫任何線程管理代碼 ​面試

異步與同步

異步dispatch_asyncmarkdown

  1. 不⽤等待當前語句執⾏完畢,就能夠執⾏下⼀條語句

  2. 會開啓線程執⾏block的任務   3. 異步是多線程的代名詞 同步dispatch_sync多線程

  1. 必須等待當前語句執⾏完畢,纔會執⾏下⼀條語句

串行隊列和併發隊列

隊列遵循FIFO原則:先進先出併發

串行隊列.png 併發隊列.png

因爲是FIFO,因此串行隊列按順序執行。併發隊列只是調度任務並非執行任務。 ​app

隊列函數的應用

如下的函數執行順序是怎樣的異步

- (void)textDemo2{
    // 同步隊列
    dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    // 異步函數
    dispatch_async(queue, ^{
        NSLog(@"2");
        // 同步
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}
複製代碼

image.png

咱們知道不管同步仍是異步函數都是一個耗時任務。async

串行和併發.png

再來一個,聽說這個是新浪的面試題函數

- (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
}
複製代碼

正確答案是AC

分析:首先開啓的是一個串行隊列,12行的代碼阻塞的是13行如下的,因此3在0以前,123沒有順序,789也沒有順序,使用排除法獲得AC

死鎖問題

若是把隊列修改成串行隊列那麼此時調用的順序爲:

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

此時打印崩潰:

image.png

死鎖問題.png

此時在這裏,2執行完以後,執行到了一個代碼塊(dispatch_sync而sync的特色是阻塞,必須等到本身執行完以後才能夠),而隊列因爲是先進先出的原則,因此此時形成了4等待塊執行完成,塊的執行完成須要3執行,而3又等待4執行,這樣就形成了一個死鎖的問題。 ​

改進:那麼咱們把4的任務刪除,還會形成死鎖嘛?答案是:還會死鎖 觀察調用棧發現死鎖的函數是:_dispatch_sync_f_slow 實際上發生死鎖的dispatch_asyncdispatch_sync這兩個代碼塊 ​

GCD建立隊列四種方式

// OS_dispatch_queue_serial 串行
    dispatch_queue_t serial = dispatch_queue_create("hb", DISPATCH_QUEUE_SERIAL);
    // OS_dispatch_queue_concurrent 併發
    dispatch_queue_t conque = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    // DISPATCH_QUEUE_SERIAL max && 1
    // queue 對象 alloc init class
    dispatch_queue_t mainQueue = dispatch_get_main_queue();

    dispatch_queue_t globQueue = dispatch_get_global_queue(0, 0);
複製代碼

咱們知道dispatch_get_main_queue是一個串行隊列而且這個隊列是在main()調用以前主線程自動建立的,dispatch_get_global_queue是一個併發隊列。 打印輸出能夠獲得這些信息: image.png

主隊列dispatch_get_main_queue

咱們要想研究的話,能夠從底層的源碼入手。在項目中使用GCD的地方打個斷點,查看調用棧,看看底層使用的是哪一個庫 image.png 由上面咱們能夠看出GCD在libdispatch.dylib,接下來咱們在openSource中去下載源碼。 在源碼中搜索這個dispatch_get_main_queue能夠定位到這裏的代碼

dispatch_get_main_queue(void)
{
	return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
}
複製代碼

進入到DISPATCH_GLOBAL_OBJECT,發現點擊不進去咱們只好全局搜索

#define DISPATCH_GLOBAL_OBJECT(type, object) ((type)&(object))
//dispatch_queue_main_t & _dispatch_main_q 
複製代碼

能夠得出類型是dispatch_queue_main_t,對象是_dispatch_main_q,繼續搜索 image.png 還有一個更簡單的方式定位到這裏,由上面咱們知道,主隊類的lable = **com.apple.main-thread**,因此咱們能夠直接在libdispatch裏面搜索也可以直接定位到這裏。

回到上面的函數,咱們發現主隊列的賦值:

.dq_label = "com.apple.main-thread",
	.dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1),
	.dq_serialnum = 1,
複製代碼

dq_serialnum = 1就是串行隊列嘛

那麼咱們在源碼裏面研究下串行隊列的建立特性,就知道這個條件是否是必然條件了。那麼隊列是怎麼建立的呢?咱們知道這裏用到了一個函數dispatch_queue_create,那麼就來研究下底層這個函數是怎麼實現的吧,這樣這些疑問就會立刻明瞭 ​

dispatch_queue_create底層源碼

第一個參數是const類型,搜索的時候小技巧把它帶上,能夠快速定位 image.png _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)
	// 初始化queue
	// Step 2: Initialize the queue
		
	// 申請和開闢內存
	dispatch_lane_t dq = _dispatch_object_alloc(vtable,
			sizeof(struct dispatch_lane_s));
    // 構造函數初始化 dqai.dqai_concurrent:是不是併發
	_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;
}
複製代碼

在函數構造初始化的時候有這麼一行代碼dqai.dqai_concurrent ? DISPATCH_QUEUE_WIDTH_MAX : 1 再進入_dispatch_queue_init函數中去看下

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)
{
	// ...
	dqf |= DQF_WIDTH(width);
    dq->dq_serialnum =
			os_atomic_inc_orig(&_dispatch_queue_serial_numbers, relaxed);
	// ...
	return dqu;
}
複製代碼

能夠得出: 若是是併發隊列dqf |= DQF_WIDTH(DISPATCH_QUEUE_WIDTH_MAX) 若是是串行隊列dqf |= DQF_WIDTH(1) 繼續研究dq->dq_serialnum os_atomic_inc_orig(&_dispatch_queue_serial_numbers, relaxed)

// skip zero
// 1 - main_q 主隊列
// 2 - mgr_q
// 3 - mgr_root_q
// 4,5,6,7,8,9,10,11,12,13,14,15 - global queues
// 17 - workloop_fallback_q
// we use 'xadd' on Intel, so the initial value == next assigned
#define DISPATCH_QUEUE_SERIAL_NUMBER_INIT 17
extern unsigned long volatile _dispatch_queue_serial_numbers;
複製代碼

因此這裏的_dispatch_queue_serial_numbers只是表明的是建立的隊列的歸屬(串行仍是併發),因此上面的問題dq->dq_serialnum = 1就是建立的主隊列也是串行隊列 ​

擴展os_atomic_inc_orig

搜索os_atomic_inc_orig發現是個宏定義 os_atomic_inc_orig(&_dispatch_queue_serial_numbers, relaxed)os_atomic_add_orig((17), 1, relaxed) _os_atomic_c11_op_orig((17), (v), relaxed, add, +) atomic_fetch_add_explicit(_os_atomic_c11_atomic(17), v, memory_order_relaxedm) atomic_fetch_add_explicit是一個c++函數,也就是17 + 1 ​

_dispatch_object_alloc

在creat的底層源碼中,申請和開啓內存使用的是這行代碼

dispatch_lane_t dq = _dispatch_object_alloc(vtable,
			sizeof(struct dispatch_lane_s));
複製代碼

按照咱們的常識,建立隊列嘛,確定使用 _dispatch_queue_alloc這個函數,可是這裏爲何使用的是_dispatch_object_alloc而且用dispatch_lane_t來接收?預知後續,咱們下一篇來說解。

相關文章
相關標籤/搜索