這是我參與8月更文挑戰的第3天,活動詳情查看:8月更文挑戰c++
什麼是GCD?全稱是GrandCentralDispatch
純C語⾔,提供了⾮常多強⼤的函數。總結爲一句話:將任務添加到隊列,而且指定執行任務的函數。 程序員
GCD是蘋果公司爲多核的並⾏運算提出的解決⽅案 GCD會⾃動利⽤更多的CPU內核(⽐如雙核、四核) GCD會⾃動管理線程的⽣命週期(建立線程、調度任務、銷燬線程) 程序員只須要告訴GCD想要執⾏什麼任務,不須要編寫任何線程管理代碼 面試
異步dispatch_async
markdown
2. 會開啓線程執⾏block的任務 3. 異步是多線程的代名詞 同步dispatch_sync
多線程
隊列遵循FIFO原則:先進先出併發
因爲是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");
}
複製代碼
咱們知道不管同步仍是異步函數都是一個耗時任務。async
再來一個,聽說這個是新浪的面試題函數
- (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");
}
複製代碼
此時打印崩潰:
此時在這裏,2執行完以後,執行到了一個代碼塊(dispatch_sync
而sync的特色是阻塞,必須等到本身執行完以後才能夠),而隊列因爲是先進先出的原則,因此此時形成了4等待塊執行完成,塊的執行完成須要3執行,而3又等待4執行,這樣就形成了一個死鎖的問題。
改進:那麼咱們把4的任務刪除,還會形成死鎖嘛?答案是:還會死鎖 觀察調用棧發現死鎖的函數是:_dispatch_sync_f_slow
實際上發生死鎖的dispatch_async
和dispatch_sync
這兩個代碼塊
// 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
是一個併發隊列。 打印輸出能夠獲得這些信息:
dispatch_get_main_queue
咱們要想研究的話,能夠從底層的源碼入手。在項目中使用GCD的地方打個斷點,查看調用棧,看看底層使用的是哪一個庫 由上面咱們能夠看出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
,繼續搜索 還有一個更簡單的方式定位到這裏,由上面咱們知道,主隊類的
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
類型,搜索的時候小技巧把它帶上,能夠快速定位
_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
來接收?預知後續,咱們下一篇來說解。