iOS多线程-GCD

简介

Grand Central Dispatch (GCD)是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并行任务。

GCD是一套可以充分合理的利用CPU资源,同时可以处理比较复杂的多线程问题的多线程解决方案。

优点

  • 合理利用更多的CPU内核,提高CPU资源利用率
  • 自动为任务创建分配线程、执行任务、销毁线程,所有工作都由GCD完成
  • GCD提供了非常丰富的函数

使用

GCD是使用还是比较简单的,这里可以总结为一句话:队列以并行或串行的方式执行任务,所以要明白两个重要概念,队列和串行、并行。

队列

队列用来存放待执行的任务,队列按照FIFO(先进先出)执行任务,即先加入队列的任务先被执行,后加入的排队等待,执行完的任务从队列中释放。队列分为两种,串行队列和并行队列。

  • 串行队列(Serial Dispatch Queue),任务严格的按照加入队列的顺序一个一个被执行,一个任务要想被执行必须等它前面的所有任务执行完成,并且系统分配给串行队列的线程只有一条,所有任务都在一条线程上按照FIFO的规则完成。
  • 并行队列(Concurrent Dispatch Queue)并行队列也是按照FIFO的的规则执行任务的,但是系统会根据当前队列的任务数、CPU的内核数、内存使用情况等因素动态分配多条线程给队列,CPU在多条线程上快速切换,所以看上去就像是同时执行多条线程一样。
  • 创建队列,GCD使用dispatch_queue_create创建队列,方法有两个参数,第一个参数是队列的唯一标识符,第二个参数是用来表示队列是并行的还是串行的,传入DISPATCH_QUEUE_SERIALNULL表示串行队列,传入DISPATCH_QUEUE_CONCURRENT表示并行队列,
// 串行队列
dispatch_queue_t queue= dispatch_queue_create("SerialTest.queue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue= dispatch_queue_create("SerialTest.queue", NULL);
// 并行队列
dispatch_queue_t queue= dispatch_queue_create("Concurrent Test.queue", DISPATCH_QUEUE_CONCURRENT);
  • 主队列和全局并发队列,系统默认创建了两个队列,主队列dispatch_get_main_queue()又叫UI线程,用于刷新UI,所以如果有一些耗时操作,一定不要放在主线程里,会导致界面卡死。全局并发队列dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>)第一个参数表示优先级,一般设为DISPATCH_QUEUE_PRIORITY_DEFAULT第二个参数没什么用,一般设为0,全局并发队列是常用的,如果用到并发队列我们一般加到全局队列中,不用自己创建。

同步和异步

GCD提供了这两个函数dispatch_syncdispatch_async分别表示同步执行和异步执行,同步和异步的主要区别是是否会阻塞当前线程所,调用dispatch_sync会阻塞调用该函数的线程,直到block里的任务执行完成函数才会返回,当前线程才会继续执行。dispatch_async不会阻塞当前线程,当前线程会继续往下执行。
系统会根据调用的同步或异步函数和任务加入的队列类型来决定是否需要新开线程,有如下几种组合方式:

手动创建的串行队列 并行队列 主队列
同步执行(sync) 不会开新线程,串行执行 不会开新线程,串行执行 不会开新线程,串行执行
异步执行(async) 会开新线程,串行执行 会开新线程,并发执行 不会开新线程,串行执行

我们在开发中最常用的组合是异步执行并行队列,当程序要执行一些例如下载、上传数据等耗时操作时,如果不想阻塞UI线程,就可以把耗时操作交给GCD来处理。

dispatch_async(dispatch_get_global_queue(0, 0), ^{
        /*
         ......
         耗时操作
         ......
         */
        //任务完成,返回主线程刷洗UI
        dispatch_async(dispatch_get_main_queue(), ^{
            //刷新UI
        });
    });

GCD线程死锁
线程死锁指的是两个线程互相等待对方执行完成从而造成谁也无法执行,由于GCD的同步执行函数dispatch_sync会阻塞当前线程,所以经常会造成线程死锁,当使用dispatch_sync时要格外注意。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"任务执行前 %@",[NSThread currentThread]);
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"执行任务 %@",[NSThread currentThread]);
    });
    NSLog(@"执行任务后 %@",[NSThread currentThread]);
}

运行以上代码,在打印完NSLog(@"任务执行前 %@",[NSThread currentThread]);这句后,直接抛出如下异常

运行结果.png

以上异常就是由于线程死锁引起的,当程序执行到dispatch_sync函数,这个函数会把当前线程(主线程)阻塞掉,然后把任务加入到主队列中,也就是把任务交给主线程来执行,然后这时候主线程是阻塞状态,所以block里的任务不会被执行,而主线程要想执行后面的代码,必须要等待dispatch_sync函数返回,这就造成了死锁。

使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)

Q&A

  1. 以下代码会打印什么
- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        NSLog(@"1");
        [self performSelector:@selector(test) withObject:nil afterDelay:0.0];
        NSLog(@"3");
    });
    
}

- (void)test {
    NSLog(@"2");
}

答案是只会打印1和3,2不会打印,原因是performSelector:withObject:afterDelay:的本质是往RunLoop中添加timer,而子线程的RunLoop没有开启,想要打印3需要开启子线程的Run Loop

[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
  1. 以下代码会打印什么
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        NSLog(@"1");
    }];
    [thread start];
    [self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}

- (void)test {
    NSLog(@"2");
}

只会打印1,原因是线程执行完block里的代码就退出了,想要打印2需要开启线程的RunLoop

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        NSLog(@"1");
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }];
    [thread start];
    [self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}

- (void)test {
    NSLog(@"2");
}
  1. 如何实现异步并发执行任务1、任务2,等任务1和任务2都执行完毕后,再回到主线程执行任务3
    答:可以用GCD队列组实现
dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"任务1");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"任务2");
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"任务3");
    });

你可能感兴趣的:(iOS多线程-GCD)