来自 电脑系统 2019-09-19 12:05 的文章
当前位置: 金沙澳门官网网址 > 电脑系统 > 正文

八线程之GCD

级别: ★★☆☆☆标签:「iOS」「多线程」「GCD」作者: dac_1033审校: QiShare团队

iOS多线程——GCD篇,iosgcd

什么是GCD

GCD是苹果对多线程编程做的一套新的抽象基于C语言层的API,结合Block简化了多线程的操作,使得我们对线程操作能够更加的安全高效。

在GCD出现之前Cocoa框架提供了NSObject类的

performSelectorInBackground:withObject

performSelectorOnMainThread

方法来简化多线程编程技术。

GCD可以解决以下多线程编程中经常出现的问题:
1.数据竞争(比如同时更新一个内存地址)

2.死锁(互相等待)

3.太多线程导致消耗大量内存

在iOS中,如果把需要消耗大量时间的操作放在主线程上面,会妨碍主线程中被称为RunLoop的主循环的执行,从而导致不能更新用户界面、应用程序的画面长时间停滞等问题。

 

Dispatch Queue

Dispatch Queue是GCD中对于任务的抽象队列(FIFO)执行处理。

queue分为两种,

SERIAL_DISPATCH_QUEUE           等待现在执行中处理结束

CONCURRENT_DISPATCH_QUEUE       不等待现在执行中处理结束

换句话说也就是 SERIAL_DISPATCH_QUEUE 是串行,CONCURRENT_DISPATCH_QUEUE是并行。

具体到线程上,就是SERIAL_DISPATCH_QUEUE只会创在一个线程来处理任务序列,而CONCURRENT_DISPATCH_QUEUE则会创在多个线程,但是具体创建多少个则是有运行的操作系统根据资源决定的。

所以SERIAL_DISPATCH_QUEUE 中处理的代码是有序的,而CONCURRENT_DISPATCH_QUEUE中则是无序的,但是相对会更高效一点。

 

API

dispatch_queue_create

用于创建一个任务执行queue

参数列表

const char *label             queue的名称,作为该queue的唯一标示,改名会在Xcode和Instruments的调试器中直接作为DispatchQueue名称显示出来

dispatch_queue_attr_t     设定queue的类型,即ConcurrentQueue还是SerialQueue,NULL则默认为SerialQueue

返回值

dispatch_queue_t变量

这里要说一下main_dispatch_queue 和 global_dispatch_queue 这两种系统提供的,

main_queue通过

dispatch_get_main_queue()

global_queue通过

dispatch_get_global_queue(),global等级分为

HIGH、DEFAULT、LOW、BACKGROUND四种

 

dispatch_async

向指定的queue中添加block操作,异步的执行,屏蔽了多线程的实现细节,自动为我们生成线程执行。

 

dispatch_after

类似延迟函数,可以指定queue来进行延迟操作

    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
    dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"等待3秒");
    });

 

dispatch_group_notify

对于监听queue的执行,当所有任务完成后可以进行回调操作

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        NSLog(@"1");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"2");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"3");
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"finish");
    });

对于一系列的block在同一queue中执行,如果是serialQueue是顺序进行的,因此可以在最后一个任务来处理结束操作。但是对于concurrentQueue是并行的,如果想监听完结操作,就要用该方法。

dispatch_group_wait和notify差不多,只不过wait方法可以设置等待时间。如果时间到了还没有结束queue的所有操作,那么接下来还是会继续进行,不过还是可以设定为forever一直等待下去,这样就和notify起到一样的作用。

 

dispatch_barrier_async

该操作主要是为了防止资源竞争。在concurrentQueue中,所有block无序的按照所创建的线程数量同时进行。如果在concurrentQueue中有两个写入操作,而且他都是读取操作,这时两个写入操作间就会出现资源竞争,而读取操作则会读取脏数据。所以对于在concurrentQueue中不能够与其它操作并行的block就需要使用dispatch_barrier_async方法来防止资源竞争。

 

dispatch_sync

和dispatch_async不同,dispatch_sync用于线程之间的同步操作,比如说A线程要做一件事必须要放在B线程之后来进行,那么此时就需要用到dispatch_sync。

另外,不能够在某个执行线程中同步自己,这样会造成线程死锁,比如说

    dispatch_queue_t queue1 = dispatch_get_main_queue();
    dispatch_sync(queue1, ^{
        NSLog(@"main queue 中同步main queue操作");
    });

    dispatch_queue_t queue = dispatch_queue_create("com.queue.www", NULL);
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
            NSLog(@"在新的serial queue中同步serial queue操作");
        });
    });

所以说使用serial queue的时候一定不要同步自己。

 

dispatch_apply

dispatch_apply函数是dispatch_sync和dispatch group的关联函数,是用指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理执行结束,例如

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"%ld",index);
    });
    NSLog(@"apply finish");

2015-08-02 09:38:18.296 Dispatch[7388:2035125] 4
2015-08-02 09:38:18.296 Dispatch[7388:2035244] 2
2015-08-02 09:38:18.296 Dispatch[7388:2035241] 1
2015-08-02 09:38:18.296 Dispatch[7388:2035259] 6
2015-08-02 09:38:18.296 Dispatch[7388:2035243] 0
2015-08-02 09:38:18.296 Dispatch[7388:2035257] 3
2015-08-02 09:38:18.296 Dispatch[7388:2035258] 5
2015-08-02 09:38:18.296 Dispatch[7388:2035260] 7
2015-08-02 09:38:18.296 Dispatch[7388:2035125] 8
2015-08-02 09:38:18.296 Dispatch[7388:2035244] 9
2015-08-02 09:38:18.296 Dispatch[7388:2035125] apply finish

实际上可以看出来,该函数让主线程和queue进行同步操作,并且等queue中所有线程执行完毕后才继续执行。

 

dispatch_semaphore

在进行数据处理时,dispatch_barrier_async可以避免这类问题,但是有时需要更加精细的操作。

比如要对数组添加10000个对象,用concurrentQueue添加。我们知道concurrentQueue会生成多个线程,很可能会出现多个线程一起对数组访问的情况,很容易出现问题。我们需要控制一次只让一个线程操作数组,如下:

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

    NSMutableArray *array = [NSMutableArray new];
    for (int i =  0 ; i < 10000 ; i++)
    {
        dispatch_async(queue, ^{
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            [array addObject:[NSNumber numberWithInt:i]];
            NSLog(@"add %d",i);
            dispatch_semaphore_signal(semaphore);
        });
    }

这里简单说一下信号量,也就是创建dispatch_semaphore的第二个参数。指定一个信号量,那么当信号量是大于0的时候所有线程都是可访问的。一旦有现成访问信号量会减1,如果信号量为0就会进入等待,知道dispatch_semaphore_signal函数调用来重新恢复信号量。所以基本上可以理解为有几个信号量就能有几个线程并发的访问。

再比如说现在有两个线程一个添加数据一个删除数据,那么就需要两个信号量变量来实现多线程间的协作

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphoreAdd = dispatch_semaphore_create(1);
    dispatch_semaphore_t semaphoreRemove = dispatch_semaphore_create(0);
    NSMutableArray *array = [NSMutableArray new];
    for (int i =  0 ; i < 2 ; i++)
    {
        dispatch_async(queue, ^{
            dispatch_semaphore_wait(semaphoreAdd, DISPATCH_TIME_FOREVER);
            [array addObject:[NSNumber numberWithInt:i]];
            NSLog(@"add %lu",[array count]);
            dispatch_semaphore_signal(semaphoreRemove);
        });
        dispatch_async(queue, ^{
            dispatch_semaphore_wait(semaphoreRemove, DISPATCH_TIME_FOREVER);
            [array removeObject:[NSNumber numberWithInt:i]];
            NSLog(@"add %lu",[array count]);
            dispatch_semaphore_signal(semaphoreAdd);
        });
    }

 

 

dispatch_once

dispatch_once用来标记一个操作,只执行一次,该方法一般在生产单例对象使用。如果不用dispatch_once创建单例是不安全的,需要进行加锁处理,但是dispatch_once可以很好地解决这一点。

+(instancetype)sharedInstance
{
    static CustomObject *obj;
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        obj = [[CustomObject alloc] init];
    });
    return obj;
}

 

什么是GCD GCD是苹果对多线程编程做的一套新的抽象基于C语言层的API,结合Block简化了多线程的操作,使得我们对...

Grand Central Dispatch是由苹果开发的多线程调度框架,能够优化多线程应用程序的执行过程以支持多核处理器。GCD底层有线程池,线程池的概念在上一篇文章中有简单介绍,线程池是系统自动来维护,开发者只关注队列(Dispatch Queue)及任务的创建和同步异步调用即可。iOS中使用GCD来实现线程操作很简单:创建队列 > 同步/异步调用 > 将任务放在上一步调用的block中。下面我们来实现异步执行耗时操作,当耗时操作执行完毕时,回到主线程来更新相应的UI的功能,简易的代码如下:

dispatch_queue_t queue = dispatch_queue_create("QiShareQueue", DISPATCH_QUEUE_CONCURRENT);dispatch_async(queue, ^{ // 放一些极其耗时间的任务在此执行 dispatch_async(dispatch_get_main_queue(), ^{ // 耗时任务完成,拿到资源,更新UI更新UI只可以在主线程中更新 });});

在调用GCD中方法执行多线程操作时,GCD可自动利用CPU的多核实现异步处理任务,自动管理线程的生命周期(创建线程、调度任务、销毁线程),开发者只需要设置GCD需要执行的任务,不用编写任何线程管理代码。因此GCD也是苹果最为推荐开发者使用的多线程框架。

首先我们使用GCD实现异步加载多张网络图片,来熟悉一下GCD的使用过程:

//// 自定义图片Model@interface GCDImage : NSObject@property (nonatomic, assign) NSInteger index;@property (nonatomic, strong) NSData *imgData;@end@implementation GCDImage@end//// GCD加载一张网络图片#define ColumnCount 4#define RowCount 5#define Margin 10@interface MultiThread_GCD ()@property (nonatomic, strong) NSMutableArray *imageViews;@end@implementation MultiThread_GCD- viewDidLoad { [super viewDidLoad]; [self setTitle:@"GCD"]; [self.view setBackgroundColor:[UIColor whiteColor]]; self.edgesForExtendedLayout = UIRectEdgeNone; [self layoutViews];}- layoutViews { CGSize size = self.view.frame.size; CGFloat imgWidth = (size.width - Margin * (ColumnCount + 1)) / ColumnCount; _imageViews=[NSMutableArray array]; for (int row=0; row<RowCount; row++) { for (int colomn=0; colomn<ColumnCount; colomn++) { UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(Margin + colomn * (imgWidth + Margin), Margin + row * (imgWidth + Margin), imgWidth, imgWidth)]; imageView.backgroundColor = [UIColor cyanColor]; [self.view addSubview:imageView]; [_imageViews addObject:imageView]; } } UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; button.frame = CGRectMake(15, (imgWidth + Margin) * RowCount + Margin, size.width - 15 * 2, 45); [button addTarget:self action:@selector(loadImageWithMultiOperation) forControlEvents:UIControlEventTouchUpInside]; [button setTitle:@"点击加载" forState:UIControlStateNormal]; [self.view addSubview:button];}#pragma mark - 多线程下载图片//- loadImageWithMultiOperation {//// int count = RowCount * ColumnCount;//// // 创建一个串行队列 第一个参数:队列名称 第二个参数:队列类型// // 注意queue对象不是指针类型// dispatch_queue_t serialQueue=dispatch_queue_create("QiShareSerialQueue", DISPATCH_QUEUE_SERIAL);// // 创建多个线程用于填充图片// for (int i=0; i<count; ++i) {// //异步执行队列任务// dispatch_async(serialQueue, ^{// GCDImage *gcdImg = [[GCDImage alloc] init];// gcdImg.index = i;// [self loadImg:gcdImg];// });//// }//}- loadImageWithMultiOperation { int count = RowCount * ColumnCount; // 取得全局队列 第一个参数:线程优先级 第二个参数:标记参数,目前没有用,一般传入0 dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 创建多个线程用于填充图片 for (int i=0; i<count; ++i) { //异步执行队列任务 dispatch_sync(globalQueue, ^{ GCDImage *gcdImg = [[GCDImage alloc] init]; gcdImg.index = i; [self loadImg:gcdImg]; }); }}#pragma mark - 加载图片- loadImg:(GCDImage *)gcdImg { // 请求数据 gcdImg.imgData = [self requestData]; // 更新UI界面(mainQueue是UI主线程// dispatch_sync(dispatch_get_main_queue(), ^{ [self updateImage:gcdImg];// }); // 打印当前线程 NSLog(@"current thread: %@", [NSThread currentThread]);}#pragma mark - 请求图片数据- requestData { NSURL *url = [NSURL URLWithString:@"https://store.storeimages.cdn-apple.com/8756/as-images.apple.com/is/image/AppleInc/aos/published/images/a/pp/apple/products/apple-products-section1-one-holiday-201811?wid=2560&hei=1046&fmt=jpeg&qlt=95&op_usm=0.5,0.5&.v=1540576114151"]; NSData *data = [NSData dataWithContentsOfURL:url]; return data;}#pragma mark - 将图片显示到界面- updateImage:(GCDImage *)gcdImg { UIImage *image = [UIImage imageWithData:gcdImg.imgData]; UIImageView *imageView = _imageViews[gcdImg.index]; imageView.image = image;}@end

下面我们就来逐步详细介绍GCD相关的细节...

2.1 GCD中用来执行任务的常用方法
// 同步执行任务(在当前线程中执行任务,不会开启新线程)dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);// 异步执行任务(可以在新线程中执行任务,有开启新线程的能力)dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

其中,第一个参数为任务所要加入的队列;第二个参数是一个dispatch_block_t类型的block,即任务。

2.2 GCD中的队列
// 串行队列的创建方法dispatch_queue_t queue = dispatch_queue_create("QiShareQueue", DISPATCH_QUEUE_SERIAL);// 并发队列的创建方法dispatch_queue_t queue = dispatch_queue_create("QiShareQueue", DISPATCH_QUEUE_CONCURRENT);

其中,第一个参数为队列的名称;第二个参数根据取值命名,就可看出为队列的类型。

两个特殊的队列:

  • 主队列(Main Dispatch Queue):是串行队列,所有放在主队列中的任务,都会放到主线程中执行;获取主队列:dispatch_queue_t queue = dispatch_get_main_queue();
  • 全局队列(Global Dispatch Queue):是并发行队列;获取全局队列:dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);其中,第一个参数为队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT;第二个参数用0即可。

则调度方法与任务所在队列的组合如下:

调度方法 并发队列 串行队列 主队列
异步(dispatch_async) 开启多个新线程,并发执行任务 开启1个新线程,串行执行任务 没有开启新线程,串行执行任务
同步(dispatch_sync) 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 主线程调用:死锁导致程序卡死其他线程调用:没有开启新线程,串行执行任务

我们可以运行下面的代码来验证上列表组合中每一项的正确性,如下:

- addTask:(NSInteger)tag { [NSThread sleepForTimeInterval:2]; NSLog(@"addTask%ld--->> %@", tag, [NSThread currentThread]); NSURLSessionTask *task = [NSURLSession.sharedSession dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.so.com/"]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSLog(@"任务完成%ld--->> %@", tag, [NSThread currentThread]); }]; [task resume];}#pragma mark - 串行/并发队列 + 同步/异步调用组合// 异步执行- asyncExecute { NSLog(@"CurrentThread---%@", [NSThread currentThread]); NSLog(@"asyncExecute start"); // + 并发队列(开启多个线程,并发执行任务) dispatch_queue_t queue = dispatch_queue_create(QiShareQueue, DISPATCH_QUEUE_CONCURRENT);// // + 串行队列(开启1个新线程,串行执行任务)// dispatch_queue_t queue = dispatch_queue_create(QiShareQueue, DISPATCH_QUEUE_SERIAL);// // + 主队列(没有开启新线程,串行执行任务)// dispatch_queue_t queue = dispatch_get_main_queue(); for (NSInteger i=0; i<10; i++) { dispatch_async(queue, ^{ // 追加任务 [self addTask:i]; }); } NSLog(@"asyncExecute end");}// 同步执行- syncExecute { NSLog(@"currentThread---%@", [NSThread currentThread]); NSLog(@"syncExecute start"); // // + 并发队列(没有开启新线程,串行执行任务)// dispatch_queue_t queue = dispatch_queue_create(QiShareQueue, DISPATCH_QUEUE_CONCURRENT);// // + 串行队列(没有开启新线程,串行执行任务)// dispatch_queue_t queue = dispatch_queue_create(QiShareQueue, DISPATCH_QUEUE_SERIAL); // + 主队列(1.主线程调用:死锁;2.其他线程调用:不会开启新线程,执行完一个任务,再执行下一个任务) dispatch_queue_t queue = dispatch_get_main_queue(); for (NSInteger i=0; i<10; i++) { dispatch_sync(queue, ^{ // 追加任务 [self addTask:i]; }); } NSLog(@"syncExecute end");}

本文由金沙澳门官网网址发布于电脑系统,转载请注明出处:八线程之GCD

关键词: