iOS執行緒、同步非同步、序列並行佇列

BetterDays發表於2018-06-19

執行緒與佇列說不清道不明的關係:

執行緒是程式碼執行的路徑,佇列則是用於儲存以及管理任務的,執行緒負責去佇列中取任務進行執行。 我的理解:多個佇列的任務可以在一條執行緒上執行,一個佇列的任務也可以在多條執行緒上執行。個人理解,佇列可以包含執行緒,執行緒也可以包含佇列。

dispatch_sync:立馬在當前執行緒執行任務,執行完再往下走,這句話就可以解釋很多問題。

dispatch_async:不要求立馬在當前執行緒執行任務,可能會開啟新執行緒,也有可能不會。

一、畫圖解釋下佇列跟執行緒間的關係

1、一個佇列對應一個執行緒

"主佇列" 對應 "主執行緒"

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor blueColor];
    [self threadTest];
}
- (void)threadTest{
    NSLog(@"我是任務1");
    NSLog(@"我是任務2");
}
複製程式碼

iOS執行緒、同步非同步、序列並行佇列

2、一個佇列對應兩個執行緒

"佇列1" 對應 "主執行緒"和"新執行緒" (因為主佇列沒有開啟新執行緒的能力所以用"佇列1")

- (void)threadTest{
    dispatch_queue_t queue = dispatch_queue_create("佇列1", DISPATCH_QUEUE_SERIAL);
    //同步 不開啟新執行緒,所以在主執行緒執行
    dispatch_sync(queue, ^{
        NSLog(@"任務1");
        [self getCurrentThread];
    });
    //非同步 開啟新執行緒
    dispatch_async(queue, ^{
        NSLog(@"任務2");//2️⃣
        [self getCurrentThread];
    });
    sleep(3);//如果沒有這個方法,2️⃣3️⃣的執行先後順序是不確定的,因為是兩個執行緒,執行先後沒有一毛錢關係
    NSLog(@"方法執行結束");//3️⃣
}
- (void)getCurrentThread{
    NSThread *currentThread = [NSThread currentThread];
    NSLog(@"currentThread == %@",currentThread);
}
複製程式碼
2018-06-20 14:40:05.904914+0800 WLZCyclycReference[4497:158751] 任務1
2018-06-20 14:40:05.905099+0800 WLZCyclycReference[4497:158751] currentThread == <NSThread: 0x60800007c000>{number = 1, name = main}
2018-06-20 14:40:05.905230+0800 WLZCyclycReference[4497:158829] 任務2
2018-06-20 14:40:05.905381+0800 WLZCyclycReference[4497:158829] currentThread == <NSThread: 0x608000270800>{number = 3, name = (null)}
2018-06-20 14:40:08.906308+0800 WLZCyclycReference[4497:158751] 方法執行結束
複製程式碼

iOS執行緒、同步非同步、序列並行佇列

3、兩個佇列對應一個執行緒

"主佇列"和"佇列1" 對應 "主執行緒"

- (void)threadTest{
    dispatch_queue_t queue = dispatch_queue_create("佇列1", DISPATCH_QUEUE_SERIAL);
    NSLog(@"我是任務2");
    [self getCurrentThread];
    //同步 不開啟新執行緒,所以在主執行緒執行
    dispatch_sync(queue, ^{
        sleep(3);//睡三秒可以解釋很多問題
        NSLog(@"我是任務1");
        [self getCurrentThread];
    });
    NSLog(@"方法執行結束");
}
- (void)getCurrentThread{
    NSThread *currentThread = [NSThread currentThread];
    NSLog(@"currentThread == %@",currentThread);
}
複製程式碼
2018-06-20 14:53:39.210769+0800 WLZCyclycReference[4741:169494] 我是任務2
2018-06-20 14:53:39.210959+0800 WLZCyclycReference[4741:169494] currentThread == <NSThread: 0x608000065640>{number = 1, name = main}
2018-06-20 14:53:42.212170+0800 WLZCyclycReference[4741:169494] 我是任務1
2018-06-20 14:53:42.212615+0800 WLZCyclycReference[4741:169494] currentThread == <NSThread: 0x608000065640>{number = 1, name = main}
2018-06-20 14:53:42.212810+0800 WLZCyclycReference[4741:169494] 方法執行結束
複製程式碼

iOS執行緒、同步非同步、序列並行佇列

4、兩個佇列對應兩個執行緒

"主佇列"和"佇列1" 對應 "主執行緒"和"新執行緒"

