概览

  • 进程与线程的概念
  • 多线程的由来
  • 并行与并发
  • 多线程的实现
  • 串行与并行
  • 线程的几种状态
  • 串行队列与并发队列区别
  • iOS 实现多线程的几种方法(NSOperation/GCD
  • GCD线程阻塞的几种情况
  • NSOperationGCD 的关系以及 NSOperation 的使用、实现
  • iOS 中是怎么定义多线程的?或者说什么情况下是多线程的?
  • pThread、NSThread、GCD、NSOperation 之间的关系
  • 串行队列与主队列的区别
  • 全局队列与并发队列的区别
  • 调度组的作用以及使用

进程与线程

进程是运行中的程序,线程是进程的子集,具有单个处理任务的能力,进程通过对线程的包装,通过时间轮转算法间接实现了程序处理任务的并发,进而多线程技术应运而生。

由于计算机程序处理任务的是按照串行处理的方式进行,如果程序以线程为基准去处理一些任务,假如处理的任务比较耗费时间,例如对与 IO 的操作,那么由于串行处理机制,在时间轮转分发到某个进程的时候,恰巧该进程假如正在处理 IO 流耗时操作的话,如果没有线程的存在,那么该进程就会始终处于处理 IO 操作的过程中,程序就会因此而假死,此时都在等待 IO 操作,而 cpu 此时由于没有其他任务处理,所以也是闲置状态,导致整体cpu效率变低。为了保证这些耗时操作能够独立去某个地方去执行而同时又不影响其他任务的正常执行,提出了多线程的处理方案,让程序运行的时候拆分成多个任务,这些任务分别去分发到不同的地方去单独处理,这些单独的地方就是线程,不同线程包装不同的任务,这样就解决了因为耗时操作而导致的cpu闲置,运行效率变低的问题。

多线程技术的实现

多线程是通过时间轮转算法实现的并发

并发与并行的区别

并行是并发的子集,并行从硬件层面上实现了多线程,所谓的硬件就是一些厂商经常宣传的工艺,几核几线程工艺,从硬件上直接实现了并行。并发实现的另一种方式就是多线程技术,是从软件层面上实现的并发。

NSOperation 与 GCD 的关系以及 NSOperation 的使用、实现

首先, NSOperation是一个抽象类,本身不能通过实例去实现它。其次,NSOperation 是对 GCD 的封装,在原有基础上又添加了一些线程的操作(GCD 中没有的 api), 例如取消线程任务判断线程的执行状态以及控制线程的数量等,这些都是 GCD 不对外暴露的,所以一些三方的框架例如 AFNetworkingSDWebImage 等框架都是使用的 NSOperation 进行的封装。如果想精确的对线程进行操作的话,NSOperation 更适合去进行对应的相关操作。

NSOperation 的实例是通过两个子类进行实现的,分别为 NSBlockOperation 、NSInvocationOperation。两者的区别是 NSBlockOperation 是通过 Block 形式添加任务、而 NSInvocationOperation 是通过方法的形式去添加任务的。除此之外,NSBlockOperation start 之后是并发执行添加的任务的。而 NSInvocationOperation 是非并发执行任务的。 一般情况下,我们需要将创建的线程放在 NSOperationQueue 中去自动执行任务,如果不放在 NSOpertionQueue 中,直接通过手动的 Start 方法去执行的话,那么默认的执行操作是在当前线程中执行任务的,如果执行任务,需要当前的线程处于 ready 状态,如果不在改状态去执行任务的话,系统会抛出异常。如果想在子线程中去执行操作,需要手动去将其放在子线程中执行。也可以通过子类继承 NSOperation 重写 main 方法来实现子线程操作。这样的操作都很麻烦,而且很容易出错,除非特别需要,我们一般都使用 NSOperationQueue 进行NSOperation 的管理。

NSOperationQueue 中添加NSOperation 的时候,默认会在子线程自动执行任务(直接执行或者间接通过 GCD 执行)

pThread、NSThread、GCD、NSOperation之间的关系可以用图形的形式表示如下

iOS 中是怎么定义多线程的?或者说什么情况下是多线程的?

当有任意的线程从主线程分离出去的时候,App被认为是多线程的,可以通过 isMultiThread 来判断当前 App是否是多线程状态。只要某个子线程被创建后(不是 NSThread 对象),不需要正在运行,就认为是多线程状态。

并发队列与串行队列的区别

并发队列是派发到队列中的任务并发执行,而串行队列是指派发到队列中的任务顺序执行,派发到队列中的任务执行完成之后才能够继续执行派发的下一个任务。

并发队列可以同时执行多个任务,多个任务的执行受限于当前的派发方式(同步派发与异步派发)。

并发队列同步派发会由于同步原因(阻塞当前线程)会执行同步任务直到其完成才能够执行下一条分发的任务。每次只能够执行单一的任务(同步造成当前队列中只有一个任务存在,尽管是并发队列,每次也只能够执行一个任务)。

而并发队列异步派发是同时派发了多个任务,而派发的任务因为是异步派发的,所以同时执行的任务是多个(队列中异步派发了多个任务,所以并行队列能够同时执行多个任务,此时需要新开多个线程处理任务)。

注意: 串行队列,每次只执行一个任务,直到当前的任务执行完成之后,再去执行下一个任务。 并发队列,每次执行多个任务,所以多个任务的执行完成顺序不能够确定(通过开启多个线程实现的)。

任务派发与不同队列执行的情况

下面的表格总结的很详细

它们的区别可以总结如下:

  • 同步派发:当前任务不执行完成,不会执行下一条任务
  • 异步派发:当前任务执行过程中,同样可以执行下一条任务
  • 串行队列:必须等待第一个任务执行完成以后,再去调度另一个任务
  • 并发队列:同时调度多个任务,至于这些任务通过几个线程去执行是由 GCD 管理
  • 主队列:全局串行队列,由主线程串行调度,并且有且只有一个
  • 全局队列:没有名称的并发队列

结合例子:

串行队列同步任务

/**串行队列 同步任务//串行队列同步分发任务,阻塞当前的线程,不需要开启新的线程去执行//受限于串行队列与同步分发的特点,同步分发的任务如果当前的任务不执行完那么就不会去执行下一条任务//串行队列则是每次执行分发一条的任务,当前任务完成之后才能够继续执行下一条任务*/
- (void)serialSyncTask{dispatch_queue_t queue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);for (int i = 0; i<4; i++) {dispatch_sync(queue, ^{NSLog(@"串行队列同步分发任务-Task(%d),currentThead:%@",i,[NSThread currentThread]);});NSLog(@"分发 Task(%d) 完成",i);}}复制代码

串行队列异步任务

/**串行队列 异步任务//串行队列异步分发任务,不阻塞当前的线程,所以队列中同一时间分发了三个任务,按照先进先出原则,//任务按顺序开始,由于是串行队列,在执行某个任务的时候,是不会去调度执行其他任务的,所以此时依次//按照队列中的任务执行完成操作。//主队列异步分发任务,不阻塞队列中其他任务的执行*/
- (void)serialAsyncTask{/******************************串行队列异步分发任务*********************************************/dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);for(int i = 0; i<4; i++){dispatch_async(serialQueue, ^{sleep(i+1);NSLog(@"串行队列异步分发任务-Task(%d),currentThead:%@",i,[NSThread currentThread]);});NSLog(@"分发 Task(%d) 完成",i);}NSLog(@"执行结束");}复制代码

运行结果如下:

并发队列同步任务

/**并发队列 同步任务//阻塞当前线程//并行队列同一时将任务分发到队列中,同步执行需要当前的任务完成之后才能继续执行其他的任务,是按照顺序进行的,所以此时不会新建线程去处理任务*/
- (void)concurrentSynac{dispatch_queue_t queue = dispatch_queue_create("concorrentQueue", DISPATCH_QUEUE_CONCURRENT);for (int i = 0; i<4; i++) {dispatch_sync(queue, ^{sleep(1);NSLog(@"并发队列同步分发任务-Task(%d),currentThead:%@",i,[NSThread currentThread]);});NSLog(@"分发 Task(%d) 完成",i);}}复制代码

并发队列 异步任务

/**并发队列 异步任务不阻塞当前线程由于队列中异步添加了多个任务,并发队列同一时间能够执行多个任务,所以需要新建多个线程去处理队列中的任务*/
- (void)concurrentAsynac{dispatch_queue_t queue = dispatch_queue_create("concorrentQueue", DISPATCH_QUEUE_CONCURRENT);for (int i = 0; i<4; i++) {dispatch_async(queue, ^{sleep(1);NSLog(@"并发队列异步分发任务-Task(%d),currentThead:%@",i,[NSThread currentThread]);});NSLog(@"分发 Task(%d) 完成",i);}}复制代码

死锁的发生

如果当前队列中 task0正在执行操作,此时如果同步分发任务task1,同步分发阻塞当前线程,从而会执行当前的任务直到当前分配的任务结束,但是,因为是串行队列,串行队列中每次只能执行一个任务,由于 task0没有执行完成,所以 task1需要等待 task0执行完成才能够执行。由于task1要想执行需要等到task0执行完成,而 task0中由于同步分发了task1,需要等到 task1执行完成才能够继续完成当前的任务,两者相互等待对方任务执行完成,最终造成死锁。

相同的情况,主队列同步任务也会造成死锁,具体原因也很类似。

原因:主队列是全局串行队列,如果同步执行任务的话,由于主队列当前的任务没有完成此时是不会调度当前分发的任务的,而此时分发的任务又是同步的,同步分发的任务有个特点就是阻塞当前线程,执行当前的任务知道结束。所以由于上一个任务始终由于分发导致其完成不了,分发的任务又一直在等待其完成,两者造成了一个死循环,不断在等待对方完成,却永远都完成不了,导致死锁的发生。

简洁的说:

  • 主队列:如果主线程正在执行代码,就不调度任务
  • 同步任务:如果队列中前一个任务没有执行完成,就继续等待上一个任务完成再去执行下一个任务
  • 两者相互等待造成死锁

全局队列与并发队列的区别

  • 全局队列是没有队列名字的,并发队列是有名字的。而有名字的队列是可以跟踪到的
  • 一般使用全局队列
  • MRC 中需要手动管理内存,并发队列是通过 creat 出来的,在 MRC 中见到creat就需要 release 操作,而全局队列不需要 release 操作,全局队列有且只有一个

同步任务的用途

一般一些耗时操作会放在一个线程中执行,而当前的这个操作可能需要一些『依赖』关系的操作之后才能够执行,这时候就需要同步的操作了。比如一个界面的展示需要两个接口数据的返回才能够正常展示,那么此时需要同步两个接口的数据,然后最后才去展示数据内容,对于前两个接口的请求数据我们需要将其操作通过同步分发的操作保证每一步执行完成之后在进行下一步操作。如下代码:

- (void)syncThreadUse{//全局并发队列 异步调度派发dispatch_async(dispatch_get_global_queue(0, 0), ^{//并发队列,同步派发dispatch_sync(dispatch_get_global_queue(0, 0), ^{sleep(3);NSLog(@"请求接口一,currentThread:%@",[NSThread currentThread]);});//并发队列,同步派发dispatch_sync(dispatch_get_global_queue(0, 0), ^{sleep(1);NSLog(@"请求接口二,currentThread:%@",[NSThread currentThread]);});//并发队列,异步派发dispatch_async(dispatch_get_global_queue(0, 0), ^{NSLog(@"异步派发,currentThread:%@",[NSThread currentThread]);/*注意:此时用dispatch_sync 与 dispatch_async效果一样dispatch_sync并不造成死锁,因为造成死锁的原因是队列中分发的任务与当前队列中执行的任务相互等待造成的死锁此时任务的分发由于不在dispatch_get_main_queue()主线程队列中,所以并没有让分发的任务等待主线程正在执行任务结束的情况存在,依据主线程队列的执行任务的特点在主线程已经执行完成任务之后才会去执行当前的任务,来执行当前任务dispatch_sync(dispatch_get_main_queue(), ^{NSLog(@"展示界面");});*///如果对死锁不清楚的话,建议使用dispatch_async方式进行任务的派发,从而避免死锁的发生dispatch_async(dispatch_get_main_queue(), ^{NSLog(@"展示界面");});});});
}复制代码

结果如下:

调度组的作用以及使用

Dispatch Group 调度组

  • Dispatch Group 在添加到组里面所有的任务完成的时候发出通知,这些任务可以是同步的,也可以是异步的,哪怕是不同的队列。
  • Dispatch Group只有异步执行
  • 创建的 Dispatch Group,像是一个未完成的计数器
  • dispatch_group_enter 手动通知 Dispatch Group 任务已经开始。它与 dispatch_group_leave 成对出现
  • Dispatch Group 可以包含不同的队列类型,包括:自定义串行队列、主队列(全局串行队列)、并发队列

Dispatch Group 的几个函数

  • dispatch_group_wait 会阻塞当前的线程,知道组里面所有的任务都完成或者等到某个超时完成。 如果所在的任务完成前超时了,该函数会返回一个非零值。可以对此返回值做条件判断来确定是否超出了等待周期。 DISPATCH_TIME_FOREVER 让这个组永远等待。
  • dispatch_apply 类似于 for 循环,能够并发的执行不同的迭代。 这个函数是同步执行的,只会在所有工作完成之后才能返回。 对于并发循环使用 dispatch_apply 可以帮助我们追踪任务的进度
  • dispatch_group_notify 异步方式进行 不会阻塞任何线程 有时候需要在多个异步任务都执行完成之后后续做一些操作

调度组的使用:

- (void)dispatchGroup{dispatch_group_t group = dispatch_group_create();dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{sleep(6);NSLog(@"请求接口1");});dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{NSLog(@"请求接口2");});dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{NSLog(@"请求接口3");});dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"请求接口全部完成");});}复制代码

参考文章: iOS中的多线程技术 多线程之GCD

iOS 多线程技术总结相关推荐

  1. java游戏 动态录入弹球_动态弹球的实现 加入了多线程技术--javaSE游戏准备工作...

    任务描述:实现了动态弹球的功能,对于有弹球功能的SE游戏奠定了基础. package 运用线程技术的小球; import java.awt.*; import java.awt.event.*; im ...

  2. iOS 多线程的四种技术方案

    iOS 多线程的四种技术方案 image pthread 实现多线程操作 代码实现: void * run(void *param) {for (NSInteger i = 0; i < 100 ...

  3. iOS多线程开发之GCD(基础篇)

    总纲: GCD基本概念 GCD如何实现 GCD如何使用 队列和任务组合 一.GCD基本概念 GCD 全称Grand Central Dispatch(大中枢队列调度),是一套低层API,提供了⼀种新的 ...

  4. iOS多线程编程之多线程简单介绍(转载)

    一.进程和线程 1.什么是进程 进程是指在系统中正在运行的一个应用程序 每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内 比如同时打开QQ.Xcode,系统就会分别启动2个进程 通过& ...

  5. iOS:多线程的详细介绍

    多线程: 一.概念 1.什么是进程?     程序的一次性执行就是进程.进程占独立的内存空间.   2.什么是线程?     进程中的代码的执行路径.   3.进程与线程之间的关系?      每个进 ...

  6. iOS 多线程:『GCD』详尽总结

    原文链接:www.jianshu.com/p/2d57c7201- 感谢大家对这篇文章的喜欢和支持.为了不辜负大家的喜欢,也为了更好的让大家了解 iOS 多线程,以及 GCD 的相关知识,我对这篇文章 ...

  7. ios多线程Android,iOS 关于多线程

    一.进程和线程 1.什么是进程 进程是指在系统中正在运行的一个应用程序 每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内 比如:同时打开QQ,Xcode,系统就会分别启动2个进程 通过 ...

  8. 多线程 循环 锁_大多数人还不清楚的iOS多线程

    你不知道的的 iOS 多线程 程序员用有限的生命去追求无限的知识. 有言在先 首先我不是故意要做标题党的,也不是我要炒冷饭,我只是想换个姿势看多线程,本文大部分内容在分析如何造死锁,奈何功力尚浅,然而 ...

  9. 干货!总结19个提升iOS开发技术的必看教程!

    2019独角兽企业重金招聘Python工程师标准>>> 又到了ibnShawari一周一篇技术推送的时间了,今天我为大家带来了iOS开发篇,绝对实用,绝对简单!!! iOS开发经典路 ...

最新文章

  1. 生死讯息:密码背后的数学
  2. 程序员要避免的五种程序注释方式
  3. Ispur服务器收集系统日志,centos 7.2往rsyslog服务器端发送系统日志
  4. error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏的解决方案
  5. kfold_机器学习gridsearchcv(网格搜索)和kfold validation(k折验证)
  6. SourceTree安装破姐添加SSH KEY以及拉取代码教程(附资源下载)
  7. Mysql字符集之utf8和utf8mb4的使用问题
  8. python中messagebox用法实例_pyqt4教程之messagebox使用示例分享
  9. LVM--逻辑卷管理
  10. 零基础 Amazon Web Services (AWS) 入门教程图文版(三)
  11. linux中进程的用户管理
  12. 英语国家的学生学语法么?_纪念国家语法日
  13. 错误笔记:在OleDb执行下Access ,程序不报错,但是Update也更新不成功的
  14. 我插计算机英语,帮我翻译以下计算机英语的句子
  15. Java基础-零拷贝技术应用案例
  16. c++ 函数指针和指针函数
  17. 编址与存储相关计算(一)——软考之路
  18. 量化投资学习——行业轮动规律
  19. 复选框点击后弹出输入框
  20. java实现QQ空间日志列表获取

热门文章

  1. android 事件传递
  2. Android开发--真机调试出现device offline提示
  3. Qtum量子链AUR开发工具包即日上线
  4. React路由 + 绝对路径引用
  5. HDU 2047 阿牛的EOF牛肉串
  6. XHTML 相对路径与绝对路径
  7. 互联网产品经理应该具备的技能(需求篇)
  8. 【TweenMax】实例TimelineMax
  9. 数据库连接jdbc理解
  10. 教你怎么上传本地代码到github