在项目中,我们必不可少的会遇到多线程的处理问题,在我最近的项目中,我也踩到了多线程这个坑,在此总结一下自己的经验。
在iOS中,和线程相关的有四种:
- Pthreads
- NSThread
- GCD
- NSOperation & NSOperationQueue
但是我们日常开发任务中,最常用到的就是NSThread和GCD。
而我对于NSThread的理解就是在于用
[NSThread currentThread];
通过这个来查看当前线程。- - !
来谈谈今天的重点GCD。
GCD = Grand Central Dispatch
听到这个名字就感觉很霸气,感谢苹果给我们封装了一个这么好的多线程处理框架,它是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们管理,我们只需要告诉干什么就行。同时它使用的也是 C语言,不过由于使用了 Block(Swift里叫做闭包),使得使用起来更加方便,而且灵活。所以基本上大家都使用 GCD 这套方案。
在 GCD 中,加入了两个非常重要的概念: 任务 和 队列。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"async queue %@", [NSThread currentThread]);
});
上面这段代码就是向全局队列(global)异步(async)添加了一个任务(block内的代码)。
然而你会发现,这里并没有出现线程,因为GCD并不会让我们直接去面对线程,而是直接管理线程的调度者---队列。
我简单的阐述下线程、队列、任务三者的关系。
线程就是工地上的工人,他负责做事情,处理我们的任务。
队列就工头,他负责安排哪个人来处理我们的任务。
至于任务,这个东西你说啥,那他就是啥。
搞清楚线程、队列、任务的关系之后,我们讨论一下线程死锁。
死锁的规范定义:集合中的每一个进程都在等待只能由本集合中的其他进程才能引发的事件,那么该组进程是死锁的。
讲道理,这样的官方说法,看起来太费脑,talk is cheap,show me the code.
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"sync queue %@", [NSThread currentThread]);
});
这就是一个很经典的线程死锁,经典到任何一个用GCD的人都不会犯这个错误。
现在来分析一下,为什么这段代码会造成线程死锁。
这段代码执行在主线程,但是执行到这的时候,他向主线程同步提交了一个任务,主线程是串行执行,也就是按顺序一个一个执行当前任务。
然而同步是会阻塞当前线程,而且不会开辟新的线程。
这就会导致一个情况,主线程被卡死。而且是一直卡死。主线程卡死就会导致UI不能刷新,不能响应用户的点击事件。用户的体验就是:草,死机了!
造成上述死锁有三个条件:
- 同步提交
- 串行执行
- 上下文队列和目标队列是同一个队列
总结
dispatch_sync执行的上下文环境所处的队列,如果跟提交到的目标队列是同一个队列,别管这个队列是main队列还是你手动创建的,都会死锁(仅限于串行)。
多线程分析的要点是:1、上下文环境所处的队列 2、执行同步或者异步操作所提交到的目标队列。掌握这两点,是必须的。
如果向一个并行队列同步提交一个任务,相当于没写,此任务所在线程就是当前线程。
同步和异步的区别:是否会阻塞当前线程,如果阻塞当前线程,则是同步。反之则为异步。