- (void)threadTest{
    dispatch_queue_t queue = dispatch_queue_create("佇列1", DISPATCH_QUEUE_SERIAL);
    NSLog(@"我是任務2");
    [self getCurrentThread];
    //開啟新執行緒
    dispatch_async(queue, ^{
        NSLog(@"我是任務1");//1️⃣
        [self getCurrentThread];
    });
    NSLog(@"方法執行結束");//2️⃣
    //1️⃣2️⃣的執行先後順序是隨機的
}
- (void)getCurrentThread{
    NSThread *currentThread = [NSThread currentThread];
    NSLog(@"currentThread == %@",currentThread);
}
複製程式碼
2018-06-20 15:11:12.132538+0800 WLZCyclycReference[5076:184244] 我是任務2
2018-06-20 15:11:12.132705+0800 WLZCyclycReference[5076:184244] currentThread == <NSThread: 0x60400007bd80>{number = 1, name = main}
2018-06-20 15:11:12.132809+0800 WLZCyclycReference[5076:184244] 方法執行結束
2018-06-20 15:11:12.132823+0800 WLZCyclycReference[5076:184332] 我是任務1
2018-06-20 15:11:12.133009+0800 WLZCyclycReference[5076:184332] currentThread == <NSThread: 0x6080002602c0>{number = 3, name = (null)}
複製程式碼

iOS執行緒、同步非同步、序列並行佇列

二、主執行緒特點講解

主執行緒特點:如果主執行緒裡有任務就必須等主執行緒任務執行完才輪到主佇列(如果是其他佇列的任務,那麼任務就不用等待,會直接被主執行緒執行)的。所以說如果在主佇列非同步(開啟新執行緒)執行任務無所謂,但是如果在主佇列同步(不開啟新執行緒,需要在主執行緒執行)執行任務會迴圈等待,造成死鎖(但是在一般序列佇列這樣執行就不會出問題,一切都是因為主執行緒的這個特點)。

1、主執行緒特點演示

dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_async(mainQueue, ^{
        NSLog(@"1");
        NSThread *mainThread = [NSThread currentThread];
        NSLog(@"currentThread == %@",mainQueue);
        sleep(3);
        NSLog(@"2");
    });
NSLog(@"主執行緒執行完了");
複製程式碼
2018-06-20 12:09:05.598367+0800 WLZCyclycReference[3254:101260] 主執行緒執行完了
2018-06-20 12:09:05.601950+0800 WLZCyclycReference[3254:101260] 1
2018-06-20 12:09:05.602095+0800 WLZCyclycReference[3254:101260] currentThread == <OS_dispatch_queue_main: com.apple.main-thread>
2018-06-20 12:09:08.603048+0800 WLZCyclycReference[3254:101260] 2
複製程式碼

從執行結果可以完美的看出主執行緒的特點,所以我嚴重懷疑:主佇列的任務只能在主執行緒上執行,換句話說就是主佇列是序列佇列的閹割版,即主佇列沒有建立新執行緒的能力。

主執行緒在執行主佇列任務的過程中:

  • 如果主佇列通過非同步的方式新增"任務1",那麼既然主佇列沒有開新執行緒的能力,就等我主執行緒把我當前的任務執行完了,我主執行緒再去執行"任務1";
  • 如果主佇列通過同步的方式新增"任務2",那麼我主執行緒還是要先執行完我當前的任務,但是"任務2"是同步的,"任務2"又必須要主執行緒執行自己。那主執行緒只好迴圈等待然後死了。

2、主執行緒死鎖演示,主執行緒產生死鎖針對的是主佇列

//主佇列  同步執行任務  死鎖
dispatch_sync(dispatch_get_main_queue(), ^{
});
複製程式碼

3、主執行緒中,同樣的任務放到其它佇列(序列、並行均可)同步執行,不會死鎖,並且順序執行,執行完“序列佇列1”的任務,繼續向下執行主佇列的任務。

dispatch_queue_t queue = dispatch_queue_create("序列佇列1", DISPATCH_QUEUE_SERIAL);
//序列佇列  同步執行任務 因為沒有開啟執行緒所以還是主執行緒
dispatch_sync(queue, ^{
});
複製程式碼

上述程式碼中"主佇列"、"序列佇列1"兩個佇列的任務互不干擾,這種情況其實就相當於把兩個佇列放到了一個更大的虛擬並行佇列中,可以同時執行任務

FIFO,序列佇列任務1沒有執行完,同步執行任務2的話絕逼死鎖,但是死鎖產生的原因是同一個佇列裡邊的兩個任務相互等待,如果不是同一個佇列,那麼久不會產生死鎖。

4、同一個序列佇列,同步執行任務巢狀,造成死鎖。

dispatch_queue_t queue = dispatch_queue_create("序列佇列", DISPATCH_QUEUE_SERIAL);
//序列佇列  同步執行任務 因為沒有開啟執行緒所以還是主執行緒
dispatch_sync(queue, ^{
    NSLog(@"1");
    dispatch_sync(queue, ^{
        NSLog(@"序列佇列巢狀執行");
    });
    NSLog(@"2");
});
複製程式碼

上邊死鎖的情況可以說明一個問題,某一個序列佇列正在執行一個同步任務,這時候又在當前佇列插入了一個同步任務就會造成死鎖。這種情況就跟1(主佇列有任務,主執行緒執行ing,主佇列插入同步任務造成死鎖)的情況是一樣的,只不過主佇列是系統自帶的,我們這個佇列是自己建立的而已。

同步非同步:指的是函式(方法),能否開啟新的執行緒。同步不能,非同步可以

序列並行:指的是佇列,任務的執行方式,序列指各個任務按順序執行。並行指多個任務可以同時執行

序列佇列也可以開啟新的執行緒,只不過開啟之後也只是按順序執行,

三、概念解釋

以下概念參考"08號瘋子"的部落格: IOS多執行緒知識總結/佇列概念/GCD/序列/並行/同步/非同步

  • 程式:正在進行中的程式被稱為程式,負責程式執行的記憶體分配;每一個程式都有自己獨立的虛擬記憶體空間;

  • 執行緒:執行緒是程式中一個獨立的執行路徑(控制單元);一個程式中至少包含一條執行緒,即主執行緒。

  • 佇列:dispatch_queue_t,一種先進先出的資料結構,執行緒的建立和回收不需要程式設計師操作,由佇列負責。

  • 佇列-序列佇列:佇列中的任務只會順序執行

dispatch_queue_t q = dispatch_queue_create(“....”, dispatch_queue_serial);

  • 佇列-並行佇列:佇列中的任務通常會併發執行

dispatch_queue_t q = dispatch_queue_create("......", dispatch_queue_concurrent);

  • 佇列-全域性佇列:是系統開發的,直接拿過來(get)用就可以;與並行佇列類似,但除錯時,無法確認操作所在佇列  

dispatch_queue_t q = dispatch_get_global_queue(dispatch_queue_priority_default, 0);

  • 佇列-主佇列:每一個應用程式對應唯一一個主佇列,直接get即可;在多執行緒開發中,使用主佇列更新UI

dispatch_queue_t q = dispatch_get_main_queue();

  • 序列佇列同步:操作不會新建執行緒、操作順序執行;

  • 序列佇列非同步:操作需要一個子執行緒,會新建執行緒、執行緒的建立和回收不需要程式設計師參與,操作順序執行,是最安全的選擇;

  • 並行佇列同步:操作不會新建執行緒、操作順序執行;

  • 並行佇列非同步:操作會新建多個執行緒(有多少任務,就開n個執行緒執行)、操作無序執行;佇列前如果有其他任務,會等待前面的任務完成之後再執行;場景:既不影響主執行緒,又不需要順序執行的操作

  • 全域性佇列同步:操作不會新建執行緒、操作順序執行;

  • 全域性佇列非同步:操作會新建多個執行緒、操作無序執行,佇列前如果有其他任務,會等待前面的任務完成之後再執行;

  • 主局佇列同步:如果把主執行緒中的操作看成一個大的block,那麼除非主執行緒被使用者殺掉,否則永遠不會結束;主佇列中新增的同步操作永遠不會被執行,會死鎖;

  • 主局佇列非同步:操作都應該在主執行緒上順序執行的,不存在非同步的;

1、佇列和執行緒的區別:

佇列:是管理執行緒的,相當於執行緒池,能管理執行緒什麼時候執行。

佇列分為序列佇列和並行佇列:

序列佇列:佇列中的執行緒按順序執行(不會同時執行)

並行佇列:佇列中的執行緒會併發執行,可能會有一個疑問,佇列不是先進先出嗎,如果後面的任務執行完了,怎麼出去的了。這裡需要強調下,任務執行完畢了,不一定出佇列。只有前面的任務執行完了,才會出佇列。

2、主執行緒佇列和gcd建立的佇列也是有區別的。

主執行緒佇列和gcd建立的佇列是不同的。在gcd中建立的佇列優先順序沒有主佇列高,所以在gcd中的序列佇列開啟同步任務裡面沒有巢狀任務是不會阻塞主執行緒,只有一種可能導致死鎖,就是序列佇列裡,巢狀開啟任務,有可能會導致死鎖。

主執行緒佇列中不能開啟同步,會阻塞主執行緒。只能開啟非同步任務,開啟非同步任務也不會開啟新的執行緒,只是降低非同步任務的優先順序,讓cpu空閒的時候才去呼叫。而同步任務,會搶佔主執行緒的資源,會造成死鎖。

3、執行緒:裡面有非常多的任務(同步,非同步)

同步與非同步的區別:

同步任務優先順序高,線上程中有執行順序,不會開啟新的執行緒。

非同步任務優先順序低,線上程中執行沒有順序,看cpu閒不閒。在主佇列中不會開啟新的執行緒,其他佇列會開啟新的執行緒。

4、主執行緒佇列注意:

在主佇列開啟非同步任務,不會開啟新的執行緒而是依然在主執行緒中執行程式碼塊中的程式碼。為什麼不會阻塞執行緒?

主佇列開啟非同步任務,雖然不會開啟新的執行緒,但是他會把非同步任務降低優先順序,等閒著的時候,就會在主執行緒上執行非同步任務。

在主佇列開啟同步任務,為什麼會阻塞執行緒?

在主佇列開啟同步任務,因為主佇列是序列佇列,裡面的執行緒是有順序的,先執行完一個執行緒才執行下一個執行緒,而主佇列始終就只有一個主執行緒,主執行緒是不會執行完畢的,因為他是無限迴圈的,除非關閉應用開發程式。因此在主執行緒開啟一個同步任務,同步任務會想搶佔執行的資源,而主執行緒任務一直在執行某些操作,不肯放手。兩個的優先順序都很高,最終導致死鎖,阻塞執行緒了。

相關文章