GCD底层看到的另一种解释
这里对GCD的底层看到一段和Python中协程相关的理解,记录一下
- 1.GCD的层次比线程高,其底层是利用多线程来实现的,苹果将线程交给了系统去管理,这样任务的管理和执行比起线程来更加高效。
- 2.使用者要做的是定义自己Task任务,然后将其放到合适的分发队列去执行。
- 3.可以将GCD看作是一种更加轻量级的线程,类似于Python中的协程的概念。GCD的本质是在操作系统层面提供提供并行调度的,这样任务的切换效率要远高于内核的切换效率,它的大部分功能实在运行库里实现的。
GCD(Grand Central Dispatch)是苹果为实现并发编程提供的新技术。从基本功能上讲,GCD有点像NSOperationQueue,他们都允许程序将任务切分为多个单一任务然后提交至工作队列来并发地或者串行地执行。但是GCD比之NSOpertionQueue更底层更高效。
GCD的API很大程度上基于block,当然,GCD也可以脱离block来使用,比如使用传统c机制提供函数指针和上下文指针。实践证明,当配合block使用时,GCD非常简单易用且能发挥其最大能力,这也是我们经常在程序中使用的方式。
GCD提供很多超越传统多线程编程的优势:
a.易用: GCD比之thread更加简单易用。由于GCD基于工作单元(work unit)而非像thread那样基于运算,所以GCD可以控制诸如等待任务结束、监视文件描述符、周期执行代码以及工作挂起等任务。作为程序员只要一心关注自己的业务逻辑,可以从繁杂的线程管理的工作中解放出来。基于block的血统导致它能极为简单,可以在不同代码作用域之间传递上下文(闭包特性)。
b.高效率: GCD被实现得如此轻量和优雅,使得它在很多地方比之专门创建消耗资源的线程更实用且快速。这关系到易用性:导致GCD易用的原因有一部分在于你可以不用担心太多的效率问题而仅仅使用它就行了。
c.高性能: GCD自动根据系统负载(比例内存使用情况、CPU个数)来增减线程数量,它甚至可以在一个线程中实现多任务的并发,这就减少了上下文切换带来的开销,增加了计算效率。
GDC的介绍有另外一篇博客专门写了下,需要看的可以点击以下传送门GCD线程死锁解锁案例分析,这里主要记录下基本的API使用以及两种单例的创建方式
1.GCD同步异步并发串行排列组合的集中基本形式
/*
* 异步串行 ---> 不阻塞当前线程,会开多一条线程,在这个线程中是串行执行的
*/
- (void)asynchSerial
{
// 全局并发队列
// dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 自定义并发队列
dispatch_queue_t queue1 = dispatch_queue_create("COM.MKJ.COM", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue1, ^{
NSLog(@"------1,%@",[NSThread currentThread]);
});
dispatch_async(queue1, ^{
NSLog(@"------2,%@",[NSThread currentThread]);
});
dispatch_async(queue1, ^{
NSLog(@"------3,%@",[NSThread currentThread]);
});
NSLog(@"asynchSerial------end");
// 2016-12-21 09:34:16.222 GCDBasicAPI[1137:38266] asynchSerial------end
// 2016-12-21 09:34:16.222 GCDBasicAPI[1137:38300] ------1,<NSThread: 0x60000007a640>{number = 3, name = (null)}
// 2016-12-21 09:34:16.223 GCDBasicAPI[1137:38300] ------2,<NSThread: 0x60000007a640>{number = 3, name = (null)}
// 2016-12-21 09:34:16.223 GCDBasicAPI[1137:38300] ------3,<NSThread: 0x60000007a640>{number = 3, name = (null)}
}
/*
* 同步串行 ---> 阻塞当前线程,不开多条线程,在当前线程执行,顺序执行
*/
- (void)synchSerial
{
// 全局并发队列
// dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 自定义并发队列
dispatch_queue_t queue1 = dispatch_queue_create("COM.MKJ.COM", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue1, ^{
NSLog(@"------1,%@",[NSThread currentThread]);
});
dispatch_sync(queue1, ^{
NSLog(@"------2,%@",[NSThread currentThread]);
});
dispatch_sync(queue1, ^{
NSLog(@"------3,%@",[NSThread currentThread]);
});
NSLog(@"synchSerial------end");
// 2016-12-21 09:32:25.878 GCDBasicAPI[1117:36348] ------1,<NSThread: 0x600000075700>{number = 1, name = main}
// 2016-12-21 09:32:25.878 GCDBasicAPI[1117:36348] ------2,<NSThread: 0x600000075700>{number = 1, name = main}
// 2016-12-21 09:32:25.879 GCDBasicAPI[1117:36348] ------3,<NSThread: 0x600000075700>{number = 1, name = main}
// 2016-12-21 09:32:25.879 GCDBasicAPI[1117:36348] synchSerial------end
}
/*
* 同步并发 ---> 阻塞当前线程,不开多条线程,在当前线程执行,顺序执行
*/
- (void)synchConcurrent
{
// 全局并发队列
// dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 自定义并发队列
dispatch_queue_t queue1 = dispatch_queue_create("COM.MKJ.COM", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue1, ^{
NSLog(@"------1,%@",[NSThread currentThread]);
});
dispatch_sync(queue1, ^{
NSLog(@"------2,%@",[NSThread currentThread]);
});
dispatch_sync(queue1, ^{
NSLog(@"------3,%@",[NSThread currentThread]);
});
NSLog(@"synchConcurrent------end");
// 2016-12-21 09:29:47.575 GCDBasicAPI[1071:33607] ------1,<NSThread: 0x60000006b900>{number = 1, name = main}
// 2016-12-21 09:29:47.575 GCDBasicAPI[1071:33607] ------2,<NSThread: 0x60000006b900>{number = 1, name = main}
// 2016-12-21 09:29:47.576 GCDBasicAPI[1071:33607] ------3,<NSThread: 0x60000006b900>{number = 1, name = main}
// 2016-12-21 09:29:47.576 GCDBasicAPI[1071:33607] synchConcurrent------end
}
/*
* 异步并发 ---> 不阻塞当前线程,可以开多条线程,并发执行,顺序不一定
*/
- (void)asynchConcurrent
{
// 全局并发队列
// dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 自定义并发队列
dispatch_queue_t queue1 = dispatch_queue_create("COM.MKJ.COM", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue1, ^{
NSLog(@"------1,%@",[NSThread currentThread]);
});
dispatch_async(queue1, ^{
NSLog(@"------2,%@",[NSThread currentThread]);
});
dispatch_async(queue1, ^{
NSLog(@"------3,%@",[NSThread currentThread]);
});
NSLog(@"asynchConcurrent------end");
// 2016-12-21 09:28:23.895 GCDBasicAPI[1050:32223] asynchConcurrent------end
// 2016-12-21 09:28:23.895 GCDBasicAPI[1050:32292] ------1,<NSThread: 0x60800026e180>{number = 3, name = (null)}
// 2016-12-21 09:28:23.895 GCDBasicAPI[1050:32293] ------3,<NSThread: 0x60800026fbc0>{number = 5, name = (null)}
// 2016-12-21 09:28:23.895 GCDBasicAPI[1050:32295] ------2,<NSThread: 0x60000026e0c0>{number = 4, name = (null)}
}
简单概括如下:
项目 | 同步(sync) | 异步(async) |
---|---|---|
串行 | 当前线程,顺序执行 | 另一个线程,顺序执行 |
并发 | 当前线程,顺序执行 | 另一个线程,同时执行 |
可以看出同步和异步就是开线程的能力,同步执行必然一个个顺序执行在当前线程,而异步执行可以根据队列不同来确定顺序还是同步并发执行
2.最基本的线程间通讯方式
// 不阻塞当前线程,后台拉取图片资源
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSURL *url = [NSURL URLWithString:@"https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcRCfHVwGXGvrpCBplQieSKsLgfBULL8ZZXSzosPFdoZsvjDlqnOrKK_w58"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
NSLog(@"获取资源%@",[NSThread currentThread]);
// 下载完之后回到主线程
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
NSLog(@"加载资源%@",[NSThread currentThread]);
});
});
// 2016-12-21 09:43:51.675 GCDBasicAPI[1237:45972] 获取资源<NSThread: 0x6000002742c0>{number = 3, name = (null)}
// 2016-12-21 09:43:51.676 GCDBasicAPI[1237:45929] 加载资源<NSThread: 0x60000007b680>{number = 1, name = main}
3.其他Barrier,apply,after,Group等常用API
- dispatch_barrier_async
/*
* barrier函数可以阻塞任务,执行到他这里,要等之前的任务执行完才能执行之后的任务
*/
- (void)barrier
{
dispatch_queue_t queue = dispatch_queue_create("com.mkj.hehe", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"--------1,%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 10; i ++)
{
NSLog(@"--------2%ld,%@",i,[NSThread currentThread]);
}
});
dispatch_barrier_async(queue, ^{
NSLog(@"--------3,%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"--------4,%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"--------5,%@",[NSThread currentThread]);
});
// 2016-12-21 09:58:09.231 GCDBasicAPI[1392:57437] --------1,<NSThread: 0x60000007f400>{number = 5, name = (null)}
// 2016-12-21 09:58:09.231 GCDBasicAPI[1392:57807] --------20,<NSThread: 0x60000007e780>{number = 7, name = (null)}
// 2016-12-21 09:58:09.232 GCDBasicAPI[1392:57807] --------21,<NSThread: 0x60000007e780>{number = 7, name = (null)}
// 2016-12-21 09:58:09.233 GCDBasicAPI[1392:57807] --------22,<NSThread: 0x60000007e780>{number = 7, name = (null)}
// 2016-12-21 09:58:09.233 GCDBasicAPI[1392:57807] --------23,<NSThread: 0x60000007e780>{number = 7, name = (null)}
// 2016-12-21 09:58:09.233 GCDBasicAPI[1392:57807] --------24,<NSThread: 0x60000007e780>{number = 7, name = (null)}
// 2016-12-21 09:58:09.233 GCDBasicAPI[1392:57807] --------25,<NSThread: 0x60000007e780>{number = 7, name = (null)}
// 2016-12-21 09:58:09.233 GCDBasicAPI[1392:57807] --------26,<NSThread: 0x60000007e780>{number = 7, name = (null)}
// 2016-12-21 09:58:09.234 GCDBasicAPI[1392:57807] --------27,<NSThread: 0x60000007e780>{number = 7, name = (null)}
// 2016-12-21 09:58:09.234 GCDBasicAPI[1392:57807] --------28,<NSThread: 0x60000007e780>{number = 7, name = (null)}
// 2016-12-21 09:58:09.234 GCDBasicAPI[1392:57807] --------29,<NSThread: 0x60000007e780>{number = 7, name = (null)}
// 2016-12-21 09:58:09.234 GCDBasicAPI[1392:57807] --------3,<NSThread: 0x60000007e780>{number = 7, name = (null)}
// 2016-12-21 09:58:09.235 GCDBasicAPI[1392:57807] --------4,<NSThread: 0x60000007e780>{number = 7, name = (null)}
// 2016-12-21 09:58:09.235 GCDBasicAPI[1392:57437] --------5,<NSThread: 0x60000007f400>{number = 5, name = (null)}
}
- dispatch_apply
/*
* apply 无序快速遍历,可以用于文件移动等需求
*/
- (void)apply
{
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSLog(@"%ld,%@",index,[NSThread currentThread]);
});
// 2016-12-21 10:04:01.487 GCDBasicAPI[1485:63041] 0,<NSThread: 0x600000069940>{number = 1, name = main}
// 2016-12-21 10:04:01.487 GCDBasicAPI[1485:63172] 2,<NSThread: 0x60800007a940>{number = 7, name = (null)}
// 2016-12-21 10:04:01.487 GCDBasicAPI[1485:63845] 1,<NSThread: 0x60800007a8c0>{number = 6, name = (null)}
// 2016-12-21 10:04:01.487 GCDBasicAPI[1485:63873] 3,<NSThread: 0x60800007ab00>{number = 8, name = (null)}
// 2016-12-21 10:04:01.487 GCDBasicAPI[1485:63041] 4,<NSThread: 0x600000069940>{number = 1, name = main}
// 2016-12-21 10:04:01.488 GCDBasicAPI[1485:63172] 5,<NSThread: 0x60800007a940>{number = 7, name = (null)}
// 2016-12-21 10:04:01.488 GCDBasicAPI[1485:63845] 6,<NSThread: 0x60800007a8c0>{number = 6, name = (null)}
// 2016-12-21 10:04:01.488 GCDBasicAPI[1485:63873] 7,<NSThread: 0x60800007ab00>{number = 8, name = (null)}
// 2016-12-21 10:04:01.488 GCDBasicAPI[1485:63041] 8,<NSThread: 0x600000069940>{number = 1, name = main}
// 2016-12-21 10:04:01.488 GCDBasicAPI[1485:63172] 9,<NSThread: 0x60800007a940>{number = 7, name = (null)}
}
- dispatch_after
/*
* 延时执行
*/
- (void)delay
{
// 方法1
NSLog(@"开始了");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"我才开始");
});
// 方法2
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];
}
- (void)run
{
NSLog(@"run");
}
- dispatch_group_async
/*
* 线程组 实现多个请求完成之后同时刷新UI的问题
* 把多个任务加入到队列里面,最后都完成之后会调用Notify的方法进行通知,最终刷新UI
*/
- (void)group
{
// 并发队列
dispatch_queue_t queue = dispatch_queue_create("com.mkj.hh", DISPATCH_QUEUE_CONCURRENT);
// 线程组
dispatch_group_t group = dispatch_group_create();
// 任务1加入组
dispatch_group_async(group, queue, ^{
NSURL *url = [NSURL URLWithString:@"https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcQxex7CvJ0pArQ8NHwXMaZ8fSt3ALAZBlljTQVlDsh6AIegeMjWWMoSVtej"];
NSData *data = [NSData dataWithContentsOfURL:url];
self.image1 = [UIImage imageWithData:data];
});
// 任务2加入组
dispatch_group_async(group, queue, ^{
// 这里可以一样是耗时的网络请求,暂时处理成本地的
self.image2 = [UIImage imageNamed:@"Play"];
});
// 任务完成之后统一通知
dispatch_group_notify(group, queue, ^{
// 这里的queue如果是mainQueue的话就可以直接回到主线程操作需要的UI
// 现在还是在并发队列里面 进行图片合成 还是放在子线程
UIGraphicsBeginImageContext(CGSizeMake([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.width));
[self.image1 drawInRect:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.width)];
[self.image2 drawInRect:CGRectMake([UIScreen mainScreen].bounds.size.width/2, [UIScreen mainScreen].bounds.size.width/2, 30, 40)];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// 回到主线程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
}
4.两种方式实现单例
- 自己加锁实现
static id obj;
+ (instancetype)shareInstance
{
@synchronized (self) {
if (!obj) {
obj = [[self alloc] init];
}
}
return obj;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
@synchronized (self) {
if (!obj) {
obj = [super allocWithZone:zone];
}
}
return obj;
}
- (id)copyWithZone:(NSZone *)zone
{
return obj;
}
- GCD实现(直接抽成宏)
#define MKJSingletonH + (instancetype)shareManager;
#define MKJSinletonM \
static id obj; \
\
+ (instancetype)shareManager \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
obj = [[self alloc] init]; \
}); \
return obj; \
} \
\
+ (instancetype)allocWithZone:(struct _NSZone *)zone \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
obj = [super allocWithZone:zone]; \
}); \
return obj; \
} \
\
- (id)copyWithZone:(NSZone *)zone \
{ \
return obj; \
}
抽成宏之后可以直接进行调用就OK
@interface ManagerHelper : NSObject
MKJSingletonH
@end
@implementation ManagerHelper
MKJSinletonM
@end
注:这里为什么需要用Static进行修饰,static保证只有本文件才能访问,这样就能形成单例,但是如果不用static修饰,外部也一样能访问,这个时候外部如果置为nil之后,这个对象就不存在了,再也不会创建了,dispatch_once只会执行一次,这样肯定不对的
非常简单的介绍,这里有个Demo,需要的朋友可以下载看看基础用法先,文章头部有介绍GCD死锁解锁的案例文章,需要的也可以看看
本文Demo传送门
NSOperation用法和GCD之间的关系