您的位置:首页 > 移动开发 > IOS开发

ios 深入浅出多线程(GCD)

2016-03-14 18:20 211 查看
一、前言

相信很多人跟我有不谋而合的想法,对多线程编程存在一定的误解。一个比较直接的说法就是: 新开一个线程,能提高速度,避免阻塞主线程!

二、线程与进程

1、进程

( 1 ) 正在进行中的程序被称为进程,负责程序运行的内存分配

( 2 ) 每一个进程都有自己独立的虚拟内存空间。

例子: 同一台手机运行多个app,每个运行中的app就是一个进程。

2、线程

( 1 ) 线程是进程中一个独立的执行路径(控制单元)

( 2 ) 一个进程中至少包含一条线程,即主线程

( 3 ) 可以将耗时的执行路径(如网络请求)放在其他线程中执行

( 4 ) 线程不能被杀掉,但是可以暂停/休眠一条线程

例子: 运行中的某个app,切换到某一级页面,该页面正在请求数据,请求数据就成为一个(子)线程。

三、什么是多线程?

看到一篇技术软文,作者在谈到多线程的时候,说了这么一句话:”其实,如果不考虑其他任何因素和技术,多线程有百害而无一利,只能浪费时间,降低程序效率。” 刚看到这句话,我懵逼了。后来我觉得还是有一定的道理的。

试想一下,一个任务由十个子任务组成。现在有两种方式完成这个任务:

1、建十个线程,把每个子任务放在对应的线程中执行。执行完一个线程中的任务就切换到另一个线程。

2、把十个任务放在一个线程里,按顺序执行。

操作系统的基础知识告诉我们,线程,是执行程序最基本的单元,它有自己栈和寄存器。说得再具体一些,线程就是“一个CPU执行的一条无分叉的命令列”。

对于第一种方法,在十个线程之间来回切换,就意味着有十组栈和寄存器中的值需要不断地被备份、替换。

而对于对于第二种方法,只有一组寄存器和栈存在,显然效率完胜前者。

四、并发、并行和串行

1、并发

首先,并发是一种现象。他具体指:多个任务同时发生,需要被处理。侧重点在发生!

例如: 很多人排队等待检票

2、并行

同理,并行是一种技术。他具体指:一个同时处理多个任务的技术,一种同时处理多个任务的能力。侧重点在运行时!

例如: 景点开发多了检票窗口,同一时间内能服务多个游客。

3、串行

并行的反义词,表示任务按顺序执行,前一个任务执行完后才能执行下一个。

总结 : 我们经常挂在嘴边的“多线程”,正是采用了并行技术,从而提高了执行效率。因为有多个线程,所以计算机的多个CPU可以同时工作,同时处理不同线程内的指令。并发是一种现象,面对这一现象,我们首先创建多个线程,真正加快程序运行速度的,是并行技术。也就是让多个CPU同时工作。而多线程,是为了让多个CPU同时工作成为可能。

五、同步与异步

1、同步

第一行调用requestData()方法,那么程序运行到第二行的时候,requestData()肯定执行完了。

2、异步

允许在执行某一任务,函数立刻返回,但是要执行的任务稍后完成。

例如: 点击保存一些数据: 数据写到磁盘,更新UI。 对于同步异步而言,需要等待数据保存完成,载去更新UI;对于异步而言,立刻从保存数据方法返回并向后执行代码,真正保存数据的指令稍后执行。

3、区别和联系

假设现在有三个任务需要处理。假设单个CPU处理它们分别需要3、1、1秒。

并行与串行,其实讨论的是处理这三个任务的速度问题。如果三个CPU并行处理,那么一共只需要3秒。相比于串行处理,节约了两秒。

而同步/异步,其实描述的是任务之间先后顺序问题。假设需要三秒的那个是保存数据的任务,而另外两个是UI相关的任务。那么通过异步执行第一个任务,我们省去了三秒钟的卡顿时间。

