关于objective-c:iOS开发面试只需知道这些技术基本通关多线程篇

46次阅读

共计 7876 个字符,预计需要花费 20 分钟才能阅读完成。

一、过程、线程

过程:

· 1. 过程是一个具备肯定独立性能的程序对于某次数据汇合的一次运行流动,它是操作系统分配资源的根本单元.

· 2. 过程是指在零碎中正在运行的一个应用程序,就是一段程序的执行过程, 咱们能够了解为手机上的一个 app.

· 3. 每个过程之间是独立的,每个过程均运行在其专用且受爱护的内存空间内,领有独立运行所需的全副资源

线程

· 1. 程序执行流的最小单元,线程是过程中的一个实体.

· 2. 一个过程要想执行工作, 必须至多有一条线程. 应用程序启动的时候,零碎会默认开启一条线程, 也就是主线程

过程和线程的关系

· 1. 线程是过程的执行单元,过程的所有工作都在线程中执行

· 2. 线程是 CPU 分配资源和调度的最小单位

· 3. 一个程序能够对应多个过程(多过程), 一个过程中可有多个线程, 但至多要有一条线程

· 4. 同一个过程内的线程共享过程资源

二、多过程、多线程

多过程

关上 mac 的流动监视器,能够看到很多个过程同时运行

· 过程是程序在计算机上的一次执行流动。当你运行一个程序,你就启动了一个过程。显然,程序是死的(动态的),过程是活的(动静的)。

· 过程能够分为零碎过程和用户过程。但凡用于实现操作系统的各种性能的过程就是零碎过程,它们就是处于运行状态下的操作系统自身; 所有由用户启动的过程都是用户过程。过程是操作系统进行资源分配的单位。

· 过程又被细化为线程,也就是一个过程下有多个能独立运行的更小的单位。在同一个工夫里,同一个计算机系统中如果容许两个或两个以上的过程处于运行状态,这便是多过程。

多线程

1. 同一时间,CPU 只能解决 1 条线程,只有 1 条线程在执行。多线程并发执行,其实是 CPU 疾速地在多条线程之间调度(切换)。如果 CPU 调度线程的工夫足够快,就造成了多线程并发执行的假象

2. 如果线程十分十分多,CPU 会在 N 多线程之间调度,耗费大量的 CPU 资源,每条线程被调度执行的频次会升高(线程的执行效率升高)

3. 多线程的长处:

能适当进步程序的执行效率能适当进步资源利用率(CPU、内存利用率)

  1.  多线程的毛病:

开启线程须要占用肯定的内存空间(默认状况下,主线程占用 1M,子线程占用 512KB),如果开启大量的线程,会占用大量的内存空间,升高程序的性能线程越多,CPU 在调度线程上的开销就越大

程序设计更加简单:比方线程之间的通信、多线程的数据共享

三、工作、队列

首先作为一个开发者,有一个学习的气氛跟一个交换圈子特地重要,这是一个我的 iOS 开发公众号:编程大鑫,不论你是小白还是大牛都欢送入驻,让咱们一起提高,独特倒退!(群内会收费提供一些群主珍藏的收费学习书籍材料以及整顿好的几百道面试题和答案文档!)

工作

就是执行操作的意思,也就是在线程中执行的那段代码。在 GCD 中是放在 block 中的。执行工作有两种形式:同步执行(sync)和异步执行(async

同步 (Sync):同步增加工作到指定的队列中,在增加的工作执行完结之前,会始终期待,直到队列外面的工作实现之后再继续执行,即会阻塞线程。只能在以后线程中执行工作(是以后线程,不肯定是主线程),
不具备开启新线程的能力。

异步(Async):线程会立刻返回,无需期待就会继续执行上面的工作,不阻塞以后线程。能够在新的线程中执行工作,具备开启新线程的能力(并不一定开启新线程)。如果不是增加到主队列上,异步会在子线程中执行工作

队列

队列(Dispatch Queue):这里的队列指执行工作的期待队列,即用来寄存工作的队列。队列是一种非凡的线性表,采纳 FIFO(先进先出)的准则,即新工作总是被插入到队列的开端,而读取工作的时候总是从队列的头部开始读取。每读取一个工作,则从队列中开释一个工作在 GCD 中有两种队列:串行队列和并发队列。两者都合乎 FIFO(先进先出)的准则。两者的次要区别是:执行程序不同,以及开启线程数不同。

l 串行队列(Serial Dispatch Queue):

同一时间内,队列中只能执行一个工作,只有以后的工作执行实现之后,能力执行下一个工作。(只开启一个线程,一个工作执行结束后,再执行下一个工作)。主队列是主线程上的一个串行队列, 是零碎主动为咱们创立的

l 并发队列(Concurrent Dispatch Queue):

同时容许多个工作并发执行。(能够开启多个线程,并且同时执行工作)。并发队列的并发性能只有在异步(dispatch_async)函数下才无效

四、iOS 中的多线程

次要有三种:NSThread、NSoperationQueue、GCD

1. NSThread:轻量级别的多线程技术

是咱们本人手动开拓的子线程,如果应用的是初始化形式就须要咱们本人启动,如果应用的是结构器形式它就会主动启动。只有是咱们手动开拓的线程,都须要咱们本人治理该线程,不只是启动,还有该线程应用结束后的资源回收

performSelector… 只有是 NSObject 的子类或者对象都能够通过调用办法进入子线程和主线程,其实这些办法所开拓的子线程也是 NSThread 的另一种体现形式。

在编译阶段并不会去查看办法是否无效存在,如果不存在只会给出正告

须要留神的是:如果是带 afterDelay 的延时函数,会在外部创立一个 NSTimer,而后增加到以后线程的

Runloop 中。也就是如果以后线程没有开启 runloop,该办法会生效。在子线程中,须要启动 runloop(注

意调用程序)

performSelector:withObject : 只是一个单纯的音讯发送,和工夫没有一点关系。所以不须要增加到子

线程的 Runloop 中也能执行

2、GCD 比照 NSOprationQueue

咱们要明确 NSOperationQueue GCD 之间的关系

GCD 是面向底层的 C 语言的 API NSOpertaionQueue GCD 构建封装的,是 GCD 的高级形象。

1、GCD 执行效率更高,而且因为队列中执行的是由 block 形成的工作,这是一个轻量级的数据结构,写起来更不便

2、GCD 只反对 FIFO 的队列,而 NSOperationQueue 能够通过设置最大并发数,设置优先级,增加依赖关系等调整执行程序

3、NSOperationQueue 甚至能够跨队列设置依赖关系,然而 GCD 只能通过设置串行队列,或者在队列内增加 barrier(dispatch_barrier_async) 工作,能力管制执行程序, 较为简单

4、NSOperationQueue 因为面向对象,所以反对 KVO ,能够监测 operation 是否正在执行(isExecuted )、是否完结(isFinished )、是否勾销(isCanceld

l 理论我的项目开发中,很多时候只是会用到异步操作,不会有特地简单的线程关系治理,所以苹果推崇的且优化欠缺、运行疾速的 GCD  是首选

l 如果思考异步操作之间的事务性,程序行,依赖关系,比方多线程并发下载,GCD 须要本人写更多的代码来实现,而 NSOperationQueue  曾经内建了这些反对

l 不论是 GCD 还是 NSOperationQueue ,咱们接触的都是工作和队列,都没有间接接触到线程,事实上线程治理也确实不须要咱们操心,零碎对于线程的创立,调度治理和开释都做得很好。而 NSThread  须要咱们本人去治理线程的生命周期,还要思考线程同步、加锁问题,造成一些性能上的开销

五、GCD— 队列

iOS 中,有  GCD NSOperation NSThread 等几种多线程技术计划。

GCD  共有三种队列类型:

main queue :通过  dispatch_get_main_queue ()取得,这是一个与主线程相干的串行队列。

global queue :全局队列是并发队列,由整个过程共享。存在着高、中、低三种优先级的全局队列。调用 dispath_get_global_queue 并传入优先级来拜访队列。

自定义队列:通过函数 dispatch_queue_create  创立的队列

六、死锁

死锁就是队列引起的循环期待

1、一个比拟常见的死锁例子: 主队列同步

在主线程中使用主队列同步,也就是把工作放到了主线程的队列中。

同步对于工作是立即执行的,那么当把工作放进主队列时,它就会立马执行, 只有执行完这个工作,viewDidLoad 才会持续向下执行。

viewDidLoad 和工作都是在主队列上的,因为队列的先进先出准则,工作又需期待 viewDidLoad 执行结束后能力继续执行,viewDidLoad 和这个工作就造成了互相循环期待,就造成了死锁。

想防止这种死锁,能够将同步改成异步 dispatch_async , 或者将 dispatch_get_main_queue 换成其余串行或并行队列,都能够解决。

2、同样,下边的代码也会造成死锁:

里面的函数无论是同步还是异步都会造成死锁。

这是因为外面的工作和里面的工作都在同一个 serialQueue 队列内,又是同步,这就和上边主队列同步的例子一样造成了死锁解决办法也和上边一样,将外面的同步改成异步 dispatch_async , 或者将 serialQueue 换成其余串行或并行队列,都能够解决

这样是不会死锁的, 并且 serialQueue serialQueue2 是在同一个线程中的。

七、GCD 工作执行程序

1、串行队列先异步后同步

打印程序是 13245

起因是:

首先先打印 1

接下来将工作 2 其增加至串行队列上,因为工作 2 是异步,不会阻塞线程,持续向下执行,打印 3

而后是工作 4, 将工作 4 增加至串行队列上,因为工作 4 和工作 2 在同一串行队列,依据队列先进先出准则,工作 4 必须等工作 2 执行后能力执行,又因为工作 4 是同步工作,会阻塞线程,只有执行完工作 4 能力持续向下执行打印 5

所以最终程序就是 13245。

这里的工作 4 在主线程中执行,而工作 2 在子线程中执行。

如果工作 4 是增加到另一个串行队列或者并行队列,则工作 2 和工作 4 无序执行(能够增加多个工作看成果)

2、performSelector

这里的 test 办法是不会去执行的,起因在于

这个办法要创立提交工作到 runloop 上的,而 gcd 底层创立的线程是默认没有开启对应 runloop 的,所有这个办法就会生效。

而如果将 dispatch_get_global_queue 改成主队列,因为主队列所在的主线程是默认开启了 runloop 的,就会去执行 (将 dispatch_async 改成同步,因为同步是在以后线程执行,那么如果以后线程是主线程,test

办法也是会去执行的)。

八、dispatch_barrier_async

1、问:怎么用 GCD 实现多读单写?

多读单写的意思就是:能够多个读者同时读取数据,而在读的时候,不能取写入数据。并且,在写的过程中,不能有其余写者去写。即读者之间是并发的,写者与读者或其余写者是互斥的。

这里的写解决就是通过栅栏的模式去写。

就能够用 dispatch_barrier_sync(栅栏函数)去实现

2、dispatch_barrier_sync 的用法:

这里的 dispatch_barrier_sync 上的队列要和须要阻塞的工作在同一队列上,否则是有效的。

从打印上看,工作 0-9 和工作工作 10-19 因为是异步并发的起因,彼此是无序的。而因为栅栏函数的存在,导致程序必然是先执行工作 0-9,再执行栅栏函数,再去执行工作 10-19。

l dispatch_barrier_sync: Submits a barrier block object for execution and waits until that block completes.(提交一个栅栏函数在执行中, 它会期待栅栏函数执行完)

l dispatch_barrier_async: Submits a barrier block for asynchronous execution and returns immediately.(提交一个栅栏函数在异步执行中, 它会立马返回)

dispatch_barrier_sync dispatch_barrier_async 的区别也就在于会不会阻塞以后线程比方,上述代码如果在 dispatch_barrier_async 后轻易加一条打印,则会先去执行该打印,再去执行工作 0-9 和栅栏函数;而如果是 dispatch_barrier_sync,则会在工作 0-9 和栅栏函数后去执行这条打印。

3、则能够这样设计多读单写:

九、dispatch_group_async

场景:在 n 个耗时并发工作都实现后,再去执行接下来的工作。比方,在 n 个网络申请实现后去刷新 UI 页

面。

十、Dispatch Semaphore

GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号。

Dispatch Semaphore 提供了三个函数

1.dispatch_semaphore_create:创立一个 Semaphore 并初始化信号的总量
2.dispatch_semaphore_signal:发送一个信号,让信号总量加 1
3.dispatch_semaphore_wait:能够使总信号量减 1,当信号总量为 0 时就会始终期待(阻塞所在线程),否则就能够失常执行。

Dispatch Semaphore 在理论开发中次要用于:

l 放弃线程同步,将异步执行工作转换为同步执行工作

l 保障线程平安,为线程加锁

1、放弃线程同步:

dispatch_semaphore_wait 加锁阻塞了以后线程,dispatch_semaphore_signal 解锁后以后线程继续执行

2、保障线程平安,为线程加锁:

在线程平安中能够将 dispatch_semaphore_wait 看作加锁,而 dispatch_semaphore_signal 看作解锁首先创立全局变量

留神到这里的初始化信号量是 1。

异步并发调用 asyncTask

而后发现打印是从工作 1 程序执行到 100,没有产生两个工作同时执行的状况。

起因如下:

在子线程中并发执行 asyncTask,那么第一个增加到并发队列里的,会将信号量减 1,此时信号量等于 0,

能够执行接下来的工作。而并发队列中其余工作,因为此时信号量不等于 0,必须等以后正在执行的工作执行结束后调用 dispatch_semaphore_signal将信号量加 1,才能够继续执行接下来的工作,以此类推,从而达到线程加锁的目标。

十一、延时函数(dispatch_after)

dispatch_after 能让咱们增加进队列的工作延时执行,该函数并不是在指定工夫后执行解决,而只是在指定工夫追加解决到 dispatch_queue

因为其外部应用的是 dispatch_time_t 治理工夫,而不是 NSTimer

所以如果在子线程中调用,相比 performSelector:afterDelay, 不必关怀 runloop 是否开启

十二、应用 dispatch_once 实现单例

十三、NSOperationQueue 的长处

NSOperationNSOperationQueue 是苹果提供给咱们的一套多线程解决方案。实际上 NSOperationNSOperationQueue 是基于GCD 更高一层的封装,齐全面向对象。然而比GCD 更简略易用、代码可读性也更高。

1、能够增加工作依赖,不便管制执行程序

2、能够设定操作执行的优先级

3、工作执行状态管制:isReady,isExecuting,isFinished,isCancelled

如果只是重写 NSOperationmain办法,由底层管制变更工作执行及实现状态,以及工作退出如果重写了 NSOperationstart 办法,自行管制工作状态

零碎通过 KVO 的形式移除 isFinished==YESNSOperation

4、能够设置最大并发量

十四、NSOperation 和 NSOperationQueue

操作(Operation):

执行操作的意思,换句话说就是你在线程中执行的那段代码。

GCD 中是放在 block 中的。在 NSOperation 中,应用 NSOperation 子类 NSInvocationOperationNSBlockOperation,或者自定义子类来封装操作。

操作队列(Operation Queues):

这里的队列指操作队列,即用来寄存操作的队列。不同于 GCD 中的调度队列 FIFO(先进先出)的准则。

NSOperationQueue 对于增加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖

关系),而后进入就绪状态的操作的开始执行程序(非完结执行程序)由操作之间绝对的优先级决定(优先级是操作对象本身的属性)。

操作队列通过设置最大并发操作数(maxConcurrentOperationCount)来管制并发、串行。

NSOperationQueue 为咱们提供了两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,

而自定义队列在后盾执行。

十五、NSThread+runloop 实现常驻线程

NSThread 在理论开发中比拟罕用到的场景就是去实现常驻线程。

因为每次开拓子线程都会耗费 cpu,在须要频繁应用子线程的状况下,频繁开拓子线程会耗费大量的cpu,而且创立线程都是工作执行实现之后也就开释了,不能再次利用,那么如何创立一个线程能够让它能够再次工作呢?也就是创立一个常驻线程。

首先常驻线程既然是常驻,那么咱们能够用 GCD 实现一个单例来保留 NSThread

这样创立的 thread 就不会销毁了吗?

并没有打印,阐明 test 办法没有被调用。那么能够用 runloop 来让线程常驻

这时候再去调用 performSelector 就有打印了。

十六、自旋锁与互斥锁

自旋锁:

是一种用于爱护多线程共享资源的锁,与个别互斥锁(mutex)不同之处在于当自旋锁尝试获取锁时以忙期待(busy waiting)的模式一直地循环查看锁是否可用。当上一个线程的工作没有执行结束的时候(被锁住),那么下一个线程会始终期待(不会睡眠),当上一个线程的工作执行结束,下一个线程会立刻执行。

在多 CPU 的环境中,对持有锁较短的程序来说,应用自旋锁代替个别的互斥锁往往可能进步程序的性能。

互斥锁:

当上一个线程的工作没有执行结束的时候(被锁住),那么下一个线程会进入睡眠状态期待工作执行结束,当上一个线程的工作执行结束,下一个线程会主动唤醒而后执行工作。

总结:

自旋锁会忙等: 所谓忙等,即在拜访被锁资源时,调用者线程不会休眠,而是不停循环在那里,直到被锁资源开释锁。

互斥锁会休眠: 所谓休眠,即在拜访被锁资源时,调用者线程会休眠,此时 cpu 能够调度其余线程工作。直到被锁资源开释锁。此时会唤醒休眠线程。

优缺点:

自旋锁的长处在于,因为自旋锁不会引起调用者睡眠,所以不会进行线程调度,CPU 工夫片轮转等耗时操作。所有如果能在很短的工夫内取得锁,自旋锁的效率远高于互斥锁。

毛病在于,自旋锁始终占用 CPU,他在未取得锁的状况下,始终运行--自旋,所以占用着 CPU,如果不能在很短的时 间内取得锁,这无疑会使 CPU 效率升高。自旋锁不能实现递归调用。

自旋锁atomicOSSpinLockdispatch_semaphore_t

互斥锁pthread_mutex@ synchronizedNSLockNSConditionLockNSConditionNSRecursiveLock

好了,以上就是本次内容,感激观看!

正文完
 0