iOS底層原理 多線程之GCD 看我就夠了 --(10)

上篇RunLoop已經講過了RunLoop和線程的關係,以及Thread如何保活和控制生命週期,今天咱們再探究下另外的一個線程GCD,揭開蒙娜麗莎的面紗。html

GCD 基礎知識

GCD是什麼呢?咱們引用百度百科的一段話。ios

Grand Central Dispatch (GCD)是Apple開發的一個多核編程的較新的解決方法。它主要用於優化應用程序以支持多核處理器以及其餘對稱多處理系統。它是一個在線程池模式的基礎上執行的並行任務。在Mac OS X 10.6雪豹中首次推出,也可在IOS 4及以上版本使用。git

GCD有哪些優勢github

  • GCD自動管理線程
  • 開發者只須要將task加入到隊列中,不用關注細節,而後將task執行完的block傳入便可
  • GCD 自動管理線程,線程建立,掛起,銷燬。

那麼咱們研究下如何更好的使用GCD,首先要了解到串行隊列、並行隊列、併發編程

串行隊列

串行是基於隊列的,隊列會本身控制線程,在串行隊列中,任務一次只能執行一個,執行完當前任務才能繼續執行下個任務。windows

並行隊列

並行有經過新建線程來實現併發執行任務,並行隊列中同時是可能執行多個任務,當並行數量沒有限制的時候,理論上全部任務能夠同時執行。bash

併發

併發是基於線程的,同一個線程只能串行(同一時刻)執行,要想實現併發,只能多個線程一塊兒幹活多線程

串行隊列至關於工廠1條流水線4個工人生產設備,從開始到結束,一我的只能幹一件事,甲作A不作B。併發

並行隊列是一條流水線4個工人,當工人幹活速度不夠的時候能夠再申請一條流水線,實現兩條流水線同時幹活,這就實現了併發。app

併發是多個流水線在同時加工產品。

GCD中的串行隊列()

串行隊列(Serial Dispatch Queue):

按照FIFO(First In First Out先進先出)原則,先添加的任務在隊首,後添加的任務在隊尾,執行任務的時候按照隊列的從首到尾一個挨着一個執行,一次只能執行一個任務,不具有開闢新線程的能力。

併發隊列(Concurrent Dispatch Queue):

按照FIFO(First In First Out先進先出)原則,先添加的任務在隊首,後添加的任務在隊尾,執行任務的時候按照隊列的從首到若干個,執行到隊尾,一次能夠執行多個任務,具有開闢新線程的能力。

GCD使用步驟

GCD的使用很是簡單,建立隊列或者在全局隊列中新加任務就能夠了。

下邊來看看 隊列的建立方法/獲取方法,以及 任務的建立方法

獲取主隊列

主隊列是一種特殊的隊列,也是串行隊列,負責UI的更新,也能夠作其餘事情,能夠經過dispatch_get_main_queue(),通常寫的代碼沒有聲明多線程或者添加到其餘隊列中的代碼都是在主隊列中運行的。

//獲取主隊列
dispatch_queue_t main_queue= dispatch_get_main_queue();
複製代碼

獲取全局隊列

全局隊列是一個特殊的並行隊列,系統已經建立好了,使用的時候經過dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),第一個參數是identifier,表示隊列的優先級,通常傳入DISPATCH_QUEUE_PRIORITY_DEFAULT,第二個參數flags,官方說法是必須是0,不然返回NULL。暫且傳入0。下邊摘自libdispatch

Use the .Fn dispatch_get_global_queue function to obtain the global queue of given priority. The .Fa flags argument is reserved for future use and must be zero. Passing any value other than zero may result in a NULL return value.

//獲取全局隊列
dispatch_queue_t main_queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
複製代碼

任務的建立

GCD 提供了同步執行任務的建立方法dispatch_sync和異步執行任務建立方法的spatch_async

// 同步執行任務建立方法
dispatch_sync(queue, ^{
    // 這裏放同步執行任務代碼
});
// 異步執行任務建立方法
dispatch_async(queue, ^{
    // 這裏放異步執行任務代碼
});
複製代碼

雖然是隻有同步異步可是他們組合的多變的

併發隊列 建立的串行隊列 主隊列
同步(sync) 沒開啓新線程,串行執行 沒開啓新線程,串行執行任務 沒開啓新線程,串行執行任務
異步(async) 能開啓新線程,併發執行 能開啓新線程,串行執行任務 沒開啓新線程,串行執行任務

GCD的使用

主隊列+同步

在主隊列中執行任務,並同步添加任務

//主隊列+同步
-(void)syn_main{
	NSLog(@"1");
	dispatch_queue_t main_queue = dispatch_get_main_queue();
	dispatch_sync(main_queue, ^{
		NSLog(@"2");
	});
	NSLog(@"3");
}
//log
1
複製代碼

看到日誌只輸出了1就崩潰了提示exc_bad_instuction,爲何出問題呢? 主隊列是同步的,任務先後執行的任務是在主隊列中,添加的任務也是在主隊列中,並且添加是同步添加。 what???在同步隊列中添加同步任務,究竟是想讓隊列執行任務仍是添加任務。隊列遵循FIFO原則,假如要你們都在排隊等打飯,新來的員工叫的A,後邊代碼叫B,而後都在一個隊列中,忽然來了個插隊的,你說B能贊成嗎?明顯和A幹起來了,結果系統老師過來拉架了說了一句exc_bad_instuction,意思是你倆吵起來你們都吃不上飯了,結果他倆仍是接着吵,把系統吵崩潰了。 那麼咱們能在主隊列中同步添加任務嗎?答案是能夠的。看到答案不要笑哦

