讲GCD之前,必须得说说线程
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。 以下是 iOS 中 4 套多线程方案
- Pthreads : POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作系统的线程。
- NSThread : 是三种方法里面相对轻量级的,但需要管理线程的生命周期、同步、加锁问题,这会导致一定的性能开销
- Grand Central Dispatch(简称GCD,iOS4才开始支持):提供了一些新特性、运行库来支持多核并行编程,它的关注点更高:如何在多个cpu上提升效率
- NSOperation & NSOperationQueue : 苹果公司对 GCD 的封装,完全面向对象,NSOperation以面向对象的方式封装了需要执行的操作,不必关心线程管理、同步等问题。NSOperation是一个抽象基类,iOS提供了两种默认实现:NSInvocationOperation和NSBlockOperation,当然也可以自定义NSOperation
Pthreads的函数我们几乎不会用到
NSThread倒是有一些API,使用很方便来看一下
//取消线程
- (void)cancel;
//启动线程
- (void)start;
//判断某个线程的状态的属性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;
//设置和获取线程名字
-(void)setName:(NSString *)n;
-(NSString *)name;
//获取当前线程信息
+ (NSThread *)currentThread;
//获取主线程信息
+ (NSThread *)mainThread;
//使当前线程暂停一段时间,或者暂停到某个时刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;
主角出场
Grand Central Dispatch
GCD开源地址。
GCD的好处在于它不需要管理线程的生命周期,线程的创建与分配,它是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核。
来看看怎么使用GCD
描述 | 说明 |
---|---|
queue | 队列 |
main | 主队列 |
global | 全局队列 |
dispatch_queue_t | 描述队列 |
dispatch_block_t | 描述任务 |
dispatch_once_t | 描述一次性 |
dispatch_time_t | 描述时间 |
dispatch_group_t | 描述队列组 |
dispatch_semaphore_t | 描述信号量 |
函数 | 说明 |
---|---|
dispatch_sync() | 同步执行 |
dispatch_async() | 异步执行 |
dispatch_after() | 延时执行 |
dispatch_once() | 一次性执行 |
dispatch_apply() | 快速遍历 |
dispatch_queue_create() | 创建队列 |
dispatch_group_create() | 创建队列组 |
dispatch_group_async() | 提交任务到队列组 |
dispatch_group_enter() | |
dispatch_group_leave() | 进入、离开组队列 |
dispatch_group_notify() | 监听队列组执行完 |
dispatch_group_wait() | 设置等待时间 |
- 同步异步线程创建
//同步线程
dispatch_sync(队列, ^(block)) 阻塞线程
//异步线程
dispatch_async(队列, ^(block)) 不阻塞线程
- 系统标准两个队列
//全局队列,一个并行的队列
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默认为串行
//并行队列
dispatch_queue_create("com.boundless.concurrent", DISPATCH_QUEUE_CONCURRENT)
//队列优先级
* DISPATCH_QUEUE_PRIORITY_HIGH 优先级高
* DISPATCH_QUEUE_PRIORITY_DEFAULT 优先级默认
* DISPATCH_QUEUE_PRIORITY_LOW 优先级低
* DISPATCH_QUEUE_PRIORITY_BACKGROUND 优先级最低 表示用户不会察觉的任务,使用它来处理预加载,或者不需要用户交互和对时间不敏感的任务
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 可直接设置优先级
自定义队列优先级设置方法
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);
何时使用何种队列类型
- 主队列(顺序):队列中有任务完成需要更新UI时,dispatch_after在这种类型中使用。
- 并发队列:用来执行与UI无关的后台任务,dispatch_sync放在这里,方便等待任务完成进行后续处理或和dispatch barrier同步。dispatch groups放在这里也不错。
- 自定义顺序队列:顺序执行后台任务并追踪它时。这样做同时只有一个任务在执行可以防止资源竞争。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);
/** 自定义并发队列 */
dispatch_queue_t custom_concrrent_queue = dispatch_queue_create("com.boundless.concurrent", DISPATCH_QUEUE_CONCURRENT);
/** 自定义队列设置优先级 */
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);
/** 任务(^{})在全局队列(global_queue)中同步执行(dispatch_sync) */
dispatch_sync(global_queue, ^{
NSLog(@"执行任务");
});
/** 任务(^{})在全主队列(main_queue)中同步执行(dispatch_sync) */
dispatch_sync(main_queue, ^{
NSLog(@"执行任务");
});
/** 任务(^{})在全局队列(global_queue)中异步执行(dispatch_async) */
dispatch_async(global_queue, ^{
NSLog(@"执行任务");
});
/** 任务(^{})在全主队列(main_queue)中异步执行(dispatch_async) */
dispatch_async(main_queue, ^{
NSLog(@"执行任务");
});
开发中常用的一些GCD函数
1.耗时操作完成回到主线程
dispatch_async(global_queue, ^{
NSLog(@"执行任务");
dispatch_async(main_queue, ^{
NSLog(@"回到主线程刷新UI");
});
});
2.延迟执行
- #define NSEC_PER_SEC 1000000000ull //每秒有多少纳秒
- #define USEC_PER_SEC 1000000ull //每秒有多少毫秒
- #define NSEC_PER_USEC 1000ull //每毫秒有多少纳秒
/**
延迟执行
_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));
// 主队列中延迟执行
dispatch_after(time_source, dispatch_get_main_queue(), ^{
});
}
3.执行一次(单例)
/**
执行一次
_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() //创建组
- dispatch_group_async(dispatch_group_t _Nonnull group, dispatch_queue_t _Nonnull queue, ^(void)block) //添加任务到相应队列组
- 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) //阻塞线程到队列任务执行完
/**
队列组
可以使用 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(@"任务 1");
});
dispatch_group_async(group, global_queue, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"任务 2");
});
dispatch_group_async(group, global_queue, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任务 3");
});
/** 判断 队列组全部执行完成 的两个方法*/
// dispatch_group_wait(group, DISPATCH_TIME_FOREVER); 阻塞当前线程 直到队列组任务执行完
// 队列组任务执行完 接到通知 ,不会阻塞线程
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有执行完成");
});
}
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;
// 创建 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(@"拿到数据");
// 离开group
dispatch_group_leave(group);
}];
// resume
[task resume];
}
自己创建队列:使用dispatch_group_async。
无法直接使用队列变量(如使用AFNetworking添加异步任务):使用dispatch_group_enter,dispatch_group_leave。
使用dispatch_group_enter,dispatch_group_leave就可以方便的将一系列网络请求“打包”起来~
添加结束任务
添加结束任务也可以分为两种情况,如下:
在当前线程阻塞的同步等待:dispatch_group_wait。
添加一个异步执行的任务作为结束任务:dispatch_group_notify
6.快速遍历
- dispatch_apply(size_t iterations, dispatch_queue_t _Nonnull queue, ^(size_t)block)
- dispatch_apply 的作用是在一个队列上“运行”多次block,其实就是简化了用循环去向队列依次添加block任务
- dispatch_apply 会开启线程 快速执行完循环
- dispatch_apply (阻塞当前线程,直到循环执行完成)
/**
快速遍历
*/
- (void)func_dispatch_apply{
dispatch_queue_t custom_concurrent_queue = dispatch_queue_create("com.boundless.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(50/** 循环次数 */, custom_concurrent_queue, ^(size_t i) {
NSLog(@"%zu",i);
});
}
7.栅栏、用于 解决并发队列中 读写同一个资源抢夺的情况
dispatch_barrier_async(dispatch_queue_t _Nonnull queue, ^(void)block)
dispatch_barrier_sync(dispatch_queue_t _Nonnull queue, ^(void)block)
- dispatch_barrier_async(异步,不会阻塞当前线程) 会设置栅栏 栅栏之后的任务会等到栅栏之前的任务执行完成再执行
- dispatch_barrier_async 必须配合 DISPATCH_QUEUE_CONCURRENT( 自定义的并发队列),其他队列 dispatch_barrier_async的作用和 dispatch_async 一样
- dispatch_barrier_sync(同步,阻塞当前线程 ) 其他和 dispatch_barrier_async 一样
/**
栅栏
用于 解决并发队列中 读写同一个资源抢夺的情况
*/
- (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(@"写入数据 1");
});
dispatch_async(custom_concurrent_queue,^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"写入数据 2");
});
dispatch_barrier_async(custom_concurrent_queue, ^{
NSLog(@"等待数据写入完毕");
});
dispatch_async(custom_concurrent_queue, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"读取数据 1");
});
dispatch_async(custom_concurrent_queue, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"读取数据 2");
});
}
8.队列挂起/队列重启
- dispatch_suspend(dispatch_object_t _Nonnull object)
- dispatch_resume(dispatch_object_t _Nonnull object)
- dispatch_suspend 不能阻止正在执行的任务 只会阻止还未执行的任务
/**
队列挂起/队列重启
*/
- (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(@"执行任务 1");
});
dispatch_async(custom_concurrent_queue,^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"执行任务 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时等待的设置方式来达到线程同步的目的和同步锁一样能够解决资源抢占的问题。
- 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,时间到后会执行其后的语句
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 发送信号");
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 发送信号");
});
关于信号量,我们可以用停车来比喻:
停车场剩余4个车位,那么即使同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆需要等待。
信号量的值(signal)就相当于剩余车位的数目,dispatch_semaphore_wait 函数就相当于来了一辆车,dispatch_semaphore_signal 就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了(dispatch_semaphore_create(long value)),调用一次 dispatch_semaphore_signal,剩余的车位就增加一个;调用一次dispatch_semaphore_wait 剩余车位就减少一个;当剩余车位为 0 时,再来车(即调用 dispatch_semaphore_wait)就只能等待。有可能同时有几辆车等待一个停车位。有些车主没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,所以就一直等下去。
10.dispatch_queue_set_specific、dispatch_get_specific
FMDB利用它来防止死锁
作用类似objc_setAssociatedObject跟objc_getAssociatedObject
static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey;
//创建串行队列,所有数据库的操作都在这个队列里
_queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
//标记队列
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");
}