-
GCD---同步/異步 ,串行/并發(fā)
-
死鎖
-
GCD任務(wù)執(zhí)行順序
-
dispatch_barrier_async
-
dispatch_group_async
-
Dispatch Semaphore
-
延時(shí)函數(shù)(dispatch_after)
-
使用dispatch_once實(shí)現(xiàn)單例
一、GCD---隊(duì)列
iOS中,有GCD、NSOperation、NSThread等幾種多線程技術(shù)方案。
而GCD共有三種隊(duì)列類型:
main queue:通過dispatch_get_main_queue()獲得,這是一個與主線程相關(guān)的串行隊(duì)列。
global queue:全局隊(duì)列是并發(fā)隊(duì)列,由整個進(jìn)程共享。存在著高、中、低三種優(yōu)先級的全局隊(duì)列。調(diào)用dispath_get_global_queue并傳入優(yōu)先級來訪問隊(duì)列。
自定義隊(duì)列:通過函數(shù)dispatch_queue_create創(chuàng)建的隊(duì)列。
二、 死鎖
死鎖就是隊(duì)列引起的循環(huán)等待
1、一個比較常見的死鎖例子:主隊(duì)列同步
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"deallock");
});
// Do any additional setup after loading the view, typically from a nib.
}
在主線程中運(yùn)用主隊(duì)列同步,也就是把任務(wù)放到了主線程的隊(duì)列中。
同步對于任務(wù)是立刻執(zhí)行的,那么當(dāng)把任務(wù)放進(jìn)主隊(duì)列時(shí),它就會立馬執(zhí)行,只有執(zhí)行完這個任務(wù),viewDidLoad才會繼續(xù)向下執(zhí)行。
而viewDidLoad和任務(wù)都是在主隊(duì)列上的,由于隊(duì)列的先進(jìn)先出原則,任務(wù)又需等待viewDidLoad執(zhí)行完畢后才能繼續(xù)執(zhí)行,viewDidLoad和這個任務(wù)就形成了相互循環(huán)等待,就造成了死鎖。
想避免這種死鎖,可以將同步改成異步dispatch_async,或者將dispatch_get_main_queue換成其他串行或并行隊(duì)列,都可以解決。
2、同樣,下邊的代碼也會造成死鎖:
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
dispatch_sync(serialQueue, ^{
NSLog(@"deadlock");
});
});
外面的函數(shù)無論是同步還是異步都會造成死鎖。
這是因?yàn)槔锩娴娜蝿?wù)和外面的任務(wù)都在同一個serialQueue隊(duì)列內(nèi),又是同步,這就和上邊主隊(duì)列同步的例子一樣造成了死鎖
解決方法也和上邊一樣,將里面的同步改成異步dispatch_async,或者將serialQueue換成其他串行或并行隊(duì)列,都可以解決
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serialQueue2 = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
dispatch_sync(serialQueue2, ^{
NSLog(@"deadlock");
});
});
這樣是不會死鎖的,并且serialQueue和serialQueue2是在同一個線程中的。
三、GCD任務(wù)執(zhí)行順序
1、串行隊(duì)列先異步后同步
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(serialQueue, ^{
NSLog(@"2");
});
NSLog(@"3");
dispatch_sync(serialQueue, ^{
NSLog(@"4");
});
NSLog(@"5");
打印順序是13245
原因是:
首先先打印1
接下來將任務(wù)2其添加至串行隊(duì)列上,由于任務(wù)2是異步,不會阻塞線程,繼續(xù)向下執(zhí)行,打印3
然后是任務(wù)4,將任務(wù)4添加至串行隊(duì)列上,因?yàn)槿蝿?wù)4和任務(wù)2在同一串行隊(duì)列,根據(jù)隊(duì)列先進(jìn)先出原則,任務(wù)4必須等任務(wù)2執(zhí)行后才能執(zhí)行,又因?yàn)槿蝿?wù)4是同步任務(wù),會阻塞線程,只有執(zhí)行完任務(wù)4才能繼續(xù)向下執(zhí)行打印5
所以最終順序就是13245。
這里的任務(wù)4在主線程中執(zhí)行,而任務(wù)2在子線程中執(zhí)行。
如果任務(wù)4是添加到另一個串行隊(duì)列或者并行隊(duì)列,則任務(wù)2和任務(wù)4無序執(zhí)行(可以添加多個任務(wù)看效果)
2、performSelector
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self performSelector:@selector(test:) withObject:nil afterDelay:0];
});
這里的test方法是不會去執(zhí)行的,原因在于
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
這個方法要創(chuàng)建提交任務(wù)到runloop上的,而gcd底層創(chuàng)建的線程是默認(rèn)沒有開啟對應(yīng)runloop的,所有這個方法就會失效。
而如果將dispatch_get_global_queue改成主隊(duì)列,由于主隊(duì)列所在的主線程是默認(rèn)開啟了runloop的,就會去執(zhí)行(將dispatch_async改成同步,因?yàn)橥绞窃诋?dāng)前線程執(zhí)行,那么如果當(dāng)前線程是主線程,test方法也是會去執(zhí)行的)。
四、dispatch_barrier_async
1、問:怎么用GCD實(shí)現(xiàn)多讀單寫?
多讀單寫的意思就是:可以多個讀者同時(shí)讀取數(shù)據(jù),而在讀的時(shí)候,不能去寫入數(shù)據(jù)。并且,在寫的過程中,不能有其他寫者去寫。即讀者之間是并發(fā)的,寫者與讀者或其他寫者是互斥的。

這里的寫處理就是通過柵欄的形式去寫。
就可以用dispatch_barrier_sync(柵欄函數(shù))去實(shí)現(xiàn)
2、dispatch_barrier_sync的用法:
dispatch_queue_t concurrentQueue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i < 10; i++) {
dispatch_sync(concurrentQueue, ^{
NSLog(@"%zd",i);
});
}
dispatch_barrier_sync(concurrentQueue, ^{
NSLog(@"barrier");
});
for (NSInteger i = 10; i < 20; i++) {
dispatch_sync(concurrentQueue, ^{
NSLog(@"%zd",i);
});
}
這里的dispatch_barrier_sync上的隊(duì)列要和需要阻塞的任務(wù)在同一隊(duì)列上,否則是無效的。
從打印上看,任務(wù)0-9和任務(wù)任務(wù)10-19因?yàn)槭钱惒讲l(fā)的原因,彼此是無序的。而由于柵欄函數(shù)的存在,導(dǎo)致順序必然是先執(zhí)行任務(wù)0-9,再執(zhí)行柵欄函數(shù),再去執(zhí)行任務(wù)10-19。
- dispatch_barrier_sync: Submits a barrier block object for execution and waits until that block completes.(提交一個柵欄函數(shù)在執(zhí)行中,它會等待柵欄函數(shù)執(zhí)行完)
-
dispatch_barrier_async: Submits a barrier block for asynchronous execution and returns immediately.(提交一個柵欄函數(shù)在異步執(zhí)行中,它會立馬返回)
而dispatch_barrier_sync和dispatch_barrier_async的區(qū)別也就在于會不會阻塞當(dāng)前線程
比如,上述代碼如果在dispatch_barrier_async后隨便加一條打印,則會先去執(zhí)行該打印,再去執(zhí)行任務(wù)0-9和柵欄函數(shù);而如果是dispatch_barrier_sync,則會在任務(wù)0-9和柵欄函數(shù)后去執(zhí)行這條打印。
3、則可以這樣設(shè)計(jì)多讀單寫:
- (id)readDataForKey:(NSString *)key
{
__block id result;
dispatch_sync(_concurrentQueue, ^{
result = [self valueForKey:key];
});
return result;
}
- (void)writeData:(id)data forKey:(NSString *)key
{
dispatch_barrier_async(_concurrentQueue, ^{
[self setValue:data forKey:key];
});
}
五、dispatch_group_async
場景:在n個耗時(shí)并發(fā)任務(wù)都完成后,再去執(zhí)行接下來的任務(wù)。比如,在n個網(wǎng)絡(luò)請求完成后去刷新UI頁面。
dispatch_queue_t concurrentQueue = dispatch_queue_create("test1", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
for (NSInteger i = 0; i < 10; i++) {
dispatch_group_async(group, concurrentQueue, ^{
sleep(1);
NSLog(@"%zd:網(wǎng)絡(luò)請求",i);
});
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新頁面");
});
深入理解GCD之dispatch_group
六、Dispatch Semaphore
GCD 中的信號量是指 Dispatch Semaphore,是持有計(jì)數(shù)的信號。
Dispatch Semaphore 提供了三個函數(shù)
1.dispatch_semaphore_create:創(chuàng)建一個Semaphore并初始化信號的總量
2.dispatch_semaphore_signal:發(fā)送一個信號,讓信號總量加1
3.dispatch_semaphore_wait:可以使總信號量減1,當(dāng)信號總量為0時(shí)就會一直等待(阻塞所在線程),否則就可以正常執(zhí)行。
Dispatch Semaphore 在實(shí)際開發(fā)中主要用于:
- 保持線程同步,將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù)
- 保證線程安全,為線程加鎖
1、保持線程同步:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSInteger number = 0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
number = 100;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore---end,number = %zd",number);
dispatch_semaphore_wait加鎖阻塞了當(dāng)前線程,dispatch_semaphore_signal解鎖后當(dāng)前線程繼續(xù)執(zhí)行
2、保證線程安全,為線程加鎖:
在線程安全中可以將dispatch_semaphore_wait看作加鎖,而dispatch_semaphore_signal看作解鎖
首先創(chuàng)建全局變量
_semaphore = dispatch_semaphore_create(1);
注意到這里的初始化信號量是1。
- (void)asyncTask
{
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
count++;
sleep(1);
NSLog(@"執(zhí)行任務(wù):%zd",count);
dispatch_semaphore_signal(_semaphore);
}
異步并發(fā)調(diào)用asyncTask
for (NSInteger i = 0; i < 100; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self asyncTask];
});
}
然后發(fā)現(xiàn)打印是從任務(wù)1順序執(zhí)行到100,沒有發(fā)生兩個任務(wù)同時(shí)執(zhí)行的情況。
原因如下:
在子線程中并發(fā)執(zhí)行asyncTask,那么第一個添加到并發(fā)隊(duì)列里的,會將信號量減1,此時(shí)信號量等于0,可以執(zhí)行接下來的任務(wù)。而并發(fā)隊(duì)列中其他任務(wù),由于此時(shí)信號量不等于0,必須等當(dāng)前正在執(zhí)行的任務(wù)執(zhí)行完畢后調(diào)用dispatch_semaphore_signal將信號量加1,才可以繼續(xù)執(zhí)行接下來的任務(wù),以此類推,從而達(dá)到線程加鎖的目的。
六、延時(shí)函數(shù)(dispatch_after)
dispatch_after能讓我們添加進(jìn)隊(duì)列的任務(wù)延時(shí)執(zhí)行,該函數(shù)并不是在指定時(shí)間后執(zhí)行處理,而只是在指定時(shí)間追加處理到dispatch_queue
//第一個參數(shù)是time,第二個參數(shù)是dispatch_queue,第三個參數(shù)是要執(zhí)行的block
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"dispatch_after");
});
由于其內(nèi)部使用的是dispatch_time_t管理時(shí)間,而不是NSTimer。
所以如果在子線程中調(diào)用,相比performSelector:afterDelay,不用關(guān)心runloop是否開啟
七、使用dispatch_once實(shí)現(xiàn)單例
+ (instancetype)shareInstance {
static dispatch_once_t onceToken;
static id instance = nil;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}