//主隊列+同步
-(void)syn_main2{
	NSLog(@"1任務執行");
	sleep(1);
	NSLog(@"2任務執行");
	sleep(1);
	NSLog(@"3任務執行");
}
//log
1任務執行
2任務執行
3任務執行
複製代碼

沒看錯,保證在主隊列中調用該函數,那麼他就是主隊列同步執行的,若是在其餘隊列中調用,那它則是在調用者隊列中同步執行。

主隊列+異步

在主隊列中異步添加任務並執行任務

//主隊列+異步
	NSLog(@"start");
	dispatch_queue_t main_queue = dispatch_get_main_queue();
	dispatch_async(main_queue, ^{
		for (int i = 0; i < 3; i ++) {
			[NSThread sleepForTimeInterval:1];
			NSLog(@"%@ %d",[NSThread currentThread],i);
		}
	});
	dispatch_async(main_queue, ^{
		for (int i = 3; i < 6; i ++) {
			[NSThread sleepForTimeInterval:1];
			NSLog(@"%@ %d",[NSThread currentThread],i);
		}
	});
	dispatch_async(main_queue, ^{
		for (int i = 7; i < 10; i ++) {
			[NSThread sleepForTimeInterval:1];
			NSLog(@"%@ %d",[NSThread currentThread],i);
		}
	});
	NSLog(@"end");
//log
2019-07-24 15:12:24.73 start
2019-07-24 15:12:24.73 end

<NSThread: 0x600002f9a940>{number = 1, name = main} 0
2019-07-24 15:18:14.971795+0800 day15-GCD[31837:35880409] <NSThread: 0x600002f9a940>{number = 1, name = main} 1
2019-07-24 15:18:15.972421+0800 day15-GCDo[31837:35880409] <NSThread: 0x600002f9a940>{number = 1, name = main} 2
2019-07-24 15:18:16.973529+0800 day15-GCD[31837:35880409] <NSThread: 0x600002f9a940>{number = 1, name = main} 3
2019-07-24 15:18:17.974978+0800 day15-GCD[31837:35880409] <NSThread: 0x600002f9a940>{number = 1, name = main} 4
2019-07-24 15:18:18.975800+0800 day15-GCD[31837:35880409] <NSThread: 0x600002f9a940>{number = 1, name = main} 5
2019-07-24 15:18:19.977185+0800 day15-GCD[31837:35880409] <NSThread: 0x600002f9a940>{number = 1, name = main} 7
2019-07-24 15:18:20.978615+0800 day15-GCD[31837:35880409] <NSThread: 0x600002f9a940>{number = 1, name = main} 8
2019-07-24 15:18:21.979958+0800 day15-GCD[31837:35880409] <NSThread: 0x600002f9a940>{number = 1, name = main} 9
複製代碼

在主隊列異步執行任務,從日誌看出來end早於任務的執行,符合FIFO原則,都是在主線程執行,能夠看到

  • 主線程多個任務異步不能建立新線程
  • 主線程異步也是串行執行

全局隊列+同步

全局隊列是並行隊列,和同步配合就是串行執行了。

//全局隊列+同步
-(void)sync_global{
	printf("\n start");
	dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
	dispatch_sync(global_queue, ^{
		for (int i = 0; i < 3; i ++) {
			[NSThread sleepForTimeInterval:1];
			printf("\n %s %s %d",[self currentDateString].UTF8String,[NSThread currentThread].description.UTF8String,i);
		}
	});
	dispatch_sync(global_queue, ^{
		for (int i = 3; i < 6; i ++) {
			[NSThread sleepForTimeInterval:1];
			printf("\n %s %s %d",[self currentDateString].UTF8String,[NSThread currentThread].description.UTF8String,i);
		}
	});
	dispatch_sync(global_queue, ^{
		NSThread *thread = [NSThread currentThread];
		for (int i = 7; i < 10; i ++) {
			[NSThread sleepForTimeInterval:1];
			printf("\n %s %s %d",[self currentDateString].UTF8String,thread.description.UTF8String,i);
		}
	});
	printf("\n end");
}
//log
start
 2019-07-24 15:35:36 <NSThread: 0x600000592900>{number = 1, name = main} 0
 2019-07-24 15:35:37 <NSThread: 0x600000592900>{number = 1, name = main} 1
 2019-07-24 15:35:38 <NSThread: 0x600000592900>{number = 1, name = main} 2
 2019-07-24 15:35:39 <NSThread: 0x600000592900>{number = 1, name = main} 3
 2019-07-24 15:35:40 <NSThread: 0x600000592900>{number = 1, name = main} 4
 2019-07-24 15:35:41 <NSThread: 0x600000592900>{number = 1, name = main} 5
 2019-07-24 15:35:42 <NSThread: 0x600000592900>{number = 1, name = main} 7
 2019-07-24 15:35:43 <NSThread: 0x600000592900>{number = 1, name = main} 8
 2019-07-24 15:35:44 <NSThread: 0x600000592900>{number = 1, name = main} 9
 end
複製代碼

在全局隊列中使用串行添加多個任務並無新建子線程來解決問題,同步其實就是串行,使用FIFO原則,一個任務解決完再解決下一個任務。

全局隊列+異步

全局隊列有建立子線程的能力,可是須要異步async去執行。

//全局隊列+異步
-(void)async_global{
	printf("\n start");
	dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
	dispatch_async(global_queue, ^{
		for (int i = 0; i < 3; i ++) {
			[NSThread sleepForTimeInterval:1];
			printf("\n %s %s %d",[self currentDateString].UTF8String,[NSThread currentThread].description.UTF8String,i);
		}
	});
	dispatch_async(global_queue, ^{
		for (int i = 3; i < 6; i ++) {
			[NSThread sleepForTimeInterval:1];
			printf("\n %s %s %d",[self currentDateString].UTF8String,[NSThread currentThread].description.UTF8String,i);
		}
	});
	dispatch_async(global_queue, ^{
		NSThread *thread = [NSThread currentThread];
		for (int i = 7; i < 10; i ++) {
			[NSThread sleepForTimeInterval:1];
			printf("\n %s %s %d",[self currentDateString].UTF8String,thread.description.UTF8String,i);
		}
	});
	printf("\n end");
}
-(NSString *)currentDateString{
	NSDate *date=[NSDate new];
	NSDateFormatter *format = [[NSDateFormatter alloc]init];
	[format setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
	return [format stringFromDate:date];
}
//log

 start
 end
 2019-07-24 15:40:21 <NSThread: 0x600003b43dc0>{number = 5, name = (null)} 3
 2019-07-24 15:40:21 <NSThread: 0x600003b44e80>{number = 4, name = (null)} 0
 2019-07-24 15:40:21 <NSThread: 0x600003b45880>{number = 3, name = (null)} 7
 2019-07-24 15:40:22 <NSThread: 0x600003b44e80>{number = 4, name = (null)} 1
 2019-07-24 15:40:22 <NSThread: 0x600003b45880>{number = 3, name = (null)} 8
 2019-07-24 15:40:22 <NSThread: 0x600003b43dc0>{number = 5, name = (null)} 4
 2019-07-24 15:40:23 <NSThread: 0x600003b45880>{number = 3, name = (null)} 9
 2019-07-24 15:40:23 <NSThread: 0x600003b44e80>{number = 4, name = (null)} 2
 2019-07-24 15:40:23 <NSThread: 0x600003b43dc0>{number = 5, name = (null)} 5
複製代碼

全局隊列當搭配async的時候,追加多個任務,此次是使用3個線程,並且不用咱們來維護線程的生命週期,並且執行的順序是無序的。

建立串行隊列+同步

開發者本身建立的串行隊列同步調用和系統主隊列有相似的地方,也有區別。同樣都是串行執行,區別是追加任務的時候通常是在主隊列向串行隊列添加。

//建立串行隊列+同步
-(void)sync_cust_queue{
	printf("\n start");
	dispatch_queue_t custQueue = dispatch_queue_create("cust-queue", DISPATCH_QUEUE_SERIAL);
	dispatch_sync(custQueue, ^{
		for (int i = 0; i < 3; i ++) {
			[NSThread sleepForTimeInterval:1];
			printf("\n %s %s %d",[self currentDateString].UTF8String,[NSThread currentThread].description.UTF8String,i);
		}
	});
	dispatch_sync(custQueue, ^{
		for (int i = 3; i < 6; i ++) {
			[NSThread sleepForTimeInterval:1];
			printf("\n %s %s %d",[self currentDateString].UTF8String,[NSThread currentThread].description.UTF8String,i);
		}
	});
	dispatch_sync(custQueue, ^{
		NSThread *thread = [NSThread currentThread];
		for (int i = 7; i < 10; i ++) {
			[NSThread sleepForTimeInterval:1];
			printf("\n %s %s %d",[self currentDateString].UTF8String,thread.description.UTF8String,i);
		}
	});
	printf("\n end");
}

//log

start
 2019-07-24 15:53:15 <NSThread: 0x6000017ea940>{number = 1, name = main} 0
 2019-07-24 15:53:16 <NSThread: 0x6000017ea940>{number = 1, name = main} 1
 2019-07-24 15:53:17 <NSThread: 0x6000017ea940>{number = 1, name = main} 2
 2019-07-24 15:53:18 <NSThread: 0x6000017ea940>{number = 1, name = main} 3
 2019-07-24 15:53:19 <NSThread: 0x6000017ea940>{number = 1, name = main} 4
 2019-07-24 15:53:20 <NSThread: 0x6000017ea940>{number = 1, name = main} 5
 2019-07-24 15:53:21 <NSThread: 0x6000017ea940>{number = 1, name = main} 7
 2019-07-24 15:53:22 <NSThread: 0x6000017ea940>{number = 1, name = main} 8
 2019-07-24 15:53:23 <NSThread: 0x6000017ea940>{number = 1, name = main} 9
 end
複製代碼

同步向串行隊列添加任務並無死鎖!緣由是添加任務是在main_queue執行的,添加的任務是在cust-queue中執行,符合FIFO原則,先添加的先執行,具體執行的線程由他們本身分配。執行的任務是在main線程中。

建立串行隊列+異步

會開啓新線程,可是由於任務是串行的,執行完一個任務,再執行下一個任務

//建立串行隊列+異步
-(void)async_cust_queue{
	printf("\n start");
	dispatch_queue_t custQueue = dispatch_queue_create("cust-queue", DISPATCH_QUEUE_SERIAL);
	dispatch_async(custQueue, ^{
		for (int i = 0; i < 3; i ++) {
			[NSThread sleepForTimeInterval:1];
			printf("\n %s %s %d",[self currentDateString].UTF8String,[NSThread currentThread].description.UTF8String,i);
		}
	});
	dispatch_async(custQueue, ^{
		for (int i = 3; i < 6; i ++) {
			[NSThread sleepForTimeInterval:1];
			printf("\n %s %s %d",[self currentDateString].UTF8String,[NSThread currentThread].description.UTF8String,i);
		}
	});
	dispatch_async(custQueue, ^{
		NSThread *thread = [NSThread currentThread];
		for (int i = 7; i < 10; i ++) {
			[NSThread sleepForTimeInterval:1];
			printf("\n %s %s %d",[self currentDateString].UTF8String,thread.description.UTF8String,i);
		}
	});
	printf("\n end");
}
//log

 start
 end
 2019-07-24 16:12:57 <NSThread: 0x600002b346c0>{number = 3, name = (null)} 0
 2019-07-24 16:12:58 <NSThread: 0x600002b346c0>{number = 3, name = (null)} 1
 2019-07-24 16:12:59 <NSThread: 0x600002b346c0>{number = 3, name = (null)} 2
 2019-07-24 16:13:00 <NSThread: 0x600002b346c0>{number = 3, name = (null)} 3
 2019-07-24 16:13:01 <NSThread: 0x600002b346c0>{number = 3, name = (null)} 4
 2019-07-24 16:13:02 <NSThread: 0x600002b346c0>{number = 3, name = (null)} 5
 2019-07-24 16:13:03 <NSThread: 0x600002b346c0>{number = 3, name = (null)} 7
 2019-07-24 16:13:04 <NSThread: 0x600002b346c0>{number = 3, name = (null)} 8
 2019-07-24 16:13:05 <NSThread: 0x600002b346c0>{number = 3, name = (null)} 9
複製代碼

異步 + 串行隊列能夠看到:

開啓了一條新線程(異步執行具有開啓新線程的能力,串行隊列只開啓一個線程)。 全部任務是在打印的end以後纔開始執行的(異步執行不會作任何等待,能夠繼續執行任務)。 任務是按順序執行的(串行隊列每次只有一個任務被執行,任務一個接一個按順序執行)。

建立並行隊列+同步

在當前線程中執行任務,不會開啓新線程,執行完一個任務,再執行下一個任務

start
 2019-07-24 16:21:24 <NSThread: 0x6000031d1380>{number = 1, name = main} 0
 2019-07-24 16:21:25 <NSThread: 0x6000031d1380>{number = 1, name = main} 1
 2019-07-24 16:21:26 <NSThread: 0x6000031d1380>{number = 1, name = main} 2
 2019-07-24 16:21:27 <NSThread: 0x6000031d1380>{number = 1, name = main} 3
 2019-07-24 16:21:28 <NSThread: 0x6000031d1380>{number = 1, name = main} 4
 2019-07-24 16:21:29 <NSThread: 0x6000031d1380>{number = 1, name = main} 5
 2019-07-24 16:21:30 <NSThread: 0x6000031d1380>{number = 1, name = main} 7
 2019-07-24 16:21:31 <NSThread: 0x6000031d1380>{number = 1, name = main} 8
 2019-07-24 16:21:32 <NSThread: 0x6000031d1380>{number = 1, name = main} 9
 end
複製代碼

全局隊列其實就是特殊的並行隊列,這裏結果和全局隊列+同步一致。

建立並行隊列+異步

在當前線程中執行任務,會開啓新線程,能夠同時執行多個任務。

//建立並行隊列+異步
-(void)async_queue{
	printf("\n start");
	dispatch_queue_t custQueue = dispatch_queue_create("cust-queue", DISPATCH_QUEUE_CONCURRENT);
	dispatch_async(custQueue, ^{
		for (int i = 0; i < 3; i ++) {
			[NSThread sleepForTimeInterval:1];
			printf("\n %s %s %d",[self currentDateString].UTF8String,[NSThread currentThread].description.UTF8String,i);
		}
	});
	dispatch_async(custQueue, ^{
		for (int i = 3; i < 6; i ++) {
			[NSThread sleepForTimeInterval:1];
			printf("\n %s %s %d",[self currentDateString].UTF8String,[NSThread currentThread].description.UTF8String,i);
		}
	});
	dispatch_async(custQueue, ^{
		NSThread *thread = [NSThread currentThread];
		for (int i = 7; i < 10; i ++) {
			[NSThread sleepForTimeInterval:1];
			printf("\n %s %s %d",[self currentDateString].UTF8String,thread.description.UTF8String,i);
		}
	});
	printf("\n end");
}
//log
start
 end
 2019-07-24 16:22:09 <NSThread: 0x6000004280c0>{number = 3, name = (null)} 7
 2019-07-24 16:22:09 <NSThread: 0x6000004104c0>{number = 5, name = (null)} 0
 2019-07-24 16:22:09 <NSThread: 0x600000422300>{number = 4, name = (null)} 3
 2019-07-24 16:22:10 <NSThread: 0x6000004104c0>{number = 5, name = (null)} 1
 2019-07-24 16:22:10 <NSThread: 0x6000004280c0>{number = 3, name = (null)} 8
 2019-07-24 16:22:10 <NSThread: 0x600000422300>{number = 4, name = (null)} 4
 2019-07-24 16:22:11 <NSThread: 0x6000004280c0>{number = 3, name = (null)} 9
 2019-07-24 16:22:11 <NSThread: 0x6000004104c0>{number = 5, name = (null)} 2
 2019-07-24 16:22:11 <NSThread: 0x600000422300>{number = 4, name = (null)} 5
複製代碼

並行隊列+異步全局隊列+異步一致,也會新建線程執行任務,且是併發執行。

GCD其餘高級用法

子線程執行任務 主線程刷新UI

- (void)backToMain{
	dispatch_queue_t main = dispatch_get_main_queue();
	dispatch_queue_t glo = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
	dispatch_async(glo, ^{
		for (int i = 0; i < 3; i ++) {
			[NSThread sleepForTimeInterval:1];
			printf("\n %s %s %d",[self dateUTF8],[self threadInfo],i);
		}
		dispatch_sync(main, ^{
			printf("\n %s %s 我在刷新UI",[self dateUTF8],[self threadInfo]);
		});
	});	
}
//log
 2019-07-24 16:45:07 <NSThread: 0x600001e84380>{number = 3, name = (null)} 0
 2019-07-24 16:45:08 <NSThread: 0x600001e84380>{number = 3, name = (null)} 1
 2019-07-24 16:45:09 <NSThread: 0x600001e84380>{number = 3, name = (null)} 2
 2019-07-24 16:45:09 <NSThread: 0x600001ef2940>{number = 1, name = main} 我在刷新UI
複製代碼

隊列分組 dispatch_group_t

dispatch_group_notify

GCD有有分組的概念,當全部加入分組的隊列中的任務都執行完成的時候,經過dispatch_group_notify完成回調,第一個參數group是某個分組的回調。

-(void)group{
	dispatch_group_t group = dispatch_group_create();
	dispatch_queue_t queue= dispatch_queue_create("cust.queue.com", DISPATCH_QUEUE_CONCURRENT);
	dispatch_queue_t queue2= dispatch_queue_create("cust2.queue.com", DISPATCH_QUEUE_CONCURRENT);
	dispatch_group_async(group, queue, ^{
		for (int i = 0; i < 3; i ++) {
			[NSThread sleepForTimeInterval:1];
			printf("\n %s %s %d",[self dateUTF8],[self threadInfo],i);
		}
	});
	dispatch_group_async(group, queue2, ^{
		for (int i = 4; i < 6; i ++) {
			[NSThread sleepForTimeInterval:1];
			printf("\n %s %s %d",[self dateUTF8],[self threadInfo],i);
		}
	});
	dispatch_group_notify(group, dispatch_get_main_queue(), ^{
		printf("\n %s %s ---end1----",[self dateUTF8],[self threadInfo]);
	});
	dispatch_group_async(group, queue, ^{
		for (int i = 6; i < 8; i ++) {
			[NSThread sleepForTimeInterval:1];
			printf("\n %s %s %d",[self dateUTF8],[self threadInfo],i);
		}
	});
	dispatch_group_async(group, queue2, ^{
		for (int i = 8; i < 10; i ++) {
			[NSThread sleepForTimeInterval:1];
			printf("\n %s %s %d",[self dateUTF8],[self threadInfo],i);
		}
	});
}
複製代碼
dispatch_group_wait && dispatch_group_enter && dispatch_group_leave

dispatch_group_enterdispatch_group_leave須要成對使用,不然dispatch_group_wait在缺乏leave的狀況下會等待到死,形成線程阻塞。

static	dispatch_group_t group ;
if (group == nil) {
	group = dispatch_group_create();
}
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//	dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
	[self print];
	[NSThread sleepForTimeInterval:2];
//		dispatch_group_leave(group);//當註釋掉  阻塞在wait不繼續向下執行
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
	[self print];
	[NSThread sleepForTimeInterval:2];
	dispatch_group_leave(group);
});
//log
2019-07-25 10:58:50 <NSThread: 0x600002d84180>{number = 3, name = (null)} 
2019-07-25 10:58:52 <NSThread: 0x600002d84180>{number = 3, name = (null)} 

複製代碼

柵欄函數 dispatch_barrier_sync

柵欄函數實現了異步的隊列中在多個任務結束的時候實行回調,回調分異步和同步,同步回調在主線程,異步在其餘線程。

- (void)barry{
	dispatch_queue_t queue= dispatch_queue_create("cust.queue.com", DISPATCH_QUEUE_CONCURRENT);
	dispatch_async(queue, ^{
		for (int i = 0; i < 3; i ++) {
			[NSThread sleepForTimeInterval:1];
			printf("\n %s %s %d",[self dateUTF8],[self threadInfo],i);
		}
	});
	dispatch_barrier_sync(queue, ^{
		printf("\n %s %s ---中間暫停一下----",[self dateUTF8],[self threadInfo]);
	});
	dispatch_async(queue, ^{
		for (int i = 3; i < 6; i ++) {
			[NSThread sleepForTimeInterval:1];
			printf("\n %s %s %d",[self dateUTF8],[self threadInfo],i);
		}
	});
	dispatch_barrier_async(queue, ^{
		printf("\n %s %s ---中間第二次暫停一下----",[self dateUTF8],[self threadInfo]);
	});
}
//log
 2019-07-24 16:52:33 <NSThread: 0x600003158440>{number = 3, name = (null)} 0
 2019-07-24 16:52:34 <NSThread: 0x600003158440>{number = 3, name = (null)} 1
 2019-07-24 16:52:35 <NSThread: 0x600003158440>{number = 3, name = (null)} 2
 2019-07-24 16:52:35 <NSThread: 0x6000031293c0>{number = 1, name = main} ---中間暫停一下----
 2019-07-24 16:52:36 <NSThread: 0x600003158440>{number = 3, name = (null)} 3
 2019-07-24 16:52:37 <NSThread: 0x600003158440>{number = 3, name = (null)} 4
 2019-07-24 16:52:38 <NSThread: 0x600003158440>{number = 3, name = (null)} 5
 2019-07-24 16:52:38 <NSThread: 0x600003158440>{number = 3, name = (null)} ---中間第二次暫停一下----
複製代碼

單例-執行一次的函數 dispatch_once_t

單例能夠經過這個函數實現,只執行一次的函數。

//只執行一次的dispatch_once
-(void)exc_once{
	static dispatch_once_t onceToken;
	static NSObject *obj;
	dispatch_once(&onceToken, ^{
		obj=[NSObject new];
		printf("\n just once %s %s",[self dateUTF8],obj.description.UTF8String);
	});
	printf("\n %s %s",[self dateUTF8],obj.description.UTF8String);
}
調用4次
dispatch_apply(4, dispatch_get_global_queue(0, 0), ^(size_t idx) {
		[self exc_once];
	});
	
//log
just once 2019-07-25 14:46:00 <NSObject: 0x60000378b100>
2019-07-25 14:46:00 <NSObject: 0x60000378b100>
2019-07-25 14:46:00 <NSObject: 0x60000378b100>
2019-07-25 14:46:00 <NSObject: 0x60000378b100>
複製代碼

當調用4次的時候,日誌打印的四次obj均爲同一個地址,證實block回調四次可是隻執行了一次。

延遲執行 dispatch_after

當記錄日誌或者點擊事件的方法咱們不但願當即執行,則會用到延遲

//延遲執行
-(void)delayTimeExc{
	printf("\n %s %s begin",[self dateUTF8],[self threadInfo]);
	
	dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
	
		printf("\n %s %s",[self dateUTF8],[self threadInfo]);
		
	});
	printf("\n %s %s end",[self dateUTF8],[self threadInfo]);
}
//log
2019-07-24 17:07:48 <NSThread: 0x600003cc6940>{number = 1, name = main} begin
2019-07-24 17:07:48 <NSThread: 0x600003cc6940>{number = 1, name = main} end
2019-07-24 17:07:50 <NSThread: 0x600003cc6940>{number = 1, name = main}
複製代碼

信號量 dispatch_semaphore_t

信號量爲1能夠做爲線程鎖來用,當N>1的時候,同時執行的有N個任務。 dispatch_apply能夠通知建立多個線程來執行任務,用它來測試信號量再好不過了。

//信號量 當信號量爲1 能夠未作鎖來用,當N>1,t通知執行的數量則是數字N。
- (void)semaphore{
	static dispatch_semaphore_t sem;
	if (sem == NULL) {
		sem = dispatch_semaphore_create(1);
	}
	dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
	static int i = 0;
	int currentI = i +2;
	for (; i < currentI; i ++) {
		[NSThread sleepForTimeInterval:1];
		printf("\n %s %s %d",[self currentDateString].UTF8String,[NSThread currentThread].description.UTF8String,i);
	}
	dispatch_semaphore_signal(sem);
}
-(void)asyn_semaphore{
	dispatch_apply(3, dispatch_get_global_queue(0, 0), ^(size_t idx) {
		[self semaphore];
	});
}
//log

2019-07-24 17:25:04 <NSThread: 0x6000002a2940>{number = 1, name = main} 0
2019-07-24 17:25:05 <NSThread: 0x6000002a2940>{number = 1, name = main} 1
2019-07-24 17:25:06 <NSThread: 0x6000002e2b40>{number = 3, name = (null)} 2
2019-07-24 17:25:07 <NSThread: 0x6000002e2b40>{number = 3, name = (null)} 3
2019-07-24 17:25:08 <NSThread: 0x6000002d4740>{number = 4, name = (null)} 4
2019-07-24 17:25:09 <NSThread: 0x6000002d4740>{number = 4, name = (null)} 5
複製代碼

設計一個經典問題,火車票窗口買票,火車站賣票通常有多個窗口,排隊是每一個窗口排一個隊列,一個窗口同時只能賣一張票,那咱們設計一下如何實現多隊列同時訪問多個窗口的的問題。

-(void)muchQueueBuyTick{
	dispatch_queue_t queue= dispatch_queue_create("com.buy.tick", DISPATCH_QUEUE_CONCURRENT);
	dispatch_async(queue, ^{
		for (NSInteger i = 0; i < 5; i ++) {
			[self semaphore_buy_ticks:4];
		}
	});
	dispatch_queue_t queue2= dispatch_queue_create("com.buy2.tick", DISPATCH_QUEUE_CONCURRENT);
	dispatch_async(queue2, ^{
		for (NSInteger i = 0; i < 5; i ++) {
			[self semaphore_buy_ticks:2];
		}
	});
}
- (void)semaphore_buy_ticks:(NSInteger)windowsCount{
	static dispatch_semaphore_t sem;
	if (sem == NULL) {
		sem = dispatch_semaphore_create(windowsCount);
	}
	//信號量-1
	dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
	self.count--;
	if (self.count > 0) {
		printf("\n %s %s 第%ld我的買到票了",[self currentDateString].UTF8String,[NSThread currentThread].description.UTF8String,(long)self.count);
		[NSThread sleepForTimeInterval:0.2];
	}
	//信號量+1
	dispatch_semaphore_signal(sem);
}
//log

2019-07-24 18:01:44 <NSThread: 0x600003935e00>{number = 4, name = (null)} 第8我的買到票了
 2019-07-24 18:01:44 <NSThread: 0x600003904c40>{number = 3, name = (null)} 第8我的買到票了
 2019-07-24 18:01:45 <NSThread: 0x600003904c40>{number = 3, name = (null)} 第6我的買到票了
 2019-07-24 18:01:45 <NSThread: 0x600003935e00>{number = 4, name = (null)} 第6我的買到票了
 2019-07-24 18:01:45 <NSThread: 0x600003935e00>{number = 4, name = (null)} 第4我的買到票了
 2019-07-24 18:01:45 <NSThread: 0x600003904c40>{number = 3, name = (null)} 第4我的買到票了
 2019-07-24 18:01:45 <NSThread: 0x600003935e00>{number = 4, name = (null)} 第2我的買到票了
 2019-07-24 18:01:45 <NSThread: 0x600003904c40>{number = 3, name = (null)} 第2我的買到票了
 2019-07-24 18:01:45 <NSThread: 0x600003935e00>{number = 4, name = (null)} 第0我的買到票了
複製代碼

兩個窗口(兩個隊列),每一個窗口排了5(循環5次)我的,一共10(count=10)張票。 當同時一張票能夠分割2次,賣票的錯亂了,明顯錯誤了,如今把每張票都鎖起來,同時只能容許同一我的賣。

-(void)muchQueueBuyTick{
	dispatch_queue_t queue= dispatch_queue_create("com.buy.tick", DISPATCH_QUEUE_CONCURRENT);
	dispatch_async(queue, ^{
		for (NSInteger i = 0; i < 5; i ++) {
			[self semaphore_buy_ticks:1];
		}
	});
	dispatch_queue_t queue2= dispatch_queue_create("com.buy2.tick", DISPATCH_QUEUE_CONCURRENT);
	dispatch_async(queue2, ^{
		for (NSInteger i = 0; i < 5; i ++) {
			[self semaphore_buy_ticks:1];
		}
	});
}
//log
2019-07-24 18:03:56 <NSThread: 0x600000e1cac0>{number = 3, name = (null)} 第9我的買到票了
 2019-07-24 18:03:57 <NSThread: 0x600000e1e0c0>{number = 4, name = (null)} 第8我的買到票了
 2019-07-24 18:03:57 <NSThread: 0x600000e1cac0>{number = 3, name = (null)} 第7我的買到票了
 2019-07-24 18:03:57 <NSThread: 0x600000e1e0c0>{number = 4, name = (null)} 第6我的買到票了
 2019-07-24 18:03:57 <NSThread: 0x600000e1cac0>{number = 3, name = (null)} 第5我的買到票了
 2019-07-24 18:03:57 <NSThread: 0x600000e1e0c0>{number = 4, name = (null)} 第4我的買到票了
 2019-07-24 18:03:58 <NSThread: 0x600000e1cac0>{number = 3, name = (null)} 第3我的買到票了
 2019-07-24 18:03:58 <NSThread: 0x600000e1e0c0>{number = 4, name = (null)} 第2我的買到票了
 2019-07-24 18:03:58 <NSThread: 0x600000e1cac0>{number = 3, name = (null)} 第1我的買到票了
複製代碼

順序是對了,數量也對了。

再換一種思路實現鎖住窗口,咱們使用串行隊列也是能夠的。

//使用同步隊列賣票
- (void)sync_buy_tick{
	dispatch_async(dispatch_get_main_queue(), ^{
		self.count--;
		if (self.count > 0) {
			printf("\n %s %s 第%ld我的買到票了",[self currentDateString].UTF8String,[NSThread currentThread].description.UTF8String,(long)self.count);
			[NSThread sleepForTimeInterval:0.2];
		}
	});
}
//log
2019-07-25 09:31:56 <NSThread: 0x6000034d2e40>{number = 1, name = main} 第9我的買到票了
 2019-07-25 09:31:56 <NSThread: 0x6000034d2e40>{number = 1, name = main} 第8我的買到票了
 2019-07-25 09:31:56 <NSThread: 0x6000034d2e40>{number = 1, name = main} 第7我的買到票了
 2019-07-25 09:31:56 <NSThread: 0x6000034d2e40>{number = 1, name = main} 第6我的買到票了
 2019-07-25 09:31:57 <NSThread: 0x6000034d2e40>{number = 1, name = main} 第5我的買到票了
 2019-07-25 09:31:57 <NSThread: 0x6000034d2e40>{number = 1, name = main} 第4我的買到票了
 2019-07-25 09:31:57 <NSThread: 0x6000034d2e40>{number = 1, name = main} 第3我的買到票了
 2019-07-25 09:31:57 <NSThread: 0x6000034d2e40>{number = 1, name = main} 第2我的買到票了
 2019-07-25 09:31:57 <NSThread: 0x6000034d2e40>{number = 1, name = main} 第1我的買到票了
複製代碼

串行隊列不建立子線程,全部任務都在同一個線程執行,那麼他們就會排隊,其實無論多少人同時點擊買票,票的分割仍是串行的,因此線程鎖的可使用串行隊列來解決。

快速迭代方法:dispatch_apply

快速迭代就是同時建立不少線程來在作事情,如今工廠收到一個億的訂單,工廠原本只有2條生產線,如今緊急新建不少生產線來生產產品。

/*
同時新建了多條線程來作任務
*/
	dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t idx) {
		printf("\n %s %s ",[self dateUTF8],[self threadInfo]);
	});
	
	//log
2019-07-25 09:38:38 <NSThread: 0x600000966180>{number = 4, name = (null)} 
2019-07-25 09:38:38 <NSThread: 0x60000094a3c0>{number = 3, name = (null)} 
2019-07-25 09:38:38 <NSThread: 0x600000979dc0>{number = 5, name = (null)} 
2019-07-25 09:38:38 <NSThread: 0x60000090d3c0>{number = 1, name = main} 
2019-07-25 09:38:38 <NSThread: 0x60000095cfc0>{number = 6, name = (null)} 
2019-07-25 09:38:38 <NSThread: 0x600000950140>{number = 8, name = (null)} 
 2019-07-25 09:38:38 <NSThread: 0x60000095d0c0>{number = 9, name = (null)} 
 2019-07-25 09:38:38 <NSThread: 0x60000094a400>{number = 7, name = (null)} 
 2019-07-25 09:38:38 <NSThread: 0x600000966180>{number = 4, name = (null)} 
 2019-07-25 09:38:38 <NSThread: 0x60000094a3c0>{number = 3, name = (null)} 
複製代碼

能夠看到新建了3456789main來執行任務。

多線程RunLoop實戰

問題一:請問下邊代碼輸出什麼?

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    dispatch_queue_t  que= dispatch_get_global_queue(0, 0);
    dispatch_async(que, ^{
        NSLog(@"1");
        [self performSelector:@selector(test) withObject:nil afterDelay:.0];
        NSLog(@"3");
    });
}
- (void)test{
    NSLog(@"2");
}
複製代碼
  • 猜測1:結果是123
  • 猜測2:結果是132

有沒有第三種結果呢?

猜測1分析: 由於是延遲0s執行,固然是先執行2,再執行3了。

猜測2分析:

咱們來分析一下,異步加入全局隊列中,單個任務的時候會加入到子線程中,那麼會先輸出1,而後輸出3,最後輸出2.

最後驗證一下:

1
3
複製代碼

爲何2沒有出來呢?在看一下代碼,全局隊列,延遲執行,點進去函數查看,原來是在runloop.h文件中,咱們猜想延遲執行是timer添加到runloop中了,添加進去也應該輸出132的。由於在子線程中,沒有主動調用不會有runloop的,及時調用了也須要保活技術,那麼代碼改進一下

dispatch_queue_t  que= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(que, ^{
        NSLog(@"1");
        // 至關於[self test];
//       [self performSelector:@selector(test) withObject:nil];
        [self performSelector:@selector(test) withObject:nil afterDelay:.0];
        
        [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
        NSLog(@"3");
    });
複製代碼

經測試輸出了12,這和咱們猜測的仍是不對,原來輸出3放在了最後,致使的問題,RunLoop運行起來,進入了循環,則後面的就不會執行了,除非中止當前RunLoop,咱們再改進一下

dispatch_queue_t  que= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(que, ^{
        NSLog(@"1");
        // 至關於[self test];
//       [self performSelector:@selector(test) withObject:nil];
        [self performSelector:@selector(test) withObject:nil afterDelay:.0];
         NSLog(@"3");
        [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
    });
複製代碼

最後終於輸出了132。缺點是子線程成了死待,不死之身,關於怎麼殺死死待請看上篇優雅控制RunLoop生命週期。 關於performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay中有延遲的,都是添加到當前你線程的RunLoop,若是沒有啓動RunLoop和保活恐怕也不能一直執行。[self performSelector:@selector(test) withObject:nil]是在Foudation中,源碼是直接objc_msgSend(),至關於直接[self test],不會有延遲。

問題2:請問輸出什麼?

NSThread *thread=[[NSThread alloc]initWithBlock:^{
    NSLog(@"1");
}];
[thread start];
[self performSelector:@selector(test)
             onThread:thread
           withObject:nil
        waitUntilDone:YES];
複製代碼

這個和上面的相似,結果是打印了1就崩潰了,緣由是thread start以後執行完block就結束了,沒有runloop的支撐。當執行performSelector的時候,線程已經死掉。解決這個問題只須要向子線程中添加RunLoop,並且保證RunLoop不中止就好了。

總結

  • GCD異步負責執行耗時任務(例以下載,複雜計算),main線程負責更新UI
  • 隊列多任務異步執行最後全局執行完畢可使用group_notify來監聽執行完畢時間
  • 隊列多任務異步執行結束時間,中間攔截更新UI,而後再異步執行可使用dispatch_barrier_sync
  • 當多線程訪問同一個資源,可使用信號量來限制同時訪問資源的線程數量

參考資料

資料下載


最怕一輩子碌碌無爲,還安慰本身平凡難得。

廣告時間

相關文章
相關標籤/搜索