对于同步执行的三个任务来说,系统倾向于在同一个线程里执行它们。因为即使开了三个线程,也得等他们分别在各自的线程中完成。并不能减少总的处理时间,反而徒增了线程切换(这就是文章开头举的例子)。

对于异步执行的三个任务来说,系统倾向于在三个新的线程里执行他们。因为这样可以最大程度的利用CPU性能,提升程序运行效率。

总结:于是我们可以得出结论,在需要同时处理IO和UI的情况下,真正起作用的是异步,而不是多线程。可以不用多线程(因为处理UI非常快),但不能不用异步(否则的话至少要等IO结束)。

六、关于GCD

1、GCD是一block为基本单位,一个block可以理解为一个任务。

使用block的过程,就是把block放到合适的队列,并选择合适的方法来执行block的过程。

综上,在GCD有两个重要的概念:队列 和 执行方式。

2、队列

( 1 ) 串行队列 : 先进先出,每次只执行一个任务

( 2 ) 并行队列 : 先进先出,可形成多个任务并发

( 3 ) 主队列 : 特殊的串行队列,队列中的任务一定会在主线程中执行。

3、执行方式

( 1 ) 同步执行

( 2 ) 异步执行



综上,我们可以知道,同步不一定在本线程,异步不一定开启新线程。我们在编程的时候,更多的考虑是同步、异步和串行、并行。而不是考虑是否开启新线程。

4、GCD 的死锁问题

下面我们来看一段代码

- (void)viewDidLoad
{

[super viewDidLoad];

NSLog(@"1");

dispatch_sync(dispatch_get_main_queue(), ^{

NSLog(@"2");

});

NSLog(@"3");
}


模拟器运行后的结果: 屏幕是黑屏的。为什么呢?视图还没被渲染。

分析:

dispatch_async(queue,block) async 异步队列,dispatch_async 函数会立即返回, block会在后台异步执行。

dispatch_sync(queue,block) sync 同步队列,dispatch_sync 函数不会立即返回,及阻塞当前线程,等待 block同步执行完成。

在viewDidLoad在主线程中,即在主队列中。当执行到dispatch_sync时候,向dispatch_get_main_queue插入同步thread1。那么问题来了,sync会等到后面的block执行完才返回,然后sync又在dispatch_get_main_queue中,因为主队列是串行队列,sync是后面添加的,前一个是主线程,所以sync要想执行block必须等待主线程完成,主线程等待sync返回,去执行后续内容。在主队列中的两个任务互相等待,照成死锁,sync 等待mainThread 执行完成, mianThread 等待sync 函数返回。

解决方案:

在一般情况下,我们不必要用dispatch_sync,因为dispatch_async能够更好的利用CPU,提高运行效率。只有当我们需要保证队列中的任务要顺序执行的时候,我们才需要用到dispatch_sync。上面只需要将dispatch_sync换成dispatch_async即可。

5、任务组

了解完队列后,我们会有一个正常的想法:怎么去确定我们所有的任务都已经完成了。如果是单个串行队列中,这个不是问题,只要把block添加到队列末尾即可。但是对于并行队列和串并行队列混合呢?这个就需要dispatch_group

// 创建任务组
dispatch_group_t  group =   dispatch_group_create();

//创建全局队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);

dispatch_group_async(group, globalQueue, ^{

for (int i = 0; i < 5; i++) {

NSLog(@"%d",i);
}

});

dispatch_group_async(group, globalQueue, ^{

NSLog(@"5");

});

dispatch_group_notify(group, globalQueue, ^{

NSLog(@"完成");

});


首先我们要通过dispatch_group_create()方法生成一个组。我们把dispatch_async方法换成dispatch_group_async。这个方法多了一个参数,第一个参数填刚刚创建的分组。最后调用dispatch_group_create方法。这个方法表示把第三个参数block传入第二个参数队列中去。而且可以保证第三个参数block执行时,group中的所有任务已经全部完成。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: