多线程GCD

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程GCD相关的知识,希望对你有一定的参考价值。

多线程是程序开发中非常基础的一个概念,大家在开发过程中应该或多或少用过相关的东西。同时这恰恰又是一个比较棘手的概念,一切跟多线程挂钩的东西都会变得复杂。如果使用过程中对多线程不够熟悉,很可能会埋下一些难以预料的坑。

 

ios中的多线程技术主要有NSThread, GCD和NSOperation。他们的封装层次依次递增,其中

 

  • NSThread封装性最差,最偏向于底层,主要基于thread使用

  • GCD是基于C的API,直接使用比较方便,主要基于task使用

  • NSOperation是基于GCD封装的NSObject对象,对于复杂的多线程项目使用比较方便,主要基于队列使用

 

上篇文章介绍了NSThread的用法,NSThread已经属于古董级别的东西了,欣赏一下可以,真正使用就不要麻烦他了。GCD是多线程中的新贵,比起NSThread更加强大,也更容易使用。由于GCD的东西比较多,我会分好几篇文章介绍,这篇文章主要介绍GCD中的queue相关知识。

 

dispatch_queue_t

 

使用GCD之后,你可以不用再浪费精力去关注线程,GCD会帮你管理好一切。你只需要想清楚任务的执行方法(同步还是异步)和队列的运行方式(串行还是并行)即可。

 

任务是一个比较抽象的概念,表示一段用来执行的代码,他对应到代码里就是一个block或者一个函数。

 

队列分为串行队列和并行队列:

 

  • 串行队列一次只能执行一个任务。只有一个任务执行完成之后,下一个任务才能执行,主线程就是一个串行的队列。

    • 并行队列可以同时执行多个任务,系统会维护一个线程池来保证并行队列的执行。线程池会根据当前任务量自行安排线程的数量,以确保任务尽快执行。

     

    队列对应到代码里是一个dispatch_queue_t对象:

    dispatch_queue_t queue;

    对象就有内存。跟普通OC对象类似,我们可以用dispatch_retain()和dispatch_release()对其进行内存管理,当一个任务加入到一个queue中的时候,任务会retain这个queue,直到任务执行完成才会release。

    值得高兴的是,iOS6之后,dispatch对象已经支持ARC,所以在ARC工程之下,我们可以不用担心他的内存,想怎么玩就怎么玩。

    要申明一个dispatch的属性。一般情况下我们只需要用strong即可。

    @property (nonatomic, strong) dispatch_queue_t queue;

    如果你是写一个framework,framework的使用者的SDK有可能还是古董级的iOS6之前。那么你需要根据OS_OBJECT_USE_OBJC做一个判断是使用strong还是assign。(一般github上的优秀第三方库都会这么做)

    #if OS_OBJECT_USE_OBJC

    @property (nonatomic, strong) dispatch_queue_t queue;

    #else

    @property (nonatomic, assign) dispatch_queue_t queue;

    #endif

    async

    GCD中有2个异步的API

    void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

    void dispatch_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work);

    他们都是将一个任务提交到queue中,提交之后立即返回,不等待任务的的执行。提交之后,系统会对queue做retain操作,任务执行完成之后,queue再被release。两个函数实际的功能是一样的,唯一的区别在于dispatch_async接受block作为参数,dispatch_async_f接受函数。

    使用dispatch_async的时候block会被copy,在block执行完成之后block再release,由于是系统持有block,所以不用担心循环引用的问题,block里面的self不需要weak

    在dispatch_async_f中,context会作为第一个参数传给work函数。如果work不需要参数,context可以传入NULL。work参数不能传入NULL,否则可能发生无法预料的事儿

    异步是一个比较抽象的概念,简单的说就是将任务加入到队列中之后,立即返回,不需要等待任务的执行。语言的描述比较抽象,我们用代码加深一下对概念的理解

    NSLog(@"this is main queue, i want to throw a task to global queue");

    dispatch_queue_t globalQueue = dispatch_queue_create("com.liancheng.global_queue", DISPATCH_QUEUE_SERIAL);

    dispatch_async(globalQueue, ^{

        // task

    });

    NSLog(@"this is main queue, throw task completed");

    上面这段代码,会以这样的方式运行,红色表示正在执行的模块,灰色表示未执行或

    • 并行队列可以同时执行多个任务,系统会维护一个线程池来保证并行队列的执行。线程池会根据当前任务量自行安排线程的数量,以确保任务尽快执行。

     

    队列对应到代码里是一个dispatch_queue_t对象:

     

    dispatch_queue_t queue;

     

    对象就有内存。跟普通OC对象类似,我们可以用dispatch_retain()和dispatch_release()对其进行内存管理,当一个任务加入到一个queue中的时候,任务会retain这个queue,直到任务执行完成才会release。

     

    值得高兴的是,iOS6之后,dispatch对象已经支持ARC,所以在ARC工程之下,我们可以不用担心他的内存,想怎么玩就怎么玩。

     

    要申明一个dispatch的属性。一般情况下我们只需要用strong即可。

     

    @property (nonatomic, strong) dispatch_queue_t queue;

     

    如果你是写一个framework,framework的使用者的SDK有可能还是古董级的iOS6之前。那么你需要根据OS_OBJECT_USE_OBJC做一个判断是使用strong还是assign。(一般github上的优秀第三方库都会这么做)

     

    #if OS_OBJECT_USE_OBJC

    @property (nonatomic, strong) dispatch_queue_t queue;

    #else

    @property (nonatomic, assign) dispatch_queue_t queue;

    #endif

     

    1. 先在main queue中执行第一个nslog

    2. dispatch_async会将block提交到globalQueue中,提交成功之后立即返回

    3. main queue执行第二个nslog

    4. 等global queue中block前面的任务执行完成之后,block被执行。


    sync

     

    与异步相似,GCD中同步的API也是2个

     

    void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

    void dispatch_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work);

     

    2个API作用相同:将任务提交到queue中,任务加入queue之后不会立即返回,等待任务执行完成之后再返回。同sync类似,dispatch_sync与dispatch_sync_f唯一的区别在于dispatch_sync接收block作为参数,block被系统持有,不需要对self使用weak。dispatch_sync_f接受函数work作为参数,context作为传给work函数的第一个参数。同样,work参数也不能传入NULL,否则会发生无法预料的事儿

     

    同步表示任务加入到队列中之后不会立即返回,等待任务完成再返回。语言的描述比较抽象,我们再次用代码加深一下对概念的理解

     

    NSLog(@"this is main queue, i want to throw a task to global queue");

    dispatch_queue_t globalQueue = dispatch_queue_create("com.liancheng.global_queue", DISPATCH_QUEUE_SERIAL);

    dispatch_sync(globalQueue, ^{

        // task

    });

    NSLog(@"this is main queue, throw task completed");

    1. 先在main queue中执行第一个nslog

    2. dispatch_sync会将block提交到global queue中,等待block的执行

    3. global queue中block前面的任务执行完成之后,block执行

    4. block执行完成之后,dispatch_sync返回

    5. dispatch_sync之后的代码执行


    由于dispatch_sync需要等待block被执行,这就非常容易发生死锁。如果一个串行队列,使用dispatch_sync提交block到自己队列中,就会发生死锁

     

    dispatch_queue_t queue = dispatch_queue_create("com.liancheng.serial_queue", DISPATCH_QUEUE_SERIAL);

     

    dispatch_async(queue, ^{

        // 到达串行队列

     

        dispatch_sync(queue, ^{     //发生死锁

     

        });

    });

  • 获取全局队列

     

    除了主线程队列,GCD提供了几个全局队列,可以直接获取使用

     

    dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);

     

    dispatch_get_global_queue方法获取的全局队列都是并行队列,并且队列不能被修改,也就是说对全局队列调用dispatch_suspend(), dispatch_resume(), dispatch_set_context()等方法无效

     

    1. identifier: 用以标识队列优先级,推荐用qos_class枚举作为参数,也可以使用dispatch_queue_priority_t

    2. flags: 预留字段,传入任何非0的值都可能导致返回NULL。

    3. 创建队列

       

      当无法获取到理想的队列时,我们可以自己创建队列。

       

      dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);

       

      如果未使用ARC,dispatch_queue_create创建的queue在使用结束之后需要调用dispatch_release。

       

      • label: 队列的名称,调试的时候可以区分其他的队列

      • attr: 队列的属性,dispatch_queue_attr_t类型。用以标识队列串行,并行,以及优先级等信息


      attr参数有三种传值方式:

       

      // 串行

      #define DISPATCH_QUEUE_SERIAL NULL

       

      // 并行

      #define DISPATCH_QUEUE_CONCURRENT

              DISPATCH_GLOBAL_OBJECT(dispatch_queue_attr_t,

              _dispatch_queue_attr_concurrent)

       

      // 自定义属性值

      dispatch_queue_attr_t dispatch_queue_attr_make_with_qos_class(dispatch_queue_attr_t attr, dispatch_qos_class_t qos_class, int relative_priority);

       

      DISPATCH_QUEUE_SERIAL或者NULL,表示创建串行队列,优先级为目标队列优先级。DISPATCH_QUEUE_CONCURRENT表示创建并行队列,优先级也为目标队列优先级。

       

      dispatch_queue_attr_make_with_qos_class函数可以创建带有优先级的dispatch_queue_attr_t对象。通过这个对象可以自定义queue的优先级。

    4. dispatch_barrier

       

      在并行队列中,有的时候我们需要让某个任务单独执行,也就是他执行的时候不允许其他任务执行。这时候dispatch_barrier就派上了用场。

       

      使用dispatch_barrier将任务加入到并行队列之后,任务会在前面任务全部执行完成之后执行,任务执行过程中,其他任务无法执行,直到barrier任务执行完成

以上是关于多线程GCD的主要内容,如果未能解决你的问题,请参考以下文章

多线程——GCD

iOS多线程之GCD小记

iOS多线程开发之GCD(下篇)

多线程编程GCD

多线程GCD

iOS-多线程之GCD(原创)