一、概述 1)隊列用來存儲代碼任務,線程用來運行代碼任務; 2)main()函數作為程式入口,整個程式預設運行在主線程中,程式代碼任務預設存放在主隊列中; 3)以下所謂阻塞線程是針對主線程而言(子線程阻塞在所不問,自己手動管理);隊列阻塞主要是針對主隊列(子隊列阻塞在所不問,自己手動管理); 4)在 ...
一、概述 1)隊列用來存儲代碼任務,線程用來運行代碼任務; 2)main()函數作為程式入口,整個程式預設運行在主線程中,程式代碼任務預設存放在主隊列中; 3)以下所謂阻塞線程是針對主線程而言(子線程阻塞在所不問,自己手動管理);隊列阻塞主要是針對主隊列(子隊列阻塞在所不問,自己手動管理); 4)線上程中(如:主線程)添加block任務(以下簡稱“B”)到某個隊列中,添加B本身也是一個任務即dispatch_sync或async代碼本身也由當前線程(如:主線程)來運行(以下簡稱“A”)——簡言之,代碼就是任務,任務就是代碼(所謂的block任務,實質就是block大括弧裡面的代碼); 5)隊列分三種:主隊列、自定義串列隊列(以下簡稱“自定串”)、並行隊列; 6)線程阻塞原理(以主隊列為例):系統會從主隊列中按順序取出某個任務放到主線程中運行,一旦該任務運行完畢,該任務即從主隊列中結束銷毀,系統就會自動從主隊列中取出下一個任務又放到主線程中運行(相當於前一個),如此反覆,直到主隊列中的任務全部運行完畢; 二、非同步併發 1)示例圖 2)代碼
dispatch_queue_t queue2 = dispatch_get_global_queue(0, 0); dispatch_async(queue2, ^{ for (int i = 0; i < 10; i++) { NSLog(@"執行任務3-----%@", [NSThread currentThread]); } }); for (int i = 0; i < 10; i++) { NSLog(@"執行任務1-----%@", [NSThread currentThread]); }
//列印
2019-02-14 10:33:28.408678+0800 MJ_iOS_Test[1864:38570] 執行任務1-----<NSThread: 0x600000c16980>{number = 1, name = main} 2019-02-14 10:33:28.408685+0800 MJ_iOS_Test[1864:38623] 執行任務3-----<NSThread: 0x600000c7b780>{number = 3, name = (null)} 2019-02-14 10:33:28.408935+0800 MJ_iOS_Test[1864:38570] 執行任務1-----<NSThread: 0x600000c16980>{number = 1, name = main} 2019-02-14 10:33:28.408935+0800 MJ_iOS_Test[1864:38623] 執行任務3-----<NSThread: 0x600000c7b780>{number = 3, name = (null)} 2019-02-14 10:33:28.409076+0800 MJ_iOS_Test[1864:38570] 執行任務1-----<NSThread: 0x600000c16980>{number = 1, name = main} 2019-02-14 10:33:28.409098+0800 MJ_iOS_Test[1864:38623] 執行任務3-----<NSThread: 0x600000c7b780>{number = 3, name = (null)} 2019-02-14 10:33:28.409185+0800 MJ_iOS_Test[1864:38570] 執行任務1-----<NSThread: 0x600000c16980>{number = 1, name = main} 2019-02-14 10:33:28.409232+0800 MJ_iOS_Test[1864:38623] 執行任務3-----<NSThread: 0x600000c7b780>{number = 3, name = (null)} 2019-02-14 10:33:28.409301+0800 MJ_iOS_Test[1864:38570] 執行任務1-----<NSThread: 0x600000c16980>{number = 1, name = main} 2019-02-14 10:33:28.409618+0800 MJ_iOS_Test[1864:38623] 執行任務3-----<NSThread: 0x600000c7b780>{number = 3, name = (null)} 2019-02-14 10:33:28.409873+0800 MJ_iOS_Test[1864:38570] 執行任務1-----<NSThread: 0x600000c16980>{number = 1, name = main} 2019-02-14 10:33:28.410141+0800 MJ_iOS_Test[1864:38623] 執行任務3-----<NSThread: 0x600000c7b780>{number = 3, name = (null)} 2019-02-14 10:33:28.410480+0800 MJ_iOS_Test[1864:38570] 執行任務1-----<NSThread: 0x600000c16980>{number = 1, name = main} 2019-02-14 10:33:28.410794+0800 MJ_iOS_Test[1864:38623] 執行任務3-----<NSThread: 0x600000c7b780>{number = 3, name = (null)} 2019-02-14 10:33:28.411167+0800 MJ_iOS_Test[1864:38570] 執行任務1-----<NSThread: 0x600000c16980>{number = 1, name = main} 2019-02-14 10:33:28.411486+0800 MJ_iOS_Test[1864:38623] 執行任務3-----<NSThread: 0x600000c7b780>{number = 3, name = (null)} 2019-02-2019-02-14 10:33:28.411962+0800 MJ_iOS_Test[1864:38623] 執行任務3-----<NSThread: 0x600000c7b780>{number = 3, name = (null)} 14 10:33:28.411763+0800 MJ_iOS_Test[1864:38570] 執行任務1-----<NSThread: 0x600000c16980>{number = 1, name = main} 2019-02-14 10:33:28.422231+0800 MJ_iOS_Test[1864:38570] 執行任務1-----<NSThread: 0x600000c16980>{number = 1, name = main} 2019-02-14 10:33:28.422250+0800 MJ_iOS_Test[1864:38623] 執行任務3-----<NSThread: 0x600000c7b780>{number = 3, name = (null)}3)分析 <1>首先添加GCD中的block任務(即“執行任務3”,相當於B)到併發隊列queue2中,添加本身也是一個任務即A,該任務是存儲在主隊列中,由主線程運行; <2>B被添加到queue2後,因為是非同步,此時A不會在主線程中等待B執行完畢,而是由系統自動返回到主隊列中取出下一個任務放到主線程中運行; 與此同時,系統又會單獨開闢一個子線程來運行queue2中的B,所以即可由子線程來運行B; <3>因此,此時系統有兩條線程(主線程:number為1,name為main;子線程:number為3,name為null——因為沒有取名字)在運行各自的任務,互不影響,交替執行; 三、幾種情形 1)同步串列——主隊列 結果:壞指令執行錯誤——死鎖 //代碼
dispatch_queue_t queue1 = dispatch_get_main_queue(); dispatch_sync(queue1, ^{ for (int i = 0; i < 10; i++) { NSLog(@"執行任務3-----%@", [NSThread currentThread]); } });
//錯誤
//分析 <1>A存儲在主隊列中,由主線來運行,B被添加到主隊列中後,因為是同步,系統不會開闢一個子線程來運行B; <2>A在主線程中運行完畢後,理應從主隊列中銷毀而執行下一個任務B,但因為是同步,A會依然停留在主隊列中等待B運行完畢而不會立即銷毀;又因為主隊列是一個串列隊列,因此B必須等到A執行完畢才能被系統取出放在主線程上運行(因為A排在B前面); <3>局面:A在主隊列中一直等待B的執行完畢,而B在主隊列中一直等待A的執行完畢——“你等我,我等你”,死鎖; -------問題:死鎖僅限於主隊列嗎? //代碼dispatch_queue_t queue3 = dispatch_queue_create("myQueue3", DISPATCH_QUEUE_SERIAL);
NSLog(@"執行任務1----%@", [NSThread currentThread]);
dispatch_async(queue3, ^{
NSLog(@"執行任務2----%@", [NSThread currentThread]);
dispatch_sync(queue3, ^{
NSLog(@"執行任務3----%@", [NSThread currentThread]);
});
NSLog(@"執行任務4----%@", [NSThread currentThread]);
});
NSLog(@"執行任務5----%@", [NSThread currentThread]);
//列印
//分析 <1>queue3自定義串列隊列並且非同步執行,所以系統會開闢一個子線程(number為3);主隊列存儲1、5和第一個async對應的A(以下簡稱“A1”),共三個任務;queue3存儲2、3、4和第二個sync對應的A(以下簡稱“A2”),共四個任務; <2>首先系統從主隊列中取出1放到主線程上運行,待1運行完畢後(併在主隊列中自動銷毀),又取出A1放到主線程上運行,因為是非同步,A1並不需要停留在主隊列一直等待async中的block任務執行完畢,所以A1一執行完畢(併在主隊列中自動銷毀),系統又會從主隊列中取出5放到主線程上運行,與此同時系統又會新開闢一個子線程來運行async中的block任務,該任務中的第一個小任務就是2——因此,此刻系統同時在跑兩條線程,交替執行(誰先誰後由系統隨機決定,沒有定數); <3>執行完1、5後,主隊列中的非系統任務全部執行完畢,此時子線程運行完2後(併在queue3中自動銷毀),系統又會從queue3中取出A2放到子線程中運行,A2運行完畢時,3被添加到了queue3中,同時因為是同步,所以A2依然會停留在queue3中一直等待3運行完畢(而不會即可自動銷毀,只有等到3運行完畢時才會銷毀),又因為queue3是串列隊列,3排在A2後面一直在等待A2執行完畢——局面:A2與3相互等待,死鎖; <4>程式卡死在sync代碼處,其後的代碼(任務4)自然也就不能執行了; 所以,死鎖不限於主隊列,但限於串列隊列!!! ------問題:如何解決死鎖? 方案一:將A2和3放在不同的隊列中 //代碼
dispatch_queue_t queue3 = dispatch_queue_create("myQueue3", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queue4 = dispatch_queue_create("myQueue4", DISPATCH_QUEUE_SERIAL); NSLog(@"執行任務1----%@", [NSThread currentThread]); dispatch_async(queue3, ^{ NSLog(@"執行任務2----%@", [NSThread currentThread]); dispatch_sync(queue4, ^{ NSLog(@"執行任務3----%@", [NSThread currentThread]); }); NSLog(@"執行任務4----%@", [NSThread currentThread]); }); NSLog(@"執行任務5----%@", [NSThread currentThread]);
//列印
2019-02-15 12:28:51.782328+0800 MJ_iOS_Test[3879:128281] 執行任務1----<NSThread: 0x600003661480>{number = 1, name = main} 2019-02-15 12:28:51.782639+0800 MJ_iOS_Test[3879:128281] 執行任務5----<NSThread: 0x600003661480>{number = 1, name = main} 2019-02-15 12:28:51.782678+0800 MJ_iOS_Test[3879:128333] 執行任務2----<NSThread: 0x600003607fc0>{number = 3, name = (null)} 2019-02-15 12:28:51.782818+0800 MJ_iOS_Test[3879:128333] 執行任務3----<NSThread: 0x600003607fc0>{number = 3, name = (null)} 2019-02-15 12:28:51.782955+0800 MJ_iOS_Test[3879:128333] 執行任務4----<NSThread: 0x600003607fc0>{number = 3, name = (null)}
//分析
<1>dispatch_sync本身運行在子線程中,而該子線程對應的隊列為queue3,因此A2存放在queue3中,而3存放在queue4中;
<2>當子線程運行完畢2後(並隨即自動銷毀),此時系統是從queue3中取出4還是從queue4取出3放到子線程中運行呢?——因為同步,系統不會開闢新的線程,添加的block任務依然運行在當前線程中(number為3的子線程);同時,A2會一直停留在queue3中,直到3執行完畢才會自動銷毀,又因為queue3是個串列隊列而4排在A2後面,所以A2把4給堵住了,當3運行完畢後,A2銷毀,系統就從queue3中取出4放到當前線程中運行;
註:此處將3放到一個併發隊列中,效果是一樣的;
方案二:非同步添加3到queue3中 //代碼dispatch_queue_t queue3 = dispatch_queue_create("myQueue3", DISPATCH_QUEUE_SERIAL); NSLog(@"執行任務1----%@", [NSThread currentThread]); dispatch_async(queue3, ^{ NSLog(@"執行任務2----%@", [NSThread currentThread]); dispatch_async(queue3, ^{ NSLog(@"執行任務3----%@", [NSThread currentThread]); }); NSLog(@"執行任務4----%@", [NSThread currentThread]); }); NSLog(@"執行任務5----%@", [NSThread currentThread]);
//列印
2019-02-15 14:09:46.953030+0800 MJ_iOS_Test[5523:191263] 執行任務1----<NSThread: 0x6000030aa900>{number = 1, name = main} 2019-02-15 14:09:46.953330+0800 MJ_iOS_Test[5523:191263] 執行任務5----<NSThread: 0x6000030aa900>{number = 1, name = main} 2019-02-15 14:09:46.953326+0800 MJ_iOS_Test[5523:191330] 執行任務2----<NSThread: 0x6000030c2880>{number = 3, name = (null)} 2019-02-15 14:09:46.953608+0800 MJ_iOS_Test[5523:191330] 執行任務4----<NSThread: 0x6000030c2880>{number = 3, name = (null)} 2019-02-15 14:09:46.954050+0800 MJ_iOS_Test[5523:191330] 執行任務3----<NSThread: 0x6000030c2880>{number = 3, name = (null)}
//分析——我門只分析3和4的順序
<1>因為是非同步且queue3非主隊列,所以系統理應開闢一個新線程,但是運行完2後,此刻當前線程(number為3的子線程)處於閑置狀態,為了節省資源,系統並不會真的開闢一個新線程,所有後面的任務依然會運行在當前子線程中(除非當前線程正在運行中,而同時要非同步運行另一個任務,系統才會考慮開啟一個新線程,或者手動強行開啟一個新線程(非同步併發));
<2>首先,我們明確下A2、3和4在queue3中的存放順序:
因為3的存放是由A2線上程上運行完成的,而在編譯階段A2並不會運行,但此時系統會給A2和4分配位置——因此A2和4的存放是在編譯階段完成的,而3的存放是在運行階段完成的,所以如下圖所示
<3>因為非同步,所以A2不會停留在queue3中一直等待3執行完畢而是將3添加到queue3中隨即自動銷毀,因為queue3是個串列隊列,此時系統就從queue3中取出4放到當前子線程中運行,待4運行完畢,系統又會取出3放到當前子線程中運行;
說明:dispatch添加block任務(不論非同步還是同步),都是在當前線程上運行完成的,因此該block任務的存放位置一定在dispatch後面所有外部任務(後面dispatch中的block任務以此類推)的位置後面———這點,請註意!!!
2)同步串列——自定義串列隊列 結果:順序執行 //代碼dispatch_queue_t queue3 = dispatch_queue_create("myQueue3", DISPATCH_QUEUE_SERIAL); dispatch_sync(queue3, ^{ for (int i = 0; i < 10; i++) { NSLog(@"執行任務3-----%@", [NSThread currentThread]); } }); for (int i = 0; i < 10; i++) { NSLog(@"執行任務1-----%@", [NSThread currentThread]); }
//列印
2019-02-14 11:10:57.529267+0800 MJ_iOS_Test[2440:59133] 執行任務3-----<NSThread: 0x60000252ce40>{number = 1, name = main} 2019-02-14 11:10:57.529503+0800 MJ_iOS_Test[2440:59133] 執行任務3-----<NSThread: 0x60000252ce40>{number = 1, name = main} 2019-02-14 11:10:57.529627+0800 MJ_iOS_Test[2440:59133] 執行任務3-----<NSThread: 0x60000252ce40>{number = 1, name = main} 2019-02-14 11:10:57.529771+0800 MJ_iOS_Test[2440:59133] 執行任務3-----<NSThread: 0x60000252ce40>{number = 1, name = main} 2019-02-14 11:10:57.529903+0800 MJ_iOS_Test[2440:59133] 執行任務3-----<NSThread: 0x60000252ce40>{number = 1, name = main} 2019-02-14 11:10:57.530018+0800 MJ_iOS_Test[2440:59133] 執行任務3-----<NSThread: 0x60000252ce40>{number = 1, name = main} 2019-02-14 11:10:57.530147+0800 MJ_iOS_Test[2440:59133] 執行任務3-----<NSThread: 0x60000252ce40>{number = 1, name = main} 2019-02-14 11:10:57.530270+0800 MJ_iOS_Test[2440:59133] 執行任務3-----<NSThread: 0x60000252ce40>{number = 1, name = main} 2019-02-14 11:10:57.530418+0800 MJ_iOS_Test[2440:59133] 執行任務3-----<NSThread: 0x60000252ce40>{number = 1, name = main} 2019-02-14 11:10:57.530547+0800 MJ_iOS_Test[2440:59133] 執行任務3-----<NSThread: 0x60000252ce40>{number = 1, name = main} 2019-02-14 11:10:57.530666+0800 MJ_iOS_Test[2440:59133] 執行任務1-----<NSThread: 0x60000252ce40>{number = 1, name = main} 2019-02-14 11:10:57.530898+0800 MJ_iOS_Test[2440:59133] 執行任務1-----<NSThread: 0x60000252ce40>{number = 1, name = main} 2019-02-14 11:10:57.531152+0800 MJ_iOS_Test[2440:59133] 執行任務1-----<NSThread: 0x60000252ce40>{number = 1, name = main} 2019-02-14 11:10:57.549535+0800 MJ_iOS_Test[2440:59133] 執行任務1-----<NSThread: 0x60000252ce40>{number = 1, name = main} 2019-02-14 11:10:57.549711+0800 MJ_iOS_Test[2440:59133] 執行任務1-----<NSThread: 0x60000252ce40>{number = 1, name = main} 2019-02-14 11:10:57.549853+0800 MJ_iOS_Test[2440:59133] 執行任務1-----<NSThread: 0x60000252ce40>{number = 1, name = main} 2019-02-14 11:10:57.549989+0800 MJ_iOS_Test[2440:59133] 執行任務1-----<NSThread: 0x60000252ce40>{number = 1, name = main} 2019-02-14 11:10:57.550113+0800 MJ_iOS_Test[2440:59133] 執行任務1-----<NSThread: 0x60000252ce40>{number = 1, name = main} 2019-02-14 11:10:57.550247+0800 MJ_iOS_Test[2440:59133] 執行任務1-----<NSThread: 0x60000252ce40>{number = 1, name = main} 2019-02-14 11:10:57.550381+0800 MJ_iOS_Test[2440:59133] 執行任務1-----<NSThread: 0x60000252ce40>{number = 1, name = main}//分析 <1>A存儲在主隊列中,由主線來運行,B被添加到自定義串列隊列queue3中;因為是同步,系統不會開闢一個子線程來運行B; <2>A在主線程運行完畢後會停留在主隊列中一直等待B執行完畢,此時系統無法從主隊列中取出下一個任務放到主線程上運行(因為被A堵住了),但會並且能從queue3中取出B放到主線程上運行; <3>當主線程在運行“執行任務3”時,線程阻塞,當“執行任務3”運行完畢時,A收到消息則銷毀,此時系統就會從主隊列中取出下一個任務“執行任務1”放到主線程上運行; 註:同樣是同步串列,為什麼此處就不會造成死鎖——因為,A和B存放在不同隊列中,不存在相互等待的局面; 3)同步並行 同“2)”; //代碼
dispatch_queue_t queue2 = dispatch_get_global_queue(0, 0); for (int i = 0; i < 5; i++) { NSLog(@"執行任務1----%@", [NSThread currentThread]); } dispatch_async(queue2, ^{ for (int i = 0; i < 5; i++) { NSLog(@"執行任務2----%@", [NSThread currentThread]); } dispatch_sync(queue2, ^{ for (int i = 0; i < 5; i++) { NSLog(@"執行任務3----%@", [NSThread currentThread]); } }); for (int i = 0; i < 5; i++) { NSLog(@"執行任務4----%@", [NSThread currentThread]); } }); for (int i = 0; i < 5; i++) { NSLog(@"執行任務5----%@", [NSThread currentThread]); }
//列印
2019-02-15 17:19:50.417681+0800 MJ_iOS_Test[8640:311746] 執行任務1----<NSThread: 0x600002e96900>{number = 1, name = main} 2019-02-15 17:19:50.417926+0800 MJ_iOS_Test[8640:311746] 執行任務1----<NSThread: 0x600002e96900>{number = 1, name = main} 2019-02-15 17:19:50.418055+0800 MJ_iOS_Test[8640:311746] 執行任務1----<NSThread: 0x600002e96900>{number = 1, name = main} 2019-02-15 17:19:50.418175+0800 MJ_iOS_Test[8640:311746] 執行任務1----<NSThread: 0x600002e96900>{number = 1, name = main} 2019-02-15 17:19:50.418292+0800 MJ_iOS_Test[8640:311746] 執行任務1----<NSThread: 0x600002e96900>{number = 1, name = main} 2019-02-15 17:19:50.418449+0800 MJ_iOS_Test[8640:311746] 執行任務5----<NSThread: 0x600002e96900>{number = 1, name = main} 2019-02-15 17:19:50.418492+0800 MJ_iOS_Test[8640:311806] 執行任務2----<NSThread: 0x600002ef8680>{number = 3, name = (null)} 2019-02-15 17:19:50.418569+0800 MJ_iOS_Test[8640:311746] 執行任務5----<NSThread: 0x600002e96900>{number = 1, name = main} 2019-02-15 17:19:50.418727+0800 MJ_iOS_Test[8640:311806] 執行任務2----<NSThread: 0x600002ef8680>{number = 3, name = (null)} 2019-02-15 17:19:50.419157+0800 MJ_iOS_Test[8640:311746] 執行任務5----<NSThread: 0x600002e96900>{number = 1, name = main} 2019-02-15 17:19:50.419427+0800 MJ_iOS_Test[8640:311806] 執行任務2----<NSThread: 0x600002ef8680>{number = 3, name = (null)} 2019-02-15 17:19:50.419705+0800 MJ_iOS_Test[8640:311746] 執行任務5----<NSThread: 0x600002e96900>{number = 1, name = main} 2019-02-15 17:19:50.419987+0800 MJ_iOS_Test[8640:311806] 執行任務2----<NSThread: 0x600002ef8680>{number = 3, name = (null)} 2019-02-15 17:19:50.420301+0800 MJ_iOS_Test[8640:311746] 執行任務5----<NSThread: 0x600002e96900>{number = 1, name = main} 2019-02-15 17:19:50.427851+0800 MJ_iOS_Test[8640:311806] 執行任務2----<NSThread: 0x600002ef8680>{number = 3, name = (null)} 2019-02-15 17:19:50.428027+0800 MJ_iOS_Test[8640:311806] 執行任務3----<NSThread: 0x600002ef8680>{number = 3, name = (null)} 2019-02-15 17:19:50.428232+0800 MJ_iOS_Test[8640:311806] 執行任務3----<NSThread: 0x600002ef8680>{number = 3, name = (null)} 2019-02-15 17:19:50.429049+0800 MJ_iOS_Test[8640:311806] 執行任務3----<NSThread: 0x600002ef8680>{number = 3, name = (null)} 2019-02-15 17:19:50.429279+0800 MJ_iOS_Test[8640:311806] 執行任務3----<NSThread: 0x600002ef8680>{number = 3, name = (null)} 2019-02-15 17:19:50.429431+0800 MJ_iOS_Test[8640:311806] 執行任務3----<NSThread: 0x600002ef8680>{number = 3, name = (null)} 2019-02-15 17:19:50.429774+0800 MJ_iOS_Test[8640:311806] 執行任務4----<NSThread: 0x600002ef8680>{number = 3, name = (null)} 2019-02-15 17:19:50.430498+0800 MJ_iOS_Test[8640:311806] 執行任務4----<NSThread: 0x600002ef8680>{number = 3, name = (null)} 2019-02-15 17:19:50.430812+0800 MJ_iOS_Test[8640:311806] 執行任務4----<NSThread: 0x600002ef8680>{number = 3, name = (null)} 2019-02-15 17:19:50.431099+0800 MJ_iOS_Test[8640:311806] 執行任務4----<NSThread: 0x600002ef8680>{number = 3, name = (null)} 2019-02-15 17:19:50.431531+0800 MJ_iOS_Test[8640:311806] 執行任務4----<NSThread: 0x600002ef8680>{number = 3, name = (null)}
//分析
<1>當主線程(以下簡稱“T0”)運行完1後,程式走到第一個非同步併發,此時系統必然開啟一個新線程(以下簡稱“T1”),該線程運行第一個大block中的內容(以下簡稱“B1”),而主線程繼續運行5,5和第一個大block中的內容的運行互不影響,交替執行,這點沒問題;
<2>但是,問題來了—————第一個大block中的內容要先執行哪一個呢?為什麼是2跟5交替執行,而其他都是順序執行呢?
$0:首先要明確一點,不論是串列隊列還是並行隊列,其內部任務的添加都要遵循先來後到的順序;
$1:各個隊列中任務的位置順序:主隊列中存放三個任務,順序依次為1、第一個async任務本身(以下簡稱“A1”)和5,均由主線程來運行並且A1不會等待,所以1和A1執行完畢才能執行5,這點沒問題;queue2中存放2、第二個sync任務本身(以下簡稱“A2”)、3和4四個任務,按照上述分析,dispatch添加的block任務3會在2和4之後,所以存放順序依次為2、A2、4和3,如下圖;