容易混淆的概念(隊列、線程、串行并行、同步異步、任務(wù))
任務(wù):代碼塊(一行或多行),也叫block
隊列:管理任務(wù)的一個線性數(shù)據(jù)結(jié)構(gòu),有兩種:串行和并行。區(qū)別是是否有開啟新線程的能力。
同步異步:說的是任務(wù)的執(zhí)行方式。同步指的是阻塞當(dāng)前線程等待該任務(wù)執(zhí)行完才繼續(xù)下面的任務(wù)。
//是否開辟新線程不是隊列決定的,是由系統(tǒng)線程池決定的。經(jīng)驗:
1.串行+同步不創(chuàng)建,串行+異步會創(chuàng)建(主隊列除外)
2.并行+同步不創(chuàng)建,并行+異步可能會創(chuàng)建
// dispatch_sync官方原文
This function submits a block to the specified dispatch queue for synchronous execution. Unlike dispatch_async, this function does not return until the block has finished. Calling this function and targeting the current queue results in deadlock.
Unlike with dispatch_async, no retain is performed on the target queue. Because calls to this function are synchronous, it "borrows" the reference of the caller. Moreover, no Block_copy is performed on the block.
As a performance optimization, this function executes blocks on the current thread whenever possible, with one exception: Blocks submitted to the main dispatch queue always run on the main thread.
//大意:
1、是在當(dāng)前線程上指定隊列中同步地執(zhí)行任務(wù)。會阻塞當(dāng)前線程等待該任務(wù)執(zhí)行完才繼續(xù)下面的任務(wù),主隊列除外。
2、是立即返回?zé)o需等待的,所以不需要block copy操作
iOS多線程方案有四種:pthread(C),NSThread(OC), GCD(C,自動管理),NSOperation(GCD的封裝)。常用的是自動管理線程生命周期的GCD和NSOperation,其中GCD有以靈活性居先。
一、GCD
1、四種基本組合
串行+同步
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
// 打印1,崩潰在__DISPATCH_WAIT_FOR_QUEUE__,死鎖
串行+異步
dispatch_queue_t queue = dispatch_get_main_queue();
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2-1-%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-2-%@", [NSThread currentThread]);
});
NSLog(@"3");
// 打印1,3,2-1-main,2-2-main
并行+同步
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
NSLog(@"1");
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"2-%@", [NSThread currentThread]);
});
NSLog(@"3");
// 打印1,2-main,3
并行+異步
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2-1-%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-2-%@", [NSThread currentThread]);
});
NSLog(@"3");
// 打印1,3,2-1-num7,2-2-num4,開啟了新線程
總結(jié):串行+同步會死鎖。并行+異步可開啟新線程。
2、復(fù)雜情況
不同隊列嵌套
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1-%@", [NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2-%@", [NSThread currentThread]);
});
NSLog(@"3-%@", [NSThread currentThread]);
});
NSLog(@"4-%@", [NSThread currentThread]);
// 打印4-main,1-num5,2-mian,3-num5。1/4順序不定
相同隊列嵌套
dispatch_queue_t queue = dispatch_queue_create("com.xx", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"#--0");
dispatch_sync(queue, ^{
NSLog(@"#--1");
});
NSLog(@"#--2");
});
// 打?。?,然后死鎖
dispatch_queue_t queue = dispatch_queue_create("com.xx", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"#--0,%@",[NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"#--1,%@",[NSThread currentThread]);
});
NSLog(@"#--2,%@",[NSThread currentThread]);
});
//打?。?-main 2-main 1-null,主隊列特殊,全是main
Exam 1
NSLog(@"#--test");
__block int i = 0;
dispatch_queue_t queue0 = dispatch_queue_create(0, DISPATCH_QUEUE_SERIAL);
while (i < 1000) {
//dispatch_queue_t queue = dispatch_queue_create(0, 0);
dispatch_async(queue0, ^{
//NSLog(@"#--thread=%@",[NSThread currentThread]);
i++;
});
}
NSLog(@"#--i=%d",i);
// >=1000
3、api使用
1.dispatch_after
// 適用于不太精準(zhǔn)的延遲執(zhí)行
NSLog(@"start");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"end");
});
dispatch_after不像NSTimer和performSelector:afterDelay一樣需要runloop支持,但dispatch_after 并不是在指定時間后執(zhí)行任務(wù),而是在指定時間之后才將任務(wù)提交到隊列中,這個延遲的時間是不精確的。
- dispatch_once
static TheClass *instance = nil;
+ (instance)sharedInstance {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[TheClass alloc] init];
});
return instance;
}
- dispatch_apply
同步執(zhí)行按照指定的次數(shù)提交到指定的隊列中的任務(wù),可以將迭代任務(wù)轉(zhuǎn)換為并發(fā)任務(wù),迭代任務(wù)繁重時用它效率成倍提升。
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t iteration) {
NSLog(@"%zu-%@", iteration, [NSThread currentThread]);
});
// 可創(chuàng)建多個線程
- dispatch_barrier_async
提供一個 “柵欄” 將兩組異步執(zhí)行的任務(wù)分隔開,保證先于柵欄方法提交到隊列的任務(wù)全部執(zhí)行完成之后,然后開始執(zhí)行將柵欄任務(wù),等到柵欄任務(wù)執(zhí)行完成后,該隊列便恢復(fù)原本執(zhí)行狀態(tài)。只適用于自定義并發(fā)隊列,全局并發(fā)隊列和串行隊列無意義。
dispatch_queue_t queue = dispatch_queue_create("thread", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"test1");
});
dispatch_async(queue, ^{
NSLog(@"test2");
});
dispatch_async(queue, ^{
NSLog(@"test3");
});
dispatch_barrier_sync(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"barrier");
});
NSLog(@"aaa");
dispatch_async(queue, ^{
NSLog(@"test4");
});
NSLog(@"bbb");
dispatch_async(queue, ^{
NSLog(@"test5");
});
dispatch_async(queue, ^{
NSLog(@"test6");
});
//打印 test1,test3,test2,barrier,aaa,bbb,test4,test5,test6
//異步 aaa,test1,bbb,test2,test3,barrier,test4,test5,test6
// 執(zhí)行的順序,同一線程內(nèi)同步有序,異步無序和執(zhí)行任務(wù)效率有關(guān)。不同線程只和任務(wù)效率有關(guān)。
5.dispatch_group
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"1");
// dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
dispatch_async(queue, ^{
sleep(1); //這里線程睡眠1秒鐘,模擬異步請求
NSLog(@"2");
// dispatch_group_leave(group);
});
});
dispatch_group_notify(group, queue, ^{
NSLog(@"3");
});
// dispatch_group_wait(group, DISPATCH_TIME_NOW);
NSLog(@"4");
dispatch_async(queue, ^{
NSLog(@"5");
});
}
// 打印1,4,3,5,2(3,5順序不定)。
//dispatch_group_enter/leave: notify只負(fù)責(zé)group里面的任務(wù)執(zhí)行完,如果group里面嵌套有異步任務(wù),是不會管他就直接返回的。這時就需要enter/leave了,成對出現(xiàn)的,enter多了會阻塞,leave多了會崩潰。
// dispatch_group_wait:阻塞group到某個時刻,然后釋放繼續(xù)執(zhí)行下面的任務(wù),參數(shù)DISPATCH_TIME_NOW表示不阻塞,相當(dāng)于沒有,參數(shù)DISPATCH_TIME_FOREVER表示永遠(yuǎn)阻塞,還可以自定義為3秒后的時刻,表示阻塞3秒,之后放開。打開wait并設(shè)置time為FOREVER后,打印為1,4,3,5,2,3在5前面
6、dispatch_semaphore
GCD的鎖機(jī)制,iOS線程同步除了os_unfair_lock 和
OSSpinLock之外,dispatch_semaphore的性能是很好的極力推薦使用。
dispatch_semaphore_create:根據(jù)傳入的初始值創(chuàng)建一個信號量。
不可傳入負(fù)值。運(yùn)行過程中,若內(nèi)部值為負(fù)數(shù),則這個值的絕對值便是正在等待資源的線程數(shù)。
dispatch_semaphore_wait:信號量 -1。 -1 之后的結(jié)果值小于 0 時,線程阻塞,并以 FIFO 的方式等待資源。
dispatch_semaphore_signal:信號量 +1。 +1 之后的結(jié)果值大于 0 時,以 FIFO 的方式喚醒等待的線程。
一般用來:控制最大并發(fā)數(shù)、數(shù)組多線程安全、阻塞異步任務(wù)
// 控制最大并發(fā)數(shù)
dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 10; i++) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
NSLog(@"%d-%@", i, [NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
}
// YYKit 中 YYThreadSafeArray 的實現(xiàn)(無法多讀)
// 通過宏定義對代碼塊進(jìn)行加鎖操作
#define LOCK(...) dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \
__VA_ARGS__; \
dispatch_semaphore_signal(_lock);
// id obj = array[idx];
- (id)objectAtIndexedSubscript:(NSUInteger)idx {
LOCK(id o = [_arr objectAtIndexedSubscript:idx]); return o;
}
// array[idx] = obj;
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx {
LOCK([_arr setObject:obj atIndexedSubscript:idx]);
}
7、dispatch_source
用GCD的函數(shù)指定一個希望監(jiān)聽的系統(tǒng)事件類型,再指定一個捕獲到事件后進(jìn)行邏輯處理的閉包或者函數(shù)作為回調(diào)函數(shù),然后再指定一個該回調(diào)函數(shù)執(zhí)行的Dispatch Queue即可,當(dāng)監(jiān)聽到指定的系統(tǒng)事件發(fā)生時會調(diào)用回調(diào)函數(shù),將該回調(diào)函數(shù)作為一個任務(wù)放入指定的隊列中執(zhí)行。
https://blog.csdn.net/hailideboke/article/details/78711514
4、靈活運(yùn)用
1.阻塞異步任務(wù)的幾種方式
group、barrier、semaphore
二、NSOperation
三、其他相關(guān)問題
1、線程與runloop
延時執(zhí)行任務(wù):
dispatch_after 比 NSTimer 優(yōu)秀,因為他不需要指定 Runloop 的運(yùn)行模式。 dispatch_after 比 performSelector:afterDelay: 優(yōu)秀,因為它不需要 Runloop 支持。
iOS定時器:
NSTimer:
dispatch_after:
2、線程與數(shù)據(jù)安全
多讀單寫-barrier、單讀單寫-semaphore
3、如何取消GCD任務(wù)
dispatch_block_cancel、外部變量控制