講GCD之前渗蟹,必須得說說線程
線程(英語:thread)是操作系統(tǒng)能夠進行運算調(diào)度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流吁津,一個進程中可以并發(fā)多個線程,每條線程并行執(zhí)行不同的任務(wù)堕扶。 以下是 iOS 中 4 套多線程方案
- Pthreads : POSIX線程(POSIX threads)碍脏,簡稱Pthreads,是線程的POSIX標(biāo)準(zhǔn)稍算。該標(biāo)準(zhǔn)定義了創(chuàng)建和操縱線程的一整套API典尾。在類Unix操作系統(tǒng)(Unix、Linux糊探、Mac OS X等)中钾埂,都使用Pthreads作為操作系統(tǒng)的線程。
- NSThread : 是三種方法里面相對輕量級的科平,但需要管理線程的生命周期褥紫、同步、加鎖問題瞪慧,這會導(dǎo)致一定的性能開銷
- Grand Central Dispatch(簡稱GCD髓考,iOS4才開始支持):提供了一些新特性、運行庫來支持多核并行編程汞贸,它的關(guān)注點更高:如何在多個cpu上提升效率
- NSOperation & NSOperationQueue : 蘋果公司對 GCD 的封裝绳军,完全面向?qū)ο螅琋SOperation以面向?qū)ο蟮姆绞椒庋b了需要執(zhí)行的操作矢腻,不必關(guān)心線程管理门驾、同步等問題。NSOperation是一個抽象基類多柑,iOS提供了兩種默認(rèn)實現(xiàn):NSInvocationOperation和NSBlockOperation奶是,當(dāng)然也可以自定義NSOperation
Pthreads的函數(shù)我們幾乎不會用到
NSThread倒是有一些API,使用很方便來看一下
//取消線程
- (void)cancel;
//啟動線程
- (void)start;
//判斷某個線程的狀態(tài)的屬性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;
//設(shè)置和獲取線程名字
-(void)setName:(NSString *)n;
-(NSString *)name;
//獲取當(dāng)前線程信息
+ (NSThread *)currentThread;
//獲取主線程信息
+ (NSThread *)mainThread;
//使當(dāng)前線程暫停一段時間竣灌,或者暫停到某個時刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;
主角出場
Grand Central Dispatch
GCD開源地址聂沙。
GCD的好處在于它不需要管理線程的生命周期,線程的創(chuàng)建與分配初嘹,它是蘋果為多核的并行運算提出的解決方案及汉,所以會自動合理地利用更多的CPU內(nèi)核。
來看看怎么使用GCD
描述 | 說明 |
---|---|
queue | 隊列 |
main | 主隊列 |
global | 全局隊列 |
dispatch_queue_t | 描述隊列 |
dispatch_block_t | 描述任務(wù) |
dispatch_once_t | 描述一次性 |
dispatch_time_t | 描述時間 |
dispatch_group_t | 描述隊列組 |
dispatch_semaphore_t | 描述信號量 |
函數(shù) | 說明 |
---|---|
dispatch_sync() | 同步執(zhí)行 |
dispatch_async() | 異步執(zhí)行 |
dispatch_after() | 延時執(zhí)行 |
dispatch_once() | 一次性執(zhí)行 |
dispatch_apply() | 快速遍歷 |
dispatch_queue_create() | 創(chuàng)建隊列 |
dispatch_group_create() | 創(chuàng)建隊列組 |
dispatch_group_async() | 提交任務(wù)到隊列組 |
dispatch_group_enter() | |
dispatch_group_leave() | 進入屯烦、離開組隊列 |
dispatch_group_notify() | 監(jiān)聽隊列組執(zhí)行完 |
dispatch_group_wait() | 設(shè)置等待時間 |
- 同步異步線程創(chuàng)建
//同步線程
dispatch_sync(隊列, ^(block)) 阻塞線程
//異步線程
dispatch_async(隊列, ^(block)) 不阻塞線程
- 系統(tǒng)標(biāo)準(zhǔn)兩個隊列
//全局隊列坷随,一個并行的隊列
dispatch_get_global_queue
//主隊列,主線程中的唯一隊列驻龟,一個串行隊列
dispatch_get_main_queue
- 自定義隊列
//串行隊列
dispatch_queue_create("com.boundless.serial", DISPATCH_QUEUE_SERIAL)
dispatch_queue_create("com.boundless.serial", NULL) // NULL默認(rèn)為串行
//并行隊列
dispatch_queue_create("com.boundless.concurrent", DISPATCH_QUEUE_CONCURRENT)
//隊列優(yōu)先級
* DISPATCH_QUEUE_PRIORITY_HIGH 優(yōu)先級高
* DISPATCH_QUEUE_PRIORITY_DEFAULT 優(yōu)先級默認(rèn)
* DISPATCH_QUEUE_PRIORITY_LOW 優(yōu)先級低
* DISPATCH_QUEUE_PRIORITY_BACKGROUND 優(yōu)先級最低 表示用戶不會察覺的任務(wù)温眉,使用它來處理預(yù)加載,或者不需要用戶交互和對時間不敏感的任務(wù)
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 可直接設(shè)置優(yōu)先級
自定義隊列優(yōu)先級設(shè)置方法
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0);
dispatch_queue_t custom_concrrent_priority_queue = dispatch_queue_create("com.boundless.concurrent", attr);
何時使用何種隊列類型
- 主隊列(順序):隊列中有任務(wù)完成需要更新UI時翁狐,dispatch_after在這種類型中使用类溢。
- 并發(fā)隊列:用來執(zhí)行與UI無關(guān)的后臺任務(wù),dispatch_sync放在這里露懒,方便等待任務(wù)完成進行后續(xù)處理或和dispatch barrier同步闯冷。dispatch groups放在這里也不錯。
- 自定義順序隊列:順序執(zhí)行后臺任務(wù)并追蹤它時隐锭。這樣做同時只有一個任務(wù)在執(zhí)行可以防止資源競爭窃躲。dipatch barriers解決讀寫鎖問題的放在這里處理。dispatch groups也是放在這里钦睡。
看代碼
/** 全局并行隊列 */
dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/** 主串行隊列 */
dispatch_queue_t main_queue = dispatch_get_main_queue();
/** 自定義串行隊列 */
dispatch_queue_t custom_serial_queue = dispatch_queue_create("com.boundless.serial", DISPATCH_QUEUE_CONCURRENT);
/** 自定義并發(fā)隊列 */
dispatch_queue_t custom_concrrent_queue = dispatch_queue_create("com.boundless.concurrent", DISPATCH_QUEUE_CONCURRENT);
/** 自定義隊列設(shè)置優(yōu)先級 */
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0);
dispatch_queue_t custom_concrrent_priority_queue = dispatch_queue_create("com.boundless.concurrent", attr);
/** 任務(wù)(^{})在全局隊列(global_queue)中同步執(zhí)行(dispatch_sync) */
dispatch_sync(global_queue, ^{
NSLog(@"執(zhí)行任務(wù)");
});
/** 任務(wù)(^{})在全主隊列(main_queue)中同步執(zhí)行(dispatch_sync) */
dispatch_sync(main_queue, ^{
NSLog(@"執(zhí)行任務(wù)");
});
/** 任務(wù)(^{})在全局隊列(global_queue)中異步執(zhí)行(dispatch_async) */
dispatch_async(global_queue, ^{
NSLog(@"執(zhí)行任務(wù)");
});
/** 任務(wù)(^{})在全主隊列(main_queue)中異步執(zhí)行(dispatch_async) */
dispatch_async(main_queue, ^{
NSLog(@"執(zhí)行任務(wù)");
});
開發(fā)中常用的一些GCD函數(shù)
1.耗時操作完成回到主線程
dispatch_async(global_queue, ^{
NSLog(@"執(zhí)行任務(wù)");
dispatch_async(main_queue, ^{
NSLog(@"回到主線程刷新UI");
});
});
2.延遲執(zhí)行
- #define NSEC_PER_SEC 1000000000ull //每秒有多少納秒
- #define USEC_PER_SEC 1000000ull //每秒有多少毫秒
- #define NSEC_PER_USEC 1000ull //每毫秒有多少納秒
/**
延遲執(zhí)行
_func_ dispatch_time(<#dispatch_time_t when#>, <#int64_t delta#>)
_func_ dispatch_after(<#dispatch_time_t when#>, <#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
*/
- (void)func_dispatch_after{
// 延遲時間
double delayInSeconds = 2.0;
dispatch_time_t time_source = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
// 主隊列中延遲執(zhí)行
dispatch_after(time_source, dispatch_get_main_queue(), ^{
});
}
3.執(zhí)行一次(單例)
/**
執(zhí)行一次
_func_ dispatch_once(<#dispatch_once_t * _Nonnull predicate#>, <#^(void)block#>)
@return
*/
+ (instancetype)func_dispatch_Once{
static GCDVC *_singleVC = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_singleVC = [GCDVC new];
});
return _singleVC;
}
4.隊列組( 可以使用 queue 的隊列組)
- dispatch_group_create() //創(chuàng)建組
- dispatch_group_async(dispatch_group_t _Nonnull group, dispatch_queue_t _Nonnull queue, ^(void)block) //添加任務(wù)到相應(yīng)隊列組
- dispatch_group_notify(dispatch_group_t _Nonnull group, dispatch_queue_t _Nonnull queue, ^(void)block) //通知蒂窒,不阻塞線程
- dispatch_group_wait(dispatch_group_t _Nonnull group, dispatch_time_t timeout) //阻塞線程到隊列任務(wù)執(zhí)行完
/**
隊列組
可以使用 queue 的隊列組
*/
- (void)func_dispatch_groups{
// 組
dispatch_group_t group = dispatch_group_create();
// 全局隊列
dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, global_queue, ^{
NSLog(@"任務(wù) 1");
});
dispatch_group_async(group, global_queue, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"任務(wù) 2");
});
dispatch_group_async(group, global_queue, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任務(wù) 3");
});
/** 判斷 隊列組全部執(zhí)行完成 的兩個方法*/
// dispatch_group_wait(group, DISPATCH_TIME_FOREVER); 阻塞當(dāng)前線程 直到隊列組任務(wù)執(zhí)行完
// 隊列組任務(wù)執(zhí)行完 接到通知 ,不會阻塞線程
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有執(zhí)行完成");
});
}
5.隊列組 ( 無法直接使用隊列變量(queue)的 打包組)
- dispatch_group_enter(dispatch_group_t _Nonnull group)
- dispatch_group_leave(dispatch_group_t _Nonnull group)
/**
隊列組
無法直接使用隊列變量(queue)的 打包組
*/
- (void)func_dispatch_enterAndLeave{
// URL
NSString *URLString = [NSString stringWithFormat:@"http://www.baidu.com"];
NSURL *URL = [[NSURL alloc] initWithString:URLString];
// session
NSURLSession *sessionManager = [NSURLSession sharedSession];
sessionManager.configuration.timeoutIntervalForRequest = 20;
sessionManager.configuration.timeoutIntervalForResource = 20;
// 創(chuàng)建 group
dispatch_group_t group = dispatch_group_create();
// task
// 進入group
dispatch_group_enter(group);
NSURLSessionTask *task =
[sessionManager dataTaskWithURL:URL
completionHandler:^(NSData * _Nullable data,
NSURLResponse * _Nullable response,
NSError * _Nullable error) {
NSLog(@"拿到數(shù)據(jù)");
// 離開group
dispatch_group_leave(group);
}];
// resume
[task resume];
}
自己創(chuàng)建隊列:使用dispatch_group_async荞怒。
無法直接使用隊列變量(如使用AFNetworking添加異步任務(wù)):使用dispatch_group_enter洒琢,dispatch_group_leave。
使用dispatch_group_enter褐桌,dispatch_group_leave就可以方便的將一系列網(wǎng)絡(luò)請求“打包”起來~
添加結(jié)束任務(wù)
添加結(jié)束任務(wù)也可以分為兩種情況衰抑,如下:
在當(dāng)前線程阻塞的同步等待:dispatch_group_wait。
添加一個異步執(zhí)行的任務(wù)作為結(jié)束任務(wù):dispatch_group_notify
6.快速遍歷
- dispatch_apply(size_t iterations, dispatch_queue_t _Nonnull queue, ^(size_t)block)
- dispatch_apply 的作用是在一個隊列上“運行”多次block荧嵌,其實就是簡化了用循環(huán)去向隊列依次添加block任務(wù)
- dispatch_apply 會開啟線程 快速執(zhí)行完循環(huán)
- dispatch_apply (阻塞當(dāng)前線程呛踊,直到循環(huán)執(zhí)行完成)
/**
快速遍歷
*/
- (void)func_dispatch_apply{
dispatch_queue_t custom_concurrent_queue = dispatch_queue_create("com.boundless.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(50/** 循環(huán)次數(shù) */, custom_concurrent_queue, ^(size_t i) {
NSLog(@"%zu",i);
});
}
7.柵欄砾淌、用于 解決并發(fā)隊列中 讀寫同一個資源搶奪的情況
dispatch_barrier_async(dispatch_queue_t _Nonnull queue, ^(void)block)
dispatch_barrier_sync(dispatch_queue_t _Nonnull queue, ^(void)block)
- dispatch_barrier_async(異步,不會阻塞當(dāng)前線程) 會設(shè)置柵欄 柵欄之后的任務(wù)會等到柵欄之前的任務(wù)執(zhí)行完成再執(zhí)行
- dispatch_barrier_async 必須配合 DISPATCH_QUEUE_CONCURRENT( 自定義的并發(fā)隊列),其他隊列 dispatch_barrier_async的作用和 dispatch_async 一樣
- dispatch_barrier_sync(同步谭网,阻塞當(dāng)前線程 ) 其他和 dispatch_barrier_async 一樣
/**
柵欄
用于 解決并發(fā)隊列中 讀寫同一個資源搶奪的情況
*/
- (void)func_dispatch_barrier_async{
dispatch_queue_t custom_concurrent_queue = dispatch_queue_create("com.boundless.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(custom_concurrent_queue, ^{
NSLog(@"寫入數(shù)據(jù) 1");
});
dispatch_async(custom_concurrent_queue,^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"寫入數(shù)據(jù) 2");
});
dispatch_barrier_async(custom_concurrent_queue, ^{
NSLog(@"等待數(shù)據(jù)寫入完畢");
});
dispatch_async(custom_concurrent_queue, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"讀取數(shù)據(jù) 1");
});
dispatch_async(custom_concurrent_queue, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"讀取數(shù)據(jù) 2");
});
}
8.隊列掛起/隊列重啟
- dispatch_suspend(dispatch_object_t _Nonnull object)
- dispatch_resume(dispatch_object_t _Nonnull object)
- dispatch_suspend 不能阻止正在執(zhí)行的任務(wù) 只會阻止還未執(zhí)行的任務(wù)
/**
隊列掛起/隊列重啟
*/
- (void)func_dispatch_suspendAndResume{
dispatch_queue_t custom_concurrent_queue = dispatch_queue_create("com.boundless.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(custom_concurrent_queue, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"執(zhí)行任務(wù) 1");
});
dispatch_async(custom_concurrent_queue,^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"執(zhí)行任務(wù) 2");
});
dispatch_suspend(custom_concurrent_queue);
[NSThread sleepForTimeInterval:1.0];
dispatch_resume(custom_concurrent_queue);
}
9.信號量 Dispatch Semaphore保證同步的方法
使用dispatch_semaphore_signal加1 dispatch_semaphore_wait減1汪厨,為0時等待的設(shè)置方式來達到線程同步的目的和同步鎖一樣能夠解決資源搶占的問題。
- dispatch_semaphore_create(long value)
- dispatch_semaphore_signal(dispatch_semaphore_t _Nonnull dsema)
- dispatch_semaphore_wait(dispatch_semaphore_t _Nonnull dsema, dispatch_time_t timeout)
dispatch_semaphore_t signal = dispatch_semaphore_create(1); //傳入值必須 >=0, 若傳入為0則阻塞線程并等待timeout,時間到后會執(zhí)行其后的語句
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"線程1 等待ing");
dispatch_semaphore_wait(signal, overTime); //signal 值 -1
NSLog(@"線程1");
dispatch_semaphore_signal(signal); //signal 值 +1
NSLog(@"線程1 發(fā)送信號");
NSLog(@"--------------------------------------------------------");
});
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"線程2 等待ing");
dispatch_semaphore_wait(signal, overTime);
NSLog(@"線程2");
dispatch_semaphore_signal(signal);
NSLog(@"線程2 發(fā)送信號");
});
關(guān)于信號量愉择,我們可以用停車來比喻:
停車場剩余4個車位劫乱,那么即使同時來了四輛車也能停的下。如果此時來了五輛車锥涕,那么就有一輛需要等待衷戈。
信號量的值(signal)就相當(dāng)于剩余車位的數(shù)目,dispatch_semaphore_wait 函數(shù)就相當(dāng)于來了一輛車层坠,dispatch_semaphore_signal 就相當(dāng)于走了一輛車殖妇。停車位的剩余數(shù)目在初始化的時候就已經(jīng)指明了(dispatch_semaphore_create(long value)),調(diào)用一次 dispatch_semaphore_signal窿春,剩余的車位就增加一個拉一;調(diào)用一次dispatch_semaphore_wait 剩余車位就減少一個;當(dāng)剩余車位為 0 時旧乞,再來車(即調(diào)用 dispatch_semaphore_wait)就只能等待蔚润。有可能同時有幾輛車等待一個停車位。有些車主沒有耐心尺栖,給自己設(shè)定了一段等待時間嫡纠,這段時間內(nèi)等不到停車位就走了,如果等到了就開進去停車延赌。而有些車主就像把車停在這除盏,所以就一直等下去。
10.dispatch_queue_set_specific挫以、dispatch_get_specific
FMDB利用它來防止死鎖
作用類似objc_setAssociatedObject跟objc_getAssociatedObject
static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey;
//創(chuàng)建串行隊列者蠕,所有數(shù)據(jù)庫的操作都在這個隊列里
_queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
//標(biāo)記隊列
dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);
//檢查是否是同一個隊列來避免死鎖的方法
- (void)inDatabase:(void (^)(FMDatabase *db))block {
FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");
}