美文网首页iOS 开发 iOS Developer
iOS多线程开发——浅谈GCD

iOS多线程开发——浅谈GCD

作者: zkhCreator | 来源:发表于2016-08-19 12:44 被阅读90次

    GCD对于iOS开发者来说肯定不陌生,他和NSThread,NSOperation一起作为iOS开发中主要的三种多线程实现方法,而GCD是最最底层的,所以对于作为一个iOSer,GCD是必须掌握的。

    我通过对于以下两篇文章的阅读,基本上掌握了GCD的基本使用方法。所以首先感谢两位作者。

    一、基本概念


    对于新手来说,最常见同时最容易搞混的的莫过于GCD中的一些基本概念了。

    1. 并行与并发(Parallelism && Concurrency)

      • 并行:顾名思义就是同时行动,两个任务在两个线程(Thread)上进行处理,彼此互不干涉。而究其根本是因为多核进行处理,从而更快的解决任务。(可以类比于两条水管中同时给游泳池放水)
      • 并发:顾名思义,同时发生。对于有的性能较差的机子,比如说那些只有单核的设备,为了让用户感觉能够同时处理多个任务,他就需要通过不断的切换正在处理的线程,从而实现一种“伪并行”,这样就防止用户因为一个任务进行太久而无法进行下一个任务。(可以类比于我用两个口给游泳池放水,不过因为只有一根水管,而放水的方向有2个方面,所以需要不断的切换,从而实现两边同时达到需要的水位)
      • 区分如下图: 队列区别

        单队列

        串行队列,同步执行

        代码如下:

        func serialDispatchQueueWithSync() {
            let queue = dispatch_queue_create("serialDispatchQueue", DISPATCH_QUEUE_SERIAL)
        
            print("0")
        
            dispatch_sync(queue) {
                print("1")
            }
            dispatch_sync(queue) {
                print("2")
            }
            dispatch_sync(queue) {
                print("3")
            }
            dispatch_sync(queue) {
                print("4")
            }
            dispatch_sync(queue) {
                print("5")
            }
            print("6")
        }
        

        运行结果如下:

        0
        1
        2
        3
        4
        5
        6
        

        串行队列,异步执行

        代码如下:

        func serialDispatchQueueWithASync() {
            let queue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL)
        
            print("0")
            
            dispatch_async(queue) {
                print("1")
            }
            dispatch_async(queue) {
                print("2")
            }
            dispatch_async(queue) {
                print("3")
            }
            dispatch_async(queue) {
                print("4")
            }
            dispatch_async(queue) {
                print("5")
            }
            dispatch_async(queue) {
                print("6")
            }
            print("7")
        }
        

        运行结果如下:

        0
        7
        1
        2
        3
        4
        5
        6
        

        解释:因为queue是异步执行的,即调用dispatch_async函数,所以输出1 2 3 4 5 6的时候是在另一个线程中的,所以和主线程中的输出1 7 无关。

        并发队列,同步执行

        代码如下:

        func concurrentDispatchsQueueWithSync() {
            let queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT)
        
            print("0")
        
            dispatch_sync(queue) {
                print("1")
                print("12")
                print("13")
            }
            dispatch_sync(queue) {
                print("2")
                print("22")
                print("23")
            }
            dispatch_sync(queue) {
                print("3")
                print("32")
                print("33")
            }
            dispatch_sync(queue) {
                print("4")
                print("42")
                print("43")
            }
            print("5")
        }
        

        运行结果如下:

        0
        1
        12
        13
        2
        22
        23
        3
        32
        33
        4
        42
        43
        5
        

        并发队列,异步执行

        代码如下:

        func concurrentDispatchsQueueWithASync() {
            let queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT)
        
            print("0")
        
            dispatch_async(queue) {
                print("1")
                print("12")
                print("13")
            }
            dispatch_async(queue) {
                print("2")
                print("22")
                print("23")
            }
            dispatch_async(queue) {
                print("3")
                print("32")
                print("33")
            }
            dispatch_async(queue) {
                print("4")
                print("42")
                print("43")
            }
            print("5")
        }
        

        运行结果如下:

        0
        7
        1
        2
        12
        3
        22
        13
        4
        32
        23
        5
        42
        33
        52
        43
        53
        

        其他常见用法:

        挂起与恢复
        • 线程挂起:dispatch_suspnd()
        • 线程恢复:dispatch_resume()
        信号量

        由于有的时候(比如说在创建添加数组中的对象的时候),因为不同线程操纵的是同一个对象,所以很容易发生报错,这个时候需要通过信号量来对对应的内容进行控制,当信号量为0的时候,进入等待状态,不能执行下面的内容;当信号量为1的时候,可以执行,同时信号量减一,等到执行完毕,信号量加一。

        • 等待执行dispatch_semaphore_wait()
        • 信号量加一 dispatch_semahore_signal

        只执行一次

        有的时候,有的东西的创建只能被创建一次(即单例),这个时候就需要用到dispatch_once()
        代码如下:

        var token: dispatch_once_t = 0
        func test() {
            dispatch_once(&token) {
                println("This is printed only on the first call to test()")
            }
            println("This is printed for each call to test()")
        }
        

        关于读写问题的解决方法(买票问题)

        由于日常编码的过程中经常会遇到,当你在调用这个这个变量的时候。另一个线程也在调用这个变量(只在并发过程中),如果两个线程都是在读取数据,那并没有什么问题,但是如果其中一个在写入,或者两个都在写入,那么就会出现很大问题,所以苹果官方在GCD中也给我们准备了一个方法,让我们解决这个问题,那就是dispatch_barrier_async,这个方法让对的内容在并发过程中加入,从而方便组织内容的修改,从而使得对应的内容只能在当前线程中被进行修改。

        dispatch_barrier_asyncdispatch_barrier_async

        常见问题:

        死锁:
        let queue = dispatch_get_main_queue()
        
        dispatch_async(queue) { 
            dispatch_sync(queue, { 
                print("1")
            })
        }
        

        产生原理:因为dispatch_sync()会等到本身结束之后才会在主线程继续执行接下去的代码,但是dispatch_sync()这个方法调用的就是主线程,所以午饭等到主线程结束,所以就无法返回,就会卡在这里。

        解释:由于是并发队列,所以他会创建多个线程,从而保证每个线程的任务都能够尽快完成,所以顺序有一定的出入。

        最后通过两张动态图来最后总结单队列:

        dispatch_sync
        - (void)viewDidLoad
        {
          [super viewDidLoad];
        
          dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        
            NSLog(@"First Log");
        
          });
        
          NSLog(@"Second Log");
        }
        
        dispatch_syncdispatch_sync
        1. 主队列按照预定的顺序下来
        2. viewDidLoad在主线程进行执行。
        3. 直到执行到dispatch_sync
        4. 调用dispatch_sync代码,将block添加到全局队列中,主队列挂起。
        5. 全局队列先完成之前存放在全局队列中的内容。
        6. 完成之前的任务后,执行dispatch_sync的block中的内容。
        7. 完成block中的任务,主队列上的任务得以恢复
        8. 主队列继续执行其他任务。
        dispatch_async
        - (void)viewDidLoad
        {
          [super viewDidLoad];
        
          dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        
            NSLog(@"First Log");
        
          });
        
          NSLog(@"Second Log");
        }
        
        ![dispatch_sync](https://camo.githubusercontent.com/2c7cbaf76001a56622e14cf48a8d914d4b5c9df4/687474703a2f2f63646e312e72617977656e6465726c6963682e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031342f30312f64697370617463685f6173796e635f696e5f616374696f6e2e676966)
        
        
        1. 主队列一路按顺序执行任务——接着是一个实例化UIViewController 的任务,其中包含了viewDidLoad
        2. viewDidLoad在主线程执行。
        3. 主线程目前在viewDidLoad内,正要到达dispatch_async
        4. dispatch_async Block被添加到一个全局队列中,将在稍后执行。
        5. viewDidLoad在添加dispatch_async到全局队列后继续进行,主线程把注意力转向剩下的任务。同时,全局队列并发地处理它未完成地任务。记住Block在全局队列中将按照(FIFO)顺序出列,但可以并发执行。
        6. 添加到dispatch_async的代码块开始执行。
        7. dispatch_async Block完成,两个NSLog语句将它们的输出放在控制台上。

        调度组

        既然能够处理一个个的任务,那么我们就继续模拟一个环境,当我们需要在网上下载内容的时候(这些内容需要彼此联系在一起才能正常使用),这个时候,上面的单队列就不够了(或者说如果使用单队列产生的效果不是时间太长,就是文件的完整性不够好)

        这个时候我们就需要引入多队列,当多个内容都处理完成之后,让系统告诉我们,我们已经完成了以上的下载。可以继续做下一步事情了。

        任务开始

        在GCD中,我们可以通过dispatch_group_enter来通知当前任务的开始,而与之相对应的,我们必须在任务完成后,手动通知调度组任务结束(dispatch_group_leave)这样才能让调度组知道我们这个任务已经结束。

        代码如下(由于demo中有一个Photo类,所以此处贴上OC代码):

        dispatch_group_enter(downloadGroup); // 3
        Photo *photo = [[Photo alloc] initwithURL:url withCompletionBlock:^(UIImage *image, NSError *_error) {
            if (_error) {
                error = _error;
            }
        dispatch_group_leave(downloadGroup); // 4
        }];
        
        [[PhotoManager sharedManager] addPhoto:photo];
        
        

        任务提醒

        当我们所有的任务都手动通知后,那么就需要条用提醒来告诉他(我已经完成了所有内容,接下去需要试试哪里能够执行了),而提醒方式在GCD中有两种:

        • dispatch_group_wait() + dispatch_async()
        • dispatch_group_notify

        其他常见用法

        dispatch_apply()

        有的时候需要调用for循环来反复执行,但是当需要执行的代码量偏大的时候,for的效率比较低,这个时候需要用dispatch_apply()来执行,这样节省效率,不过当需要执行的代码量比较小的时候,dispatch_apply()的效率就比较差了。

        这个方法的效果和dispatch_sync一样,所以要注意死锁(后面会提到)

    相关文章

      网友评论

        本文标题:iOS多线程开发——浅谈GCD

        本文链接:https://www.haomeiwen.com/subject/dnsbsttx.html