同步異步驹暑、并行串行的區(qū)分
兩個容易搞混的概念:
同步異步:是指任務添加到線程上這個過程的同步和異步
串行并行:是指任務在線程上運行的串行和并行
使用block,invocation或者是dispatch_function_t(C函數(shù))添加任務兼吓,任務中都是需要執(zhí)行return返回的视搏,同步異步就是指是否和這個return同步,如果是同步添加到線程上的任務筋遭,把任務添加到線程上的添加操作本身會等到任務中的return執(zhí)行之后才執(zhí)行自己的return。如果是異步添加到線程上的任務透且,添加任務操作本身執(zhí)行后會立即返回,并不會等待任務完成后自己才返回,例如如下這個bug:
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("LivyNN", DISPATCH_QUEUE_SERIAL);
NSArray *array = [[NSArray alloc] initWithObjects:@"liwei", nil];
void *p = (__bridge void*)array;
dispatch_async_f(queue, p, sumAB);
}
void sumAB(void *input){
id object = (__bridge id)input;
[NSThread sleepForTimeInterval:3];
NSLog(@"%@", object);
NSLog(@"線程結束");
}
創(chuàng)建了一個線程(串行:并行串行不關緊要,只有一個任務最易,都是串行),聲明一個數(shù)組指針p傳給sumAB函數(shù)毕荐,添加到線程上,結果崩潰了BAD_ACCESS第美,原因如下:
dispatch_async_f執(zhí)行的是異步把任務添加到線程中慌闭,添加完成后立刻就返回了省古,這個時候任務還沒執(zhí)行,等到任務執(zhí)行的時候viewDidLoad函數(shù)已經(jīng)執(zhí)行完,p指針已經(jīng)被釋放了,就引起了BAD_ACCESS。相比之下如果改為同步添加:
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("LivyNN", DISPATCH_QUEUE_SERIAL);
NSArray *array = [[NSArray alloc] initWithObjects:@"liwei", nil];
void *p = (__bridge void*)array;
dispatch_sync_f(queue, p, sumAB);//修改了這里async->sync
}
void sumAB(void *input){
id object = (__bridge id)input;
[NSThread sleepForTimeInterval:3];
NSLog(@"%@", object);
NSLog(@"線程結束");
}
運行后發(fā)現(xiàn)沒有報錯:原因如下:
dispatch_sync_f把任務同步添加到線程中,添加任務的操作要和任務保持同步返回,也就是說:把任務添加到線程上去還要等他執(zhí)行完才能返回盟蚣,所以黍析,dispatch_sync_f函數(shù)一直等待sumAB函數(shù)執(zhí)行,三秒鐘后屎开,sumAB執(zhí)行完奄抽,dispatch_sync_f才執(zhí)行完逞度,最后viewDidLoad才執(zhí)行結束馆匿,在sumAB執(zhí)行期間一直處在viewDidLoad的函數(shù)周期里面赃蛛,p沒有被釋放呕臂,所以不會崩潰破托。
另外:如果一定要異步添加到線程上,需要保證p只能不被釋放掉诵闭,使用__bridge_retain和__bridge_transfer關鍵字轉交所有權瘟芝,如下:
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("LivyNN", DISPATCH_QUEUE_SERIAL);
NSArray *array = [[NSArray alloc] initWithObjects:@"liwei", nil];
void *p = (__bridge_retained void*)array;//retain
dispatch_async_f(queue, p, sumAB);
}
void sumAB(void *input){
id object = (__bridge_transfer id)input;//transfer
[NSThread sleepForTimeInterval:3];
NSLog(@"%@", object);
NSLog(@"線程結束");
}
P指針指向對象的所有權會轉交到sumAB的object手中易桃,直到sumAB執(zhí)行完之前該對象都不會被釋放。
理解同步異步后并行和串行就容易理解了:
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("LivyNN", DISPATCH_QUEUE_SERIAL);//serial是串行
NSArray *array = [[NSArray alloc] initWithObjects:@"liwei", nil];
void *p = (__bridge_retained void*)array;//retain
dispatch_async_f(queue, p, sumAB);
dispatch_async_f(queue, p, sumAB);
}
void sumAB(void *input){
id object = (__bridge_transfer id)input;//transfer
[NSThread sleepForTimeInterval:3];
NSLog(@"%@", object);
NSLog(@"任務結束");
}
- 上面一共執(zhí)行了6秒鐘锌俱,3秒鐘和6秒鐘分別打印了任務結束晤郑。
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("LivyNN", DISPATCH_QUEUE_ConCurrent);//concurrent并行
NSArray *array = [[NSArray alloc] initWithObjects:@"liwei", nil];
void *p = (__bridge_retained void*)array;//retain
dispatch_async_f(queue, p, sumAB);
dispatch_async_f(queue, p, sumAB);
}
void sumAB(void *input){
id object = (__bridge_transfer id)input;//transfer
[NSThread sleepForTimeInterval:3];
NSLog(@"%@", object);
NSLog(@"任務結束");
}
上面一共執(zhí)行3秒鐘,第三秒鐘同時打印了兩個任務技術贸宏。
另一方面:串行并行可以視為隊列本身的能力造寝,同步異步可以視為是調用者的調用方式:當調用者使用同步方式(sync)向異步(concurrent)線程中添加兩個任務時,線程有能力開辟多線程并行執(zhí)行兩個任務吭练,但是調用者同步添加說明他不需要線程這樣做诫龙,所以兩個任務是串行的;如果調用者異步(async)向一個串行(serial)線程添加兩個任務鲫咽,雖然調用者試圖讓兩個任務并發(fā)執(zhí)行签赃,但是線程能力有限,所以兩個任務還是串行執(zhí)行的分尸;所以锦聊,如果需要兩個任務并發(fā)執(zhí)行,需要異步地向并行線程中添加多個任務箩绍。
GCD
*創(chuàng)建一個線程:dispatch_queue_t queue = dispatch_queue_create("一個線程的名字孔庭,在調試的時候可以看到這個名字",DISPATCH_QUEUE_ConCurrent/DISPATCH_QUEUE_SERIAL);c是并行,s是串行材蛛。
- 把一個任務添加到一個線程上去:提供block和void*函數(shù)指針兩種方法分別是(帶f的是void *圆到,不帶f的是block,帶a的是異步,不帶a的是同步):
*dispatch_async
*dispatch_async_f
*dispatch_sync
*dispatch_sync_f
- 添加方法:
dispatch_async(queue, ^{
/*異步添加block*/
});
dispatch_async_f(queue, p, sumAB);//隊列,參數(shù),函數(shù)
dispatch_sync(queue, ^{
/*同步添加block*/
});
dispatch_sync_f(queue, p, sumAB);//隊列,參數(shù),函數(shù)仰税,注意函數(shù)要定義成C語言的形狀,p需要是void*類型的地址构资,如果需要傳id類型抽诉,則需要__bridge關鍵字轉化,int類型參數(shù)可以直接int a = 0;&a
- 獲取到主隊列:
dispatch_queue_t mainQueue = dispatch_get_main_queue();
- 獲取到全局隊列:
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
*其中:DISPATCH_QUEUE_PRIORITY_LOW是個枚舉陨簇,有:HIGH,DEFAULT,LOW,BACKGROUND,分別對應于2,0迹淌,-2河绽,MIN四個數(shù)字。
第二個參數(shù)留待以后使用唉窃。
通嘲沂危看到的(0,0)就是獲取到全局default優(yōu)先級的隊列。
注意注意:不能往主線程上加同步任務N品荨9豆颉廷痘!不能往主線程上加同步任務!<选笋额!不能往主線程上加同步任務!E窭兄猩!
原因是同步任務的添加任務操作會等待任務執(zhí)行后才return,而同步添加的任務會等到主線程上當前的任務執(zhí)行完才會執(zhí)行后添加上去的任務鉴未,當前的任務是什么枢冤?就是添加任務操作。所以添加任務的任務要同步等待所添加的任務執(zhí)行完铜秆,所添加的任務又要等待主線程當前的任務執(zhí)行完淹真,當前的任務又是添加任務的任務。连茧。趟咆。。GG
但是可以在其他線程里同步添加任務到主隊列:其他線程需要等主線程梅屉,主線程不需要等其他線程值纱。
*并行循環(huán)迭代:dispatch_apply:
dispatch_queue_t concurrentQueue = dispatch_queue_create("livyNNTest", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(100, concurrentQueue, ^(size_t size) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"complete");
});
- 注意這個方法是同步的,會阻塞線程坯汤,與正常的for循環(huán)一樣虐唠。且并不是完全并行的,如上面代碼惰聂,實測每秒鐘會同時執(zhí)行6-8次代碼塊疆偿;而不是100次。
*隊列掛起:dispatch_suspend(queue);當前正在執(zhí)行的任務不會中斷
*隊列恢復:dispatch_resume(queue);
- 信號量(可以實現(xiàn)concurrent線程的最大并發(fā)數(shù)):
dispatch_queue_t queue = dispatch_queue_create("liwei", DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t signal = dispatch_semaphore_create(3);//創(chuàng)建信號量搓幌,初始有三個
dispatch_apply(100, queue, ^(size_t i) {
dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);//如果有可用的信號量就直接返回杆故,如果沒有就在這等
[NSThread sleepForTimeInterval:1];
NSLog(@"%d",i);
dispatch_semaphore_signal(signal);//運行完了釋放信號量以循環(huán)使用
});
- 分組:
*創(chuàng)建分組:dispatch_group_t group = dispatch_group_create();
*把任務放進分組:dispatch_group_async/synx(group,queue,^{任務})
*等待分組內任務全部完成:dispatch_group_wait(group,FOREVER);第二個參數(shù)是最大等待時間,等不到就不阻塞了。
*示例:
dispatch_queue_t queue = dispatch_queue_create("liwei", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"第一個任務完成了");
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:3.0];
NSLog(@"第二個任務執(zhí)行完了");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"分組中的任務都執(zhí)行完了");
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
- 1秒后第一個任務執(zhí)行完溉愁,再過兩秒处铛,后面兩句一起打印。
NSOperationQueue
*NSOperationQueue是基于GCD的封裝拐揭。
- 創(chuàng)建一個
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperationQueue *queue = [NSOperationQueue mainQueue];
*添加block任務,相當于dispatch_async(queue,block):
[queue addOperationWithBlock:^{
}];
- 創(chuàng)建任務撤蟆,添加到queue上
//block任務
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
}];
[queue addOperation:blockOperation];
//invocation任務
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationMethod) object:nil];
[queue addOperation:invocationOperation];
//子類化NSOperation,覆蓋其main方法,得子類SubOperation
SubOperation *subOperation = [[SubOperation alloc] initWith::::];
[queue addOperation:subOperation];
//添加一組
NSArray *operationArray = @[blockOperation,invocationOperation];
[queue addOperations:operationArray waitUntilFinished:NO]; //NO相當于async堂污,yes相當于sync
注意:一個Operation只能被加到一個線程中家肯,且只能加一次;如果有很多類似的Operation可以使用子類化Operation的方式盟猖。
添加依賴:
[blockOperation addDependency:invocationOperation];
- 注意:如果invocationOperation從來沒有被添加到任何一個queue中過讨衣,blockOperation也永遠都不會被執(zhí)行换棚;另外,依賴可以跨越線程:
[blockOperation addDependency:invocationOperation];
[queue addOperation:invocationOperation];
[mainQueue addOperation:blockOperation];
- 這樣寫添加的依賴也是生效的反镇,blockOperation在主線程中一直處于等待狀態(tài)圃泡,知道blockOperation在queue中執(zhí)行完。
*Operation優(yōu)先級,代碼如下:
NSBlockOperation *blockOperation0 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOperation0");
}];
NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOperation1");
}];
NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOperation2");
}];
NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOperation3");
}];
NSBlockOperation *blockOperation4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOperation4");
}];
blockOperation0.queuePriority = NSOperationQueuePriorityVeryLow;
blockOperation1.queuePriority = NSOperationQueuePriorityLow;
blockOperation2.queuePriority = NSOperationQueuePriorityNormal;
blockOperation3.queuePriority = NSOperationQueuePriorityHigh;
blockOperation4.queuePriority = NSOperationQueuePriorityVeryHigh;
NSArray *operationArray = @[blockOperation1,blockOperation3,blockOperation2,blockOperation0,blockOperation4];
[queue addOperations:operationArray waitUntilFinished:NO];
- 輸出結果看到1-4都是隨機出現(xiàn)的愿险,這優(yōu)先級也沒用捌睦?不能夠辆亏,隨機出現(xiàn)是NSOperation在沒指定最大并發(fā)數(shù)的情況下默認是0风秤,無限的,如果這樣寫就可以清楚地看到4扮叨,3缤弦,2,1彻磁,0:
queue.maxConcurrentOperationCount = 1;
- 上面這種情況下是串行隊列碍沐,所以先找優(yōu)先級高的執(zhí)行再找優(yōu)先級低的
queue.maxConcurrentOperationCount = 2;
- 如果是這樣,就會4衷蜓,3或者3累提,4在前兩個;1磁浇,2或2斋陪,1在中間,最后執(zhí)行0
queue.maxConcurrentOperationCount = 3;
如果是這樣置吓,就是4无虚,3,2在前面隨機排列衍锚,1友题,0在后面隨機排列
什么也不寫的時候相當于:
queue.maxConcurrentOperationCount = MAX;
意思就是能開多少子線程就開多少子線程,所以MAX>5的情況下戴质,就是0-4隨機排列度宦,這種情況下優(yōu)先級順序不生效,因為能力太強不用考慮誰優(yōu)先置森,大家都優(yōu)先斗埂,都優(yōu)先就隨機了符糊。
取消線程:[operation cancel];
取消隊列中的所有線程[operationQueue cancelAllOperations];
掛起:[queue setSuspended:YES];
恢復:[queue setSuspended:NO];
等待某個操作:[operation waitUntilFinished];//注意:千萬不要在主線程中執(zhí)行這個
等待queue中的操作都完成:[queue waitUntilAllOperationsAreFinished];