多线程,主GCD
串行队列(Serial Dispatch Queue,等待现有处理结束)
并行队列�(Concurrent Dispatch Queue,不用等待现有处理结束)
先来看些iOS多线程的技术
pthread
纯C,需要手动创建线程、销毁线程,手动进行线程管理,代码恶心
NSThread
Foundation框架下的OC对象,依旧需要手动管理线程。线程同步对数据的加锁会有一定的开销
GCD
纯C,不需要手动线程管理,并且会更好里利用更多的内核
NSOperationQueue
Found框架的GCD,是对GCD进行的封装
GCD的API介绍
手动创建队列
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
label:队列的标识符,日后可用来调试程序
attr:队列类型
DISPATCH_QUEUE_CONCURRENT : 并发队列
DISPATCH_QUEUE_SERIAL 或 NULL : 串行队列
2个常用队列
主队列
dispatch_get_main_queue();在主线程中执行的操作追加到此队列
全局并发队列
dispatch_get_global_queue(long identifier, unsigned long flags);
该方法返回的是全局并发队列
identifier : 优先级
DISPATCH_QUEUE_PRIORITY_HIGH : 高优先级
DISPATCH_QUEUE_PRIORITY_DEFAULT : 默认优先级
DISPATCH_QUEUE_PRIORITY_LOW : 低优先级
DISPATCH_QUEUE_PRIORITY_BACKGROUND : 后台优先级
flags : 暂时用不上, 传 0 即可
同步函数
dispatch_sync(dispatch_queue_t queue, ^(void)block);
在参数queue队列下同步执行block
异步函数
dispatch_async(dispatch_queue_t queue, ^(void)block);
在参数queue队列下异步执行block(开启新线程)
时间
dispatch_time(dispatch_time_t when, int64_t delta);
根据传入的时间(when)和延迟(delta)计算出一个未来的时间
延迟执行
dispatch_after(dispatch_time_t when, dispatch_queue_t queue, ^(void)block);
有了上述获取时间的函数, 则可以直接把时间传入, 然后定义该延迟执行的block在哪一个queue队列中执行.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// code to be executed after a specified delay
});
注意 : 延迟执行不是在指定时间后执行任务处理, 而是在指定时间后将处理追加到队列中, 这个是要分清楚的
队列组
dispatch_group_create();
有时候我们想要在队列中的多个任务都处理完毕之后做一些事情, 就能用到这个Group. 同队列一样, Group在使用完毕也是需要dispatch_release掉的(MRC). 上代码

组异步函数
dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, ^(void)block);
分发Group内的并发异步函数
组通知
dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, ^(void)block)
监听group的任务进度, 当group内的任务全部完成, 则在queue队列中执行block.
栅栏
dispatch_barrier_async(dispatch_queue_t queue, ^(void)block)
在访问数据库或文件时, 为了提高效率, 读取操作放在并行队列中执行. 但是写入操作必须在串行队列中执行(避免资源抢夺问题). 为了避免麻烦, 此时dispatch_barrier_async函数作用就出来了, 在这函数里进行写入操作, 写入操作会等到所有读取操作完毕后, 形成一道栅栏, 然后进行写入操作, 写入完毕后再把栅栏移除, 同时开放读取操作. 如图

快速迭代
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index){
// code here
});
执行10次代码, index顺序不确定. dispatch_apply会等待全部处理执行结束才会返回. 意味着dispatch_apply会阻塞当前线程. 所以dispatch_apply一般用于异步函数的block中
一次性代码
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行1次的代码(这里面默认是线程安全的)
});
挂起和恢复
dispatch_suspend(queue)
挂起指定的queue队列, 对已经执行的没有影响, 追加到队列中尚未执行的停止执行.
dispatch_resume(queue)
恢复指定的queue队列, 使尚未执行的处理继续执行.
GCD的注意点
因为在ARC下, 不需要我们释放自己创建的队列, 所以GCD的注意点就剩下死锁+
死锁
NSLog(@"111");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"222");
});
NSLog(@"333");
输出结果是
111
为什么? 看下图

毫无疑问会先输出111, 然后在当前队列下调用dispatch_sync函数, dispatch_sync函数会把block追加到当前队列上, 然后等待block调用完毕该函数才会返回, 不巧的是, block在队列的尾端, 而队列正在执行的是dispatch_sync函数. 现在的情况是, block不执行完毕, dispatch_sync函数就不能返回, dispatch_sync不返回, 就没机会执行block函数. 这种你等我, 我也等你的情况就是死锁, 后果就是大家都执行不了, 当前线程卡死在这里.
GCD的使用场景
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行耗时操作
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主线程作刷新UI等操作
});
});
单例
单例也就是在程序的整个生命周期中, 该类有且仅有一个实例对象, 此时为了保证只有一个实例对象, 我们这里用到了dispatch_once函数
static XXTool _instance;
+ (instancetype)allocWithZone:(struct _NSZone )zone {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [self allocWithZone:zone];
});
return _instance;
}
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
- (id)copy {
return _instance;
}
- (id)mutableCopy {
return _instance;
}
网友评论