本文着种介绍最后两种方案
GCD 是如何实现多线程的
- GCD 实现多线程
- GCD 简介
-
GCD 全称是
Grand Central Dispatch
,可译为“超级厉害的中枢调度器”,GCD 是苹果公司为多核的并行运算提出的解决方案, GCD会自动利用更多的 CPU 内核(比如双核、四核)来开启线程执行任务,GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程),不需要我们程序员手动管理内存。 -
任务和队列
- 任务:在同步函数和异步函数中执行
- 队列:用来存放任务(并发 串行)
GCD会自动将队列中的任务取出,放到对应的线程,任务的取出遵循FIFO
,即先入先出队列,First Input First Output
的缩写。
先进入的任务先完成并结束,再执行后面的任务。
同步函数和异步函数,并发队列和串行队列
用同步的方式执行任务:在当前线程中可立即执行任务,不具备开启线程的能力
用异步的方式执行任务:在当前线程结束时执行任务,具备开启新的线程的能力
-
并发队列:允许多个任务同时执行
-
串行队列:一个任务执行完毕后,再执行下一个任务
创建并发/串行队列代码:
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_main_queue();
// 创建串行队列 serial 串行 concurrent并发
queueSerial = dispatch_queue_create("searial.whenbar.com", DISPATCH_QUEUE_SERIAL);
//创建并行队列
// 参1:const char *label 队列名称
// 参2:dispatch_queue_attr_t attr 队列类型
queueConcurrent = dispatch_queue_create("concurrent.whenbar.com", DISPATCH_QUEUE_CONCURRENT);
}
//1 获得主队列
-(void)runqueueMain
{
// 获取主队列 在主队列中的任务都会在主线程中执行。
dispatch_queue_t queueMain = dispatch_get_main_queue();
}
//2. 创建串行队列
-(void)runqueueSerial
{
// GCD同步函数串行队列(立即执行,当前线程)
// 参1: dispatch_queue_t queue 队列
// 参2: 任务
dispatch_sync(queueSerial, ^{
for (NSInteger i = 0; i < 10; i++) {
NSLog(@"~~~%ld %@",i, [NSThread currentThread]);
}
});
// 异步函数串行队列 (另开线程,多个任务按顺序执行)
dispatch_async(queueSerial, ^{
dispatch_async(queueSerial, ^{
for (NSInteger i = 0; i < 10; i++) {
NSLog(@"~~~%ld %@",i, [NSThread currentThread]);
}
});
dispatch_async(queueSerial, ^{
for (NSInteger i = 0; i < 10; i++) {
NSLog(@"~~~%ld %@",i, [NSThread currentThread]);
}
});
dispatch_async(queueSerial, ^{
for (NSInteger i = 0; i < 10; i++) {
NSLog(@"~~~%ld %@",i, [NSThread currentThread]);
}
});
});
}
//3. 创建并发队列
-(void)runqueueConcurrent
{
// 同步函数并行队列(立即执行,当前线程)
dispatch_sync(queueConcurrent, ^{
for (NSInteger i = 0; i < 10; i++) {
NSLog(@"~~~%ld %@",i, [NSThread currentThread]);
}
});
// 异步函数并行队列 (另开线程,多个任务一起执行)
dispatch_async(queueConcurrent, ^{
dispatch_async(queueConcurrent, ^{
for (NSInteger i = 0; i < 5; i++) {
NSLog(@"~~~%ld %@",i, [NSThread currentThread]);
}
});
dispatch_async(queueConcurrent, ^{
for (NSInteger i = 0; i < 6; i++) {
NSLog(@"~~~%ld %@",i, [NSThread currentThread]);
}
});
dispatch_async(queueConcurrent, ^{
for (NSInteger i = 0; i < 7; i++) {
NSLog(@"~~~%ld %@",i, [NSThread currentThread]);
}
});
});
}
//4. 创建全局队列
-(void)runqueueGlobal
{
// 获取全局队列 全局队列是并发队列
// 参1:队列的优先级
// 参2:0(以后可能用到的参数)//#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高\
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)\
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低\
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台
dispatch_queue_t queueGlobal = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
}
// 主队列:(任何一个任务只要在主队列中,都会加入到主线程的队列中执行)
TIPS: 注意
使用sync函数(同步函数)往当前串行队列中添加任务,会卡住当前的串行队列
解释:使用同步函数添加任务 A 到串行队列,说明要在当前串行队列立即执行任务 A ,任务 A 执行完后,才会执行任务 A 后面的代码。但当前队列是串行队列,也就是说任务 A 必须等到当前串行队列中正在执行的任务 B 完成之后才能执行,因此又必须先执行任务 A 中立即执行任务,又要必须等到任务 B 执行完以后才能执行下一个任务,所以就会卡死。你等我,我等你,谁也无法执行。
####GCD实现线程通信
小项目:下载图片
代码如下:
// 获取图片的url
NSURL *url = [NSURL URLWithString:@"http://7xjanq.com1.z0.glb.clouddn.com/6478.jpg"];
// 开启线程下载图片
dispatch_queue_t queue = dispatch_queue_create("111", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
// 下载完成后返回主线程显示图片
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
————————————————————————————————————————
GCD其他常用函数
//----------------- 队列组 -----------------------------
//队列组可以将很多队列添加到一个组里,这样做的好处是,当这个组里所有的任务都执行完了,队列组会通过一个方法通知我们。下面是使用方法,这是一个很实用的功能。
-(void)rungroup
{
//1.创建队列组
dispatch_group_t group=dispatch_group_create();
//2.创建队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
//3.多次使用队列组的方法执行任务, 只有异步方法
//3.1.执行3次循环
dispatch_group_async(group,queue,^{
for (NSInteger i = 0; i< 3; i++){
NSLog(@"group-01 - %@", [NSThread currentThread]);
}
});
//3.2.主队列执行8次循环
dispatch_group_async(group, dispatch_get_main_queue(), ^{
for (NSInteger i=0;i<8;i++) {
NSLog(@"group-02 - %@", [NSThread currentThread]);
}
});
//3.3.执行5次循环
dispatch_group_async(group, queue, ^{
for(NSInteger i=0;i<5;i++) {
NSLog(@"group-03 - %@", [NSThread currentThread]);
}
});
//4.都完成后会自动通知
dispatch_group_notify(group,dispatch_get_main_queue(),^{
NSLog(@"完成 - %@", [NSThread currentThread]);
});
}
dispatch_barrier
栅栏
// 1.barrier : 在barrier前面的先执行,然后再执行barrier,然后再执行barrier后面的 barrier的queue不能是全局的并发队列
dispatch_queue_t queue = dispatch_queue_create("11", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i = 0; i < 100; i++) {
NSLog(@"%@--1", [NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 100; i++) {
NSLog(@"%@--2", [NSThread currentThread]);
}
});
dispatch_barrier_async(queue, ^{
for (int i = 0; i < 100; i++) {
NSLog(@"%@--3", [NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 100; i++) {
NSLog(@"%@--4", [NSThread currentThread]);
}
});
// dispatch_after 延迟执行
// 延迟执行
// 方法1
[self performSelector:@selector(run:) withObject:@"参数" afterDelay:2.0];
// 方法2
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
for (NSInteger i = 0; i < 100; i++) {
NSLog(@"%@", [NSThread currentThread]);
}
});
// 方法3
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run:) userInfo:nil repeats:NO];
dispatch_once 整个程序运行中执行一次
// 整个程序中只执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 一次性代码
});
####GCD定时器GCD定时器不受Mode影响因此比NSTimer要准确
讲完 GCD 就该讲讲 NSOperation,它是 GCD 的面向对象的封装,使用起来也更方便,
- NSOperation实现多线程
-
NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类
-
NSInvocationOperation
-
NSBlockOperation
自定义子类继承NSOperation,实现内部相应的方法
使用 NSOperation 实现多线程的步骤: -
创建任务 NSOperation 对象
- 创建 NSOperationQueue 队列
- 将任务 NSOperation 对象 add 到 NSOperationQueue 队列中去
- NSInvocationOperation
代码如下:
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
[op start];
注意:默认情况下,调用了start方法后并不会开一条新的线程去执行,而是在当前线程同步执行操作,只有将 NSOperation 放到一个 NSOperationQueue 中,才会异步执行操作
- NSBlockOperation
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ // 在主线程 NSLog(@"下载1------%@", [NSThread currentThread]); }]; // 添加额外的任务(在子线程执行),封装数大于1才会异步执行 [op addExecutionBlock:^{ NSLog(@"下载2------%@", [NSThread currentThread]); }];
自定义Operation:需要实现- (void)main方法,需要做的事情放在mian方法中
- NSOperationQueue
使用NSOperationQueue
创建队列:主队列和全局队列
// 创建一个其他队列(包括串行队列和并发队列) 放到这个队列中的NSOperation对象会自动放到子线程中执行
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 创建一个主队列,放到这个队列中的NSOperation对象会自动放到子线程中执行
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
// 表示并发数量:即同时执行任务的最大数。
queue.maxConcurrentOperationCount = 1;
队列的取消、暂停、恢复:
// NSOpertion的 - cancel 方法也可以停止单个操作
- (void)cancelAllOperations;
// YES代表暂停队列,NO代表恢复队列
- (void)setSuspended:(BOOL)b;
添加依赖
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download1 -------------- %@", [NSThread currentThread]);
}];
NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download2 -------------- %@", [NSThread currentThread]);
}];
NSBlockOperation *block3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download3 -------------- %@", [NSThread currentThread]);
}];
// 添加依赖: block1 和 block2执行完后 再执行 block3 block3依赖于block1和block2
// 给block3添加依赖 让block3在block1和block2之后执行
[block3 addDependency:block1];
[block3 addDependency:block2];
[queue addOperation:block1];
[queue addOperation:block2];
[queue addOperation:block3];
注意:不能循环依赖,但可以跨队列依赖,不管NSOperation对象在哪个队列。只要是两个NSOperation对象就可以依赖
线程间通信
示例:下载图片
// 下载图片 operation实现线程间通信
[[[NSOperationQueue alloc] init] addOperation:[NSBlockOperation blockOperationWithBlock:^{
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://7xjanq.com1.z0.glb.clouddn.com/6478.jpg"]]];
// 返回主线程
[[NSOperationQueue mainQueue] addOperation:[NSBlockOperation blockOperationWithBlock:^{
self.imageView.image = image;
}]];
}]];
示例:下载图片1和图片2 并合成图片
-(void)demo_combinenetworkimage
{
NSOperationQueue * queue = [[NSOperationQueue alloc] init];
__block UIImage * image1;
NSBlockOperation * block1 = [NSBlockOperation blockOperationWithBlock:^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://img1.gtimg.com/15/1513/151394/15139471_980x1200_0.jpg"]];
image1 = [UIImage imageWithData:data];
NSLog(@"下载图片1%@", [NSThread currentThread]);
}];
__block UIImage * image2;
NSBlockOperation * block2 = [NSBlockOperation blockOperationWithBlock:^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://img1.gtimg.com/15/1513/151311/15131165_980x1200_0.png"]];
image2 = [UIImage imageWithData:data];
NSLog(@"下载图片2%@", [NSThread currentThread]);
}];
NSBlockOperation * block3 = [NSBlockOperation blockOperationWithBlock:^{
CGFloat imageW = self.imageView.bounds.size.width;
CGFloat imageH = self.imageView.bounds.size.height;
// 开启位图上下文
UIGraphicsBeginImageContext(self.imageView.bounds.size);
// 画图
[image1 drawInRect:CGRectMake(0, 0, imageW * 0.5, imageH)];
[image2 drawInRect:CGRectMake(imageW * 0.5, 0, imageW * 0.5, imageH)];
// 将图片取出
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// 关闭图形上下文
UIGraphicsEndImageContext();
// 在主线程上显示图片
[[NSOperationQueue mainQueue] addOperation:[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"合成图片 %@", [NSThread currentThread]);
self.imageView.image = image;
}]];
}];
[block3 addDependency:block1];
[block3 addDependency:block2];
[queue addOperation:block1];
[queue addOperation:block2];
[queue addOperation:block3];
}
-
应用:SDWebImage 框架的底层主要功能实现就是基于多线程,使用多线程,我们可以实现小图片的多图片下载。这里的逻辑其实是比较复杂的
实现小图片的多图片下载思路:TIPS: 以上就是一些主要方法, 下面还有一些常用方法需要大家注意:
NSOperation BOOL executing; //判断任务是否正在执行 BOOL finished; //判断任务是否完成 void (^completionBlock)(void); //用来设置完成后需要执行的操作 - (void)cancel; //取消任务 - (void)waitUntilFinished; //阻塞当前线程直到此任务执行完毕 NSOperationQueue NSUInteger operationCount; //获取队列的任务数 - (void)cancelAllOperations; //取消队列中所有的任务 - (void)waitUntilAllOperationsAreFinished; //阻塞当前线程直到此队列中的所有任务执行完毕 [queue setSuspended:YES]; // 暂停queue [queue setSuspended:NO]; // 继续queue
###线程同步
所谓线程同步就是为了防止多个线程抢夺同一个资源造成的数据安全问题,所采取的一种措施。当然也有很多实现方法,请往下看:
互斥锁 :给需要同步的代码块加一个互斥锁,就可以保证每次只有一个线程访问此代码块。
@synchronized
(self) {
//需要执行的代码块
}