iOS多线程编程--GCD

Posted 乌戈勒

tags:

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

前言:

ios支持多个层次的多线程编程,层次越高的抽象程度越高,使用也越方便。

根据抽象层次从低到高依次列出iOS所支持的多线程编程方法:

1.Thread :是三种方法里面相对轻量级的,但需要管理线程的生命周期、同步、加锁问题,
这会导致一定的性能开销。

2.Cocoa Operations:是基于OC实现的,NSOperation以面向对象的方式封装了需要执行的操作,不必关心线程管理、同步等问题。
NSOperation是一个抽象基类,我们需要实例化NSOperation的具体子类来进行多线程开发。

3.Grand Central Dispatch:简称GCD,iOS4才开始支持.
提供了一些新特性、运行库来支持多核并行编程,它的关注点更高:如何在多个cpu上提升效率

了解NSOperation点这里


一、简介

1、什么是GCD?

在iOS所有实现多线程的方案中,GCD应该是最有魅力的,因为GCD本身是苹果公司为多核的并行运算提出的解决方案。

GCD在工作时会自动利用更多的处理器核心,以充分利用更强大的机器。GCD是Grand Central Dispatch的简称,它是基于C语言的,译为“牛逼的中枢调度器”。

如果使用GCD,完全由系统管理线程,我们不需要编写线程代码。只需定义想要执行的任务,然后添加到适当的调度队列(dispatch queue)。GCD会负责创建线程和调度你的任务,系统直接提供线程管理。


2、GCD的优势

1、GCD会自动利用更多的CPU内核(比如双核、四核);

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

3、程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理的代码。

二、任务和队列

1、GCD中有2个核心的概念

1、任务:执行什么操作

2、队列:用来存放任务
将长期运行的任务拆分成多个工作单元,并将这些单元添加到dispath queue中,系统会为我们管理这些dispath queue,
为我们在多个线程上执行工作单元,我们不需要直接启动和管理后台线程。

2、执行顺序FIFO

1、GCD的dispath queue严格遵循FIFO(先进先出)原则。
添加到dispath queue的工作单元将始终按照加入dispath queue的顺序启动。

2、dispatch queue按先进先出的顺序,串行或并发地执行任务。
将任务添加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行。

3、同步和异步的区别

    1、同步:在当前线程中执行;
    2、异步:在另一条线程中执行。

三、队列

1、队列的类型

1、并发dispatch queue

1、可以同时并行地执行多个任务,不过并发queue仍然按先进先出的顺序来启动任务。
并发queue会在之前的任务完成之前就出列下一个任务并开始执行。

2、系统给每个应用提供三个并发dispatch queue,整个应用内全局共享,三个queue的区别是优先级。

3、你不需要显式地创建这些queue,使用dispatch_get_global_queue函数来获取这三个queue。

4、虽然dispatch queue是引用计数的对象,但你不需要retain和release全局并发queue。
因为这些queue对应用是全局的,retain和release调用会被忽略。

5、你也不需要存储这三个queue的引用,每次都直接调用dispatch_get_global_queue获得queue就行了。
// 获取默认优先级的全局并发dispatch queue  
dispatch_queue_t  queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 

2、串行dispatch queue

1、应用的任务需要按特定顺序执行时,就需要使用串行dispatch Queue,串行queue每次只能执行一个任务。

2、保证任务按可预测的顺序执行,而且只要你异步地提交任务到串行queue,就永远不会产生死锁。

3、你必须显式地创建和管理所有你使用的串行queue,应用可以创建任意数量的串行queue,但不要为了同时执行更多任务而创建更多的串行queue。
如果你需要并发地执行大量任务,应该把任务提交到全局并发queue。
dispatch_queue_t queue;  
queue = dispatch_queue_create("com.gcd.queue", NULL); 
1、并发队列(Concurrent Dispatch Queue)
可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)

2、串行队列(Serial Dispatch Queue)
让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

注意:
并发功能只有在异步(dispatch_async)函数下才有效。


2、概念的混淆

  • 比较容易混淆的术语:同步、异步、并发、串行。

    同步和异步决定了要不要开启新的线程
    1、同步:在当前线程中执行任务,不具备开启新线程的能力;
    2、异步:在新的线程中执行任务,具备开启新线程的能力。
    
    并发和串行决定了任务的执行方式
    3、并发:多个任务并发(同时)执行;
    4、串行:一个任务执行完毕后,再执行下一个任务。
    
  • 说明:

    1、同步函数不具备开启线程的能力,无论是什么队列都不会开启线程,所以,并发功能只有在异步函数下才有效;

    2、异步函数具备开启线程的能力,开启几条线程由队列决定(串行队列只会开启一条新的线程,并发队列会开启多条线程)。

    同步函数:
    1、并发队列:不会开线程
    2、串行队列:不会开线程
    
    异步函数:
    1、并发队列:能开启N条线程
    2、串行队列:开启1条线程
    
  • 补充:

    1、凡是函数中,各种函数名中带有create\\copy\\new\\retain等字眼,都需要在不需要使用这个数据的时候进行release。

    2、GCD的数据类型在ARC的环境下不需要再做release。

    3、CF(core Foundation)的数据类型在ARC环境下还是需要做release。

    4、异步函数具备开线程的能力,但不一定会开线程。


3、如何获取队列?

串行队列的获取方式:
1、使用dispatch_queue_create函数创建串行队列SERIAL QUEUE;
2、使用dispatch_get_main_queue()获得主队列

并发队列的获取方式:
1、使用dispatch_get_global_queue函数获得全局的并发队列;
2、使用dispatch_queue_create函数创建并发队列CONCURRENT QUEUE;

注意:
绝对不要在任务中调用 dispatch_sync或dispatch_sync_f函数,并同步调度新任务到当前正在执行的 queue。对于串行queue这一点特别重要,因为这样做肯定会导致死锁,而并发queue也应该避免这样做。


4、各种队列的执行效果

比较全局并发队列手动创建串行队列主队列
同步sync“没有”开启新线程,”串行”执行“没有”开启新线程,”串行”执行“没有”开启新线程,”串行”执行
异步async“有”开启新线程,”并发”执行“有”开启新线程,”串行”执行“没有”开启新线程,”串行”执行

四、如何添加任务到队列

要执行一个任务,你需要将它添加到一个适当的dispatch queue,你可以单个或按组来添加,也可以同步或异步地执行一个任务。
一旦进入到queue中,queue会负责尽快地执行你的任务。
一般可以用一个block来封装任务内容。

1、添加单个任务到queue

1、用同步的方式执行任务 
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
注意:绝对不要在任务中调用 dispatch_sync或dispatch_sync_f函数,并同步调度新任务到当前正在执行的queue。
对于串行queue这一点特别重要,因为这样做肯定会导致死锁;而并发queue也应该避免这样做。

2、用异步的方式执行任务 
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

2、并发地执行循环迭代

如果你使用循环执行固定次数的迭代,并发dispatch queue可能会提高性能。

int i;  
int count = 10;  
for (i = 0; i < count; i++)   
   printf("%d  ",i);  
 

如果每次迭代执行的任务与其它迭代独立无关,而且循环迭代执行顺序也无关紧要的话,你可以调用dispatch_apply或dispatch_apply_f函数来替换循环。

这两个函数为每次循环迭代将指定的block或函数提交到queue。当dispatch到并发 queue时,就有可能同时执行多个循环迭代。

用dispatch_apply或dispatch_apply_f时你可以指定串行或并发 queue。并发queue允许同时执行多个循环迭代,而串行queue就没太大必要使用了。

下面代码使用dispatch_apply替换了for循环,你传递的block必须包含一个size_t类型的参数,用来标识当前循环迭代。
第一次迭代这个参数值为0,最后一次值为count - 1。

// 获得全局并发queue  
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);  
size_t count = 10;  
dispatch_apply(count, queue, ^(size_t i)   
    printf("%zd ", i);  
);  
// 销毁队列  
dispatch_release(queue);

注意:

1、这些迭代是并发执行的,和普通for循环一样,dispatch_apply和dispatch_apply_f函数也是在所有迭代完成之后才会返回,因此这两个函数会阻塞当前线程。

2、主线程中调用这两个函数必须小心,可能会阻止事件处理循环并无法响应用户事件。
所以如果循环代码需要一定的时间执行,可以考虑在另一个线程中调用这两个函数。

3、如果你传递的参数是串行queue,而且正是执行当前代码的queue,就会产生死锁。


3、在主线程中执行任务

1、GCD提供一个特殊的dispatch queue,可以在应用的主线程中执行任务。

只要应用主线程设置了run loop(由CFRunLoopRef类型或NSRunLoop对象管理),就会自动创建这个queue,并且最后会自动销毁。
非Cocoa应用如果不显式地设置run loop,就必须显式地调用dispatch_main函数来显式地激活这个dispatch queue,
否则虽然你可以添加任务到queue,但任务永远不会被执行。

2、任务中使用Objective-C对象。

GCD支持Cocoa内存管理机制,因此可以在提交到queue的block中自由地使用Objective-C对象。

每个dispatch queue维护自己的autorelease pool确保释放autorelease对象,但是queue不保证这些对象实际释放的时间。

如果应用消耗大量内存,并且创建大量autorelease对象,你需要创建自己的autorelease pool,用来及时地释放不再使用的对象。

4、暂停和继续queue

我们可以使用dispatch_suspend函数暂停一个queue以阻止它执行block对象,使用dispatch_resume函数继续dispatch queue。

调用dispatch_suspend会增加queue的引用计数,调用dispatch_resume则减少queue的引用计数。当引用计数大于0时,queue就保持挂起状态。因此你必须对应地调用suspend和resume函数。

挂起和继续是异步的,而且只在执行block之间(比如在执行一个新的block之前或之后)生效,挂起一个queue不会导致正在执行的block停止。

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

Ios 多线程之NSOperation与NSOprationQueue

iOS多线程编程(四)------ GCD(Grand Central Dispatch)

Ios 多线程之NSOperation与NSOprationQueue

ios多线程 -- GCD介绍

iOS核心笔记——多线程-GCD

iOS开发多线程篇 05 —GCD介绍