您的位置:首页 > 移动开发 > IOS开发

iOS多线程学习之GCD

2015-09-05 10:40 423 查看
首先要问GCD是什么?可以做什么用?

苹果为什么出GCD?

不用GCD可不可以实现GCD所实现的功能?如果能,各有什么优势劣势?

(一)概念

GCD,Grand Central Dispatch,是异步执行任务技术之一。开发者只需要定义想要执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并执行任务。

GCD会自动管理线程的生命周期(创建线程,调度任务,销毁线程)

(二)任务和队列

任务:就是你想要做的事情

队列:就是存放任务的

(三)GCD的使用就两个步骤

1.定制任务

2.将任务添加到队列中去

(1)GCD自动将队列中的任务取出,放到对应的线程中执行

(2)任务的取出遵循FIFO原则

(四)GCD的API

dispatch_async(dispatch_queue_t queue, ^{
/**
*想要执行的任务
*/
})
解释:block里面是想要执行的任务,queue是存放这个任务的队列,async是决定异步还是同步(具不具备开启线程的能力)

在执行时,存在两种Dispatch Queue,一种是等待现在执行中处理的Serial Dispatch Queue,另一种是不等待现在执行中处理的Concurrent Dispatch Queue。

当queue是Serial Dispatch Queue时就按顺序执行,不是就并发执行,(开的线程数由XNU系统本身决定)

虽然知道了有Serial Dispatch Queue和Concurrent Dispatch Queue这两种,但是如何才能得到这些Dispatch Queue呢?方法有两种

第一种是通过GCD的API生成Dispatch Queue

(1)生成串行队列:

dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("", NULL);
这里dispatch_queue_create操作可以生成多个任意的Dispatch Queue,即也是生成多个线程线程,但是生成多的话会消耗系统资源,所以想要并发执行且不发生数据竞争的话,使用Concurrent Dispatch Queue

(2)生成并发队列

dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
与(1)唯一不同的是第二个参数

第二种是获取系统标准提供的Dispatch Queue(常用)

那就是Main Dispatch Queue 和 Global Dispatch Queue

Main Dispatch Queue :即为主线程中执行,因为主线程只有一个,所以为Serial Dispatch Queue

另一个Global Dispatch Queue是所有程序都能够使用的Concurrent Dispatch Queue。没有必要通过dispatch_queue_create逐个生成Concurrent Dispatch Queue。只要获取Global Dispatch Queue即可。

Global Dispatch Queue有四个优先级:High Priority,Default Priority,Low Priority,Background Priority

Global Dispatch Queue的获取方法:

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);


栗子:

@interface ViewController ()

{

UIImageView * _imageView;

}

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

[self createImageView];
}

- (void)createImageView  {

UIImageView * imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
imageView.backgroundColor = [UIColor greenColor];
_imageView = imageView;
[self.view addSubview:_imageView];
}

//同步异步主要影响的是能不能开启新的线程
//串行并行主要影响的是执行任务的方式
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

[self asyncGlobalQueue];
}

- (void)asyncGlobalQueue {

//全局并发队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//把任务添加到全局队列当中去异步执行
dispatch_async(globalQueue, ^{
NSURL * url = [NSURL URLWithString:@"http://dmimg.5054399.com/allimg/optuji/kidd/4.jpg"];
NSData * data = [NSData dataWithContentsOfURL:url];
UIImage * image = [UIImage imageWithData:data];
//回到主线程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
_imageView.image = image;
});
});

}

@end


这个是最常用的方式,另外三个组合就是:asyncSerial,syncConcurrent,syncSerial

因为使用GCD就是要另外开辟线程做些耗时麻烦的事,所以这三种方式意义不大,很少用

解释四个容易混淆的术语:同步,异步,并发,串行

同步:在当前线程中执行任务,不具备开启新线程的能力

异步:在新的线程中执行任务,具备开启新线程的能力

并发:多个任务并发执行

串行:一个任务执行完毕后,再执行下一个任务

多个任务并发执行,就是block里面可以放多个任务,他们并发执行(即开启多个线程,这个有XNU决定开启多少个)如上面的代码可以这样写再加一个任务

- (void)asyncGlobalQueue {

//全局并发队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//把任务添加到全局队列当中去异步执行
dispatch_async(globalQueue, ^{
UIImage * image = [self callMethodWithUrl:@"http://dmimg.5054399.com/allimg/optuji/kidd/4.jpg"];
NSLog(@"1--%@",[NSThread currentThread]);
//回到主线程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
_imageView.image = image;
});
});

dispatch_async(globalQueue, ^{
UIImage * image2 = [self callMethodWithUrl:@"http://hiphotos.baidu.com/%BC%BE%B9%E2%BB%AA/mpic/item/a3f1ef01d14144271d9583b7.jpg"];
NSLog(@"2--%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
_imageView2.image = image2;
});
});
}

- (UIImage *)callMethodWithUrl:(NSString *)urlStr {
NSURL * url = [NSURL URLWithString:urlStr];
NSData * data = [NSData dataWithContentsOfURL:url];
UIImage * image = [UIImage imageWithData:data];
return image;
}


可以运行看一下效果,或者可以通过[NSThread currentThread]查看当前线程

(五)延迟执行

有时候我们希望一些操作延迟一会执行,比如我的项目就是当用户在当前页面输入完数据后进行提交时提交成功后,希望2s后返回上一个界面,这个时候就可以用延迟执行

有三个效果可以达到,一个是NSObject里面的一个函数self performSelector:<#(SEL)#> withObject:<#(id)#> afterDelay:<#(NSTimeInterval)#>的方法,另一个就是GCD的方法,52个有效方法建议使用GCD,原因可参考那里,最后一个是线程睡眠方式,这个是不提倡的,比如主线程如果睡眠,后果还是挺可怕的,页面啥都不相应,用户体验就会很差啊

比如上面的代码可以换成:

dispatch_async(globalQueue, ^{
UIImage * image2 = [self callMethodWithUrl:@"http://hiphotos.baidu.com/%BC%BE%B9%E2%BB%AA/mpic/item/a3f1ef01d14144271d9583b7.jpg"];

//        dispatch_async(dispatch_get_main_queue(), ^{
//            _imageView2.image = image2;
//        });
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2--%@",[NSThread currentThread]);
_imageView2.image = image2;
});
});
打印结果会发现时间相差3s,也就是3s后执行block中的代码

dispatch_walltime这个API用来计算绝对时间,如X年X月X日等,dispatch_time用来计算相对时间,如上面的例子

(六)队列组

考虑一下这个需求:

a.下载两张图片

b.合并两个图片(就是水印)

c.显示

- (void)asyncGlobalQueue {

//全局并发队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//把任务添加到全局队列当中去异步执行
dispatch_async(globalQueue, ^{
//1.下载
UIImage * image = [self callMethodWithUrl:@"http://dmimg.5054399.com/allimg/optuji/kidd/4.jpg"];
UIImage * image2 = [self callMethodWithUrl:@"https://www.baidu.com/img/bd_logo1.png"];

//2.合并图片(利用2D画图)
//开启一个位图上下文
UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0);

//绘制第一张图片
[image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];

//绘制第二张图片
[image2 drawInRect:CGRectMake(340, image.size.height-image2.size.height*0.5, image2.size.width*0.5, image2.size.height*0.5)];

//得到上下文中的图片
UIImage * fullImage = UIGraphicsGetImageFromCurrentImageContext();

//结束上下文
UIGraphicsEndImageContext();

//3.回到主线程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
_fullImageView.image = fullImage;
});
});

//    dispatch_async(globalQueue, ^{
//
//        dispatch_async(dispatch_get_main_queue(), ^{
//            _imageView2.image = image2;
//        });
////        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
////            NSLog(@"2--%@",[NSThread currentThread]);
////            _imageView2.image = image2;
////        });
//    });

}
这个效果并不是太好,有时候下载图片好慢,这是因为下载图片过程耗时,假设一个图片下载5s,那么下砸两个图片就要10s,那么有没有办法让两个图片一起下载呢?答案是当然有啦,想想可以用什么办法呢?先别往下看,自己动脑想一下

其实上面已经给出答案了,就是把下载图片的任务分别放到队列中去并发执行

- (void)asyncGlobalQueue {

//全局并发队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//把任务添加到全局队列当中去异步执行
dispatch_async(globalQueue, ^{
//1.下载
UIImage * image = [self callMethodWithUrl:@"http://dmimg.5054399.com/allimg/optuji/kidd/4.jpg"];
_image1 = image;
[self drawWithImage];

});

dispatch_async(globalQueue, ^{

UIImage * image2 = [self callMethodWithUrl:@"https://www.baidu.com/img/bd_logo1.png"];
_image2 = image2;
[self drawWithImage];
});

//    dispatch_async(globalQueue, ^{
//
//        dispatch_async(dispatch_get_main_queue(), ^{
//            _imageView2.image = image2;
//        });
////        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
////            NSLog(@"2--%@",[NSThread currentThread]);
////            _imageView2.image = image2;
////        });
//    });

}

- (void)drawWithImage {
if (_image1 == nil || _image2 == nil) return ; //这里判断只要任一图片下载失败的话都不往下执行

//2.合并图片(利用2D画图)
//开启一个位图上下文
UIGraphicsBeginImageContextWithOptions(_image1.size, NO, 0.0);

//绘制第一张图片
[_image1 drawInRect:CGRectMake(0, 0, _image1.size.width, _image1.size.height)];

//绘制第二张图片
[_image2 drawInRect:CGRectMake(340, _image1.size.height-_image2.size.height*0.5, _image2.size.width*0.5, _image2.size.height*0.5)];

//得到上下文中的图片
UIImage * fullImage = UIGraphicsGetImageFromCurrentImageContext();

//结束上下文
UIGraphicsEndImageContext();

//3.回到主线程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
_fullImageView.image = fullImage;
});
}


这个效果会好一些,但是也有一点瑕疵就是我们要定义两个全局UIImage的属性,使代码还是有一些复杂的,那么有没有可以不用设置全局属性的思路呢,这时就要用到队列组了

- (void)asyncGlobalQueue {

//全局并发队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//把任务添加到全局队列当中去异步执行
//    dispatch_async(globalQueue, ^{
//        //1.下载
//        UIImage * image = [self callMethodWithUrl:@"http://dmimg.5054399.com/allimg/optuji/kidd/4.jpg"];
//        _image1 = image;
//        [self drawWithImage];
//
//    });
//
//    dispatch_async(globalQueue, ^{
//
//        UIImage * image2 = [self callMethodWithUrl:@"https://www.baidu.com/img/bd_logo1.png"];
//        _image2 = image2;
//        [self drawWithImage];
//    });
__block UIImage * image = nil;
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, globalQueue, ^{

image = [self callMethodWithUrl:@"http://dmimg.5054399.com/allimg/optuji/kidd/4.jpg"];
});
__block UIImage * image2 = nil;
dispatch_group_async(group, globalQueue, ^{

image2 = [self callMethodWithUrl:@"https://www.baidu.com/img/bd_logo1.png"];
});
//下载图片的任务放到globalQueue中,globalQueue再放到group中
//队列组的一个好处就是凡是扔到队列组中的任务,它会等队列组中的所有任务都搞定后,他会调用下面的这个API
//合并图片
//唤醒
dispatch_group_notify(group, globalQueue, ^{

UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0);

//绘制第一张图片
[image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];

//绘制第二张图片
[image2 drawInRect:CGRectMake(340, image.size.height-image2.size.height*0.5, image2.size.width*0.5, image2.size.height*0.5)];

//得到上下文中的图片
UIImage * fullImage = UIGraphicsGetImageFromCurrentImageContext();

//结束上下文
UIGraphicsEndImageContext();

//3.回到主线程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
_fullImageView.image = fullImage;
});
});
}
源码放在这里了,可以自行下载:点击打开链接

另外,也可以使用dispatch_group_wait函数仅等待全部处理执行结束

long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
if (result == 0) {
UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0);

//绘制第一张图片
[image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];

//绘制第二张图片
[image2 drawInRect:CGRectMake(340, image.size.height-image2.size.height*0.5, image2.size.width*0.5, image2.size.height*0.5)];

//得到上下文中的图片
UIImage * fullImage = UIGraphicsGetImageFromCurrentImageContext();

//结束上下文
UIGraphicsEndImageContext();

//3.回到主线程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
_fullImageView.image = fullImage;
});
}else{

NSLog(@"某一个处理还在进行中");
}


但一般情况下还是使用dispatch_group_notify追加处理到Main_Dispatch_Queue,因为这样可以简化源代码

(七)dispatch_barrier_async

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

// [self asyncGlobalQueue];
[self readingWrittingHandle];
}

- (void)readingWrittingHandle {

dispatch_queue_t queue = dispatch_queue_create("com.exmple", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{

NSLog(@"数据库读取操作0");
});
dispatch_async(queue, ^{

NSLog(@"数据库读取操作1");
});
dispatch_async(queue, ^{

NSLog(@"数据库读取操作2");
});
dispatch_async(queue, ^{

NSLog(@"数据库读取操作3");
});
/**
*  写入处理
*  将写入的内容读取之后的处理中
*/
dispatch_async(queue, ^{

NSLog(@"数据库写入操作");
});
/**
*  像上面这样写肯定是不行的,因为可能在写入处理的前面读到了与期望不符的数据,或者后面有的可能读不到数据,还可能因为非法访问导致应用程序异常结束,也可能有数据竞争问题等等,那么我们应该怎么做呢?这是就可以用dispatch_barrier_async函数了
*/
dispatch_async(queue, ^{

NSLog(@"数据库读取操作4");
});
dispatch_async(queue, ^{

NSLog(@"数据库读取操作5");
});
dispatch_async(queue, ^{

NSLog(@"数据库读取操作6");
});
dispatch_async(queue, ^{

NSLog(@"数据库读取操作7");
});
dispatch_async(queue, ^{

NSLog(@"数据库读取操作8");
});
dispatch_async(queue, ^{

NSLog(@"数据库读取操作9");
});

}
上面你可以多打印几次看看结果
- (void)readingWrittingHandle {

dispatch_queue_t queue = dispatch_queue_create("com.exmple", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{

NSLog(@"数据库读取操作0");
});
dispatch_async(queue, ^{

NSLog(@"数据库读取操作1");
});
dispatch_async(queue, ^{

NSLog(@"数据库读取操作2");
});
dispatch_async(queue, ^{

NSLog(@"数据库读取操作3");
});
/**
*  写入处理
*  将写入的内容读取之后的处理中
*/
//    dispatch_async(queue, ^{
//
//        NSLog(@"数据库写入操作");
//    });
/**
*  像上面这样写肯定是不行的,因为可能在写入处理的前面读到了与期望不符的数据,或者后面有的可能读不到数据,还可能因为非法访问导致应用程序异常结束,也可能有数据竞争问题等等,那么我们应该怎么做呢?这是就可以用dispatch_barrier_async函数了
*/
dispatch_barrier_async(queue, ^{

NSLog(@"数据库写入操作");
});
/**
*  barrier翻译成障碍物分界线的意思
*  dispatch_barrier_async以它为分界线,这个函数会把等待追加到Concurrent Dispatch Queue中的执行处理结束后,把block的任务添加到队列中,等这个block中的任务执行结束之后,Concurrent Dispatch Queue才恢复一般的动作,追加到Concurrent Dispatch Queue中的任务又开始处理
*/
dispatch_async(queue, ^{

NSLog(@"数据库读取操作4");
});
dispatch_async(queue, ^{

NSLog(@"数据库读取操作5");
});
dispatch_async(queue, ^{

NSLog(@"数据库读取操作6");
});
dispatch_async(queue, ^{

NSLog(@"数据库读取操作7");
});
dispatch_async(queue, ^{

NSLog(@"数据库读取操作8");
});
dispatch_async(queue, ^{

NSLog(@"数据库读取操作9");
});

}


使用Concurrent Dispatch Queue 和 dispatch_barrier_async可以实现高效率的数据访问和文件访问

(八)dispatch_set_target_queue

这个是用来变更生成的Dispatch Queue的优先级的

dispatch_set_target_queue(<#dispatch_object_t object#>, <#dispatch_queue_t queue#>)第一个参数为要变更优先级的Dispatch Queue,第二个参数为和谁的执行优先级相同,第一个参数肯定不能为Main Dispatch Queue 和 Global Dispatch Queue,原因自己想吧

(九)dispatch_apply

该函数按指定的次数将BLock追加到Dispatch Queue中,并等待全部处理执行结束

- (void)applyDispatch {

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//dispatch_queue_t queue = dispatch_queue_create("com.gcd", NULL);
NSArray * array = @[@1,@2,@3,@4,@5];
dispatch_async(queue, ^{
dispatch_apply([array count], queue, ^(size_t index) {

NSLog(@"%zu: %@", index, array[index]);
});

dispatch_async(dispatch_get_main_queue(), ^{

NSLog(@"Done");
});

});
}


因为dispatch_apply 与 dispatch_sync函数相同,会等待处理执行结束,所以建议在dispatch_async函数中使用非同步执行dispatch_apply函数以此来提高效率

(十)dispatch_suspend 和 dispatch_resume

- (void)suspendAndResume {

dispatch_queue_t queue = dispatch_queue_create("com.exmple", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue1 = dispatch_queue_create("com.exmple1", DISPATCH_QUEUE_CONCURRENT);
dispatch_set_target_queue(queue1, queue);
dispatch_suspend(queue1);
dispatch_async(queue1, ^{

NSLog(@"数据库读取操作1");
});
dispatch_async(queue, ^{

NSLog(@"数据库读取操作0");
});
dispatch_async(queue, ^{

NSLog(@"数据库读取操作2");
});
dispatch_resume(queue1);

}
上面的打印结果1总是最后打印

因为我没有实践过这个,所以我个人感觉挂起和恢复是和优先级联用的

(十一)Dispatch Semaphore

如前所述,当并行执行的处理更新数据时,会产生数据不一致的情况,有时应用程序还会异常退出。虽然可利用Serial Dispatch Queue 和 dispatch_barrier_async避免这类问题,但有必要进行更细粒度的排他控制。

在没有Serial Dispatch Queue 和 dispatch_barrier_async函数那么大粒度且一部分处理需要进行排他控制的情况下,Dispatch Semaphore便可发挥威力

- (void)semaphoreDispatch {

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSMutableArray * array = [NSMutableArray array];
for (int i = 0; i < 100000; i++) {

dispatch_async(queue, ^{

[array addObject:[NSNumber numberWithInt:i]];
});
}

}


上面代码使用Global Dispatch Queue更新NSMutableArray对象,很可能由于内存错误导致程序异常结束。此时就可使用Dispatch Semaphore

- (void)semaphoreDispatch {

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSMutableArray * array = [NSMutableArray array];
//    for (int i = 0; i < 100000; i++) {
//
//        dispatch_async(queue, ^{
//
//            [array addObject:[NSNumber numberWithInt:i]];
//        });
//    }
/**
*  生成 Dispatch Semaphore
*
*  @param 1 初始值设为1,保证可访问NSMutableArray对象的线程同时只有1个
*
*/
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
for (int i = 0; i < 100000; i++) {

dispatch_async(queue, ^{

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[array addObject:[NSNumber numberWithInt:i]];
dispatch_semaphore_signal(semaphore);
});

}
NSLog(@"%@",array);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: