进程:面试
· 1.进程是一个具备必定独立功能的程序关于某次数据集合的一次运行活动,它是操做系统分配资源的基本单元.编程
· 2.进程是指在系统中正在运行的一个应用程序,就是一段程序的执行过程,咱们能够理解为手机上的一个 app
.安全
· 3.每一个进程之间是独立的,每一个进程均运行在其专用且受保护的内存空间内,拥有独立运行所需的所有资源markdown
线程网络
· 1.程序执行流的最小单元,线程是进程中的一个实体.数据结构
· 2.一个进程要想执行任务,必须至少有一条线程.应用程序启动的时候,系统会默认开启一条线程,也就是主线程多线程
进程和线程的关系并发
· 1.线程是进程的执行单元,进程的全部任务都在线程中执行app
· 2.线程是CPU 分配资源和调度的最小单位异步
· 3.一个程序能够对应多个进程(多进程),一个进程中可有多个线程,但至少要有一条线程
· 4.同一个进程内的线程共享进程资源
多进程
打开 mac
的活动监视器,能够看到不少个进程同时运行
· 进程是程序在计算机上的一次执行活动。当你运行一个程序,你就启动了一个进程。显然,程序是死的(静态的),进程是活的(动态的)。
· 进程能够分为系统进程和用户进程。凡是用于完成操做系统的各类功能的进程就是系统进程,它们就是处于运行状态下的操做系统自己;全部由用户启动的进程都是用户进程。进程是操做系统进行资源分配的单位。
· 进程又被细化为线程,也就是一个进程下有多个能独立运行的更小的单位。在同一个时间里,同一个计算机系统中若是容许两个或两个以上的进程处于运行状态,这即是多进程。
多线程
1. 同一时间,CPU
只能处理 1 条线程,只有 1 条线程在执行。多线程并发执行,实际上是 CPU
快速地在多条线程之间调度(切换)。若是 CPU
调度线程的时间足够快,就形成了多线程并发执行的假象
2. 若是线程很是很是多,CPU
会在 N 多线程之间调度,消耗大量的 CPU
资源,每条线程被调度执行的频次会下降(线程的执行效率下降)
3. 多线程的优势:
能适当提升程序的执行效率能适当提升资源利用率(CPU
、内存利用率)
开启线程须要占用必定的内存空间(默认状况下,主线程占用 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
)函数下才有效
主要有三种:NSThread、NSoperationQueue、GCD
1. NSThread:轻量级别的多线程技术
是咱们本身手动开辟的子线程,若是使用的是初始化方式就须要咱们本身启动,若是使用的是构造器方式它就会自动启动。只要是咱们手动开辟的线程,都须要咱们本身管理该线程,不仅是启动,还有该线程使用完毕后的资源回收
performSelector
...只要是 NSObject
的子类或者对象均可以经过调用方法进入子线程和主线程,其实这些方法所开辟的子线程也是 NSThread
的另外一种体现方式。
在编译阶段并不会去检查方法是否有效存在,若是不存在只会给出警告
须要注意的是:若是是带afterDelay
的延时函数,会在内部建立一个 NSTimer
,而后添加到当前线程的
Runloop
中。也就是若是当前线程没有开启runloop
,该方法会失效。在子线程中,须要启动 runloop
(注
意调用顺序)
而 performSelector:withObject
:只是一个单纯的消息发送,和时间没有一点关系。因此不须要添加到子
线程的 Runloop
中也能执行
二、GCD 对比 NSOprationQueue
咱们要明确 NSOperationQueue
与 GCD
之间的关系
GCD
是面向底层的 C 语言的 API
, NSOpertaionQueue
用 GCD
构建封装的,是 GCD
的高级抽象。
一、 GCD
执行效率更高,并且因为队列中执行的是由 block
构成的任务,这是一个轻量级的数据结构,写起来更方便
二、 GCD
只支持 FIFO
的队列,而 NSOperationQueue
能够经过设置最大并发数,设置优先级,添加依赖关系等调整执行顺序
三、 NSOperationQueue
甚至能够跨队列设置依赖关系,可是 GCD
只能经过设置串行队列,或者在队列内添加 barrier(dispatch_barrier_async)
任务,才能控制执行顺序,较为复杂
四、 NSOperationQueue
由于面向对象,因此支持 KVO
,能够监测 operation
是否正在执行( isExecuted
)、是否结束( isFinished
)、是否取消( isCanceld
)
l 实际项目开发中,不少时候只是会用到异步操做,不会有特别复杂的线程关系管理,因此苹果推崇的且优化完善、运行快速的 GCD
是首选
l 若是考虑异步操做之间的事务性,顺序行,依赖关系,好比多线程并发下载,GCD须要本身写更多的代码来实现,而 NSOperationQueue
已经内建了这些支持
l 不管是 GCD
仍是 NSOperationQueue
,咱们接触的都是任务和队列,都没有直接接触到线程,事实上线程管理也的确不须要咱们操心,系统对于线程的建立,调度管理和释放都作得很好。而 NSThread
须要咱们本身去管理线程的生命周期,还要考虑线程同步、加锁问题,形成一些性能上的开销
iOS
中,有 GCD
、 NSOperation
、 NSThread
等几种多线程技术方案。
而 GCD
共有三种队列类型:
main queue
:经过 dispatch_get_main_queue
()得到,这是一个与主线程相关的串行队列。
global queue
:全局队列是并发队列,由整个进程共享。存在着高、中、低三种优先级的全局队列。调用 dispath_get_global_queue
并传入优先级来访问队列。
自定义队列:经过函数 dispatch_queue_create
建立的队列
死锁就是队列引发的循环等待
一、一个比较常见的死锁例子:主队列同步
在主线程中运用主队列同步,也就是把任务放到了主线程的队列中。
同步对于任务是马上执行的,那么当把任务放进主队列时,它就会立马执行,只有执行完这个任务, viewDidLoad
才会继续向下执行。
而 viewDidLoad
和任务都是在主队列上的,因为队列的先进先出原则,任务又需等待 viewDidLoad
执行完毕后才能继续执行, viewDidLoad
和这个任务就造成了相互循环等待,就形成了死锁。
想避免这种死锁,能够将同步改为异步 dispatch_async
,或者将 dispatch_get_main_queue
换成其余串行或并行队列,均可以解决。
二、一样,下边的代码也会形成死锁:
外面的函数不管是同步仍是异步都会形成死锁。
这是由于里面的任务和外面的任务都在同一个 serialQueue
队列内,又是同步,这就和上边主队列同步的例子同样形成了死锁解决方法也和上边同样,将里面的同步改为异步 dispatch_async
,或者将 serialQueue
换成其余串行或并行队列,均可以解决
这样是不会死锁的,而且 serialQueue
和 serialQueue2
是在同一个线程中的。
一、串行队列先异步后同步
打印顺序是 13245
缘由是:
首先先打印 1
接下来将任务 2 其添加至串行队列上,因为任务 2 是异步,不会阻塞线程,继续向下执行,打印 3
而后是任务 4,将任务 4 添加至串行队列上,由于任务 4 和任务 2 在同一串行队列,根据队列先进先出原则,任务 4 必须等任务 2 执行后才能执行,又由于任务 4 是同步任务,会阻塞线程,只有执行完任务 4 才能继续向下执行打印 5
因此最终顺序就是 13245。
这里的任务 4 在主线程中执行,而任务 2 在子线程中执行。
若是任务 4 是添加到另外一个串行队列或者并行队列,则任务 2 和任务 4 无序执行(能够添加多个任务看效果)
二、performSelector
这里的 test
方法是不会去执行的,缘由在于
这个方法要建立提交任务到runloop
上的,而 gcd
底层建立的线程是默认没有开启对应runloop
的,全部这个方法就会失效。
而若是将 dispatch_get_global_queue
改为主队列,因为主队列所在的主线程是默认开启了runloop
的,就会去执行(将 dispatch_async
改为同步,由于同步是在当前线程执行,那么若是当前线程是主线程,test
方法也是会去执行的)。
一、问:怎么用 GCD 实现多读单写?
多读单写的意思就是:能够多个读者同时读取数据,而在读的时候,不能取写入数据。而且,在写的过程当中,不能有其余写者去写。即读者之间是并发的,写者与读者或其余写者是互斥的。
这里的写处理就是经过栅栏的形式去写。
就能够用 dispatch_barrier_sync
(栅栏函数)去实现
二、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 和栅栏函数后去执行这条打印。
三、则能够这样设计多读单写:
场景:在 n 个耗时并发任务都完成后,再去执行接下来的任务。好比,在 n 个网络请求完成后去刷新 UI 页
面。
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 保证线程安全,为线程加锁
一、保持线程同步:
dispatch_semaphore_wait
加锁阻塞了当前线程,dispatch_semaphore_signal
解锁后当前线程继续执行
二、保证线程安全,为线程加锁:
在线程安全中能够将dispatch_semaphore_wait
看做加锁,而 dispatch_semaphore_signal
看做解锁首先建立全局变量
注意到这里的初始化信号量是 1。
异步并发调用asyncTask
而后发现打印是从任务 1顺序执行到 100,没有发生两个任务同时执行的状况。
缘由以下:
在子线程中并发执行 asyncTask
,那么第一个添加到并发队列里的,会将信号量减 1,此时信号量等于 0,
能够执行接下来的任务。而并发队列中其余任务,因为此时信号量不等于 0,必须等当前正在执行的任务执行完毕后调用 dispatch_semaphore_signal
将信号量加 1,才能够继续执行接下来的任务,以此类推,从而达到线程加锁的目的。
dispatch_after
能让咱们添加进队列的任务延时执行,该函数并非在指定时间后执行处理,而只是在指定时间追加处理到 dispatch_queue
因为其内部使用的是dispatch_time_t
管理时间,而不是 NSTimer
。
因此若是在子线程中调用,相比 performSelector:afterDelay
,不用关心 runloop
是否开启
NSOperation
、NSOperationQueue
是苹果提供给咱们的一套多线程解决方案。实际上 NSOperation
、NSOperationQueue
是基于GCD
更高一层的封装,彻底面向对象。可是比GCD
更简单易用、代码可读性也更高。
一、能够添加任务依赖,方便控制执行顺序
二、能够设定操做执行的优先级
三、任务执行状态控制:isReady
,isExecuting
,isFinished
,isCancelled
若是只是重写 NSOperation
的 main
方法,由底层控制变动任务执行及完成状态,以及任务退出若是重写了 NSOperation
的 start
方法,自行控制任务状态
系统经过KVO
的方式移除 isFinished
==YES
的 NSOperation
四、能够设置最大并发量
#十4、NSOperation 和 NSOperationQueue
操做(Operation):
执行操做的意思,换句话说就是你在线程中执行的那段代码。
在 GCD
中是放在block
中的。在NSOperation
中,使用 NSOperation
子类 NSInvocationOperation
、NSBlockOperation
,或者自定义子类来封装操做。
操做队列(Operation Queues):
这里的队列指操做队列,即用来存放操做的队列。不一样于GCD
中的调度队列FIFO
(先进先出)的原则。
NSOperationQueue
对于添加到队列中的操做,首先进入准备就绪的状态(就绪状态取决于操做之间的依赖
关系),而后进入就绪状态的操做的开始执行顺序(非结束执行顺序)由操做之间相对的优先级决定(优先级是操做对象自身的属性)。
操做队列经过设置最大并发操做数(maxConcurrentOperationCount
)来控制并发、串行。
NSOperationQueue
为咱们提供了两种不一样类型的队列:主队列和自定义队列。主队列运行在主线程之上,
而自定义队列在后台执行。
NSThread
在实际开发中比较经常使用到的场景就是去实现常驻线程。
因为每次开辟子线程都会消耗cpu
,在须要频繁使用子线程的状况下,频繁开辟子线程会消耗大量的cpu
,并且建立线程都是任务执行完成以后也就释放了,不能再次利用,那么如何建立一个线程可让它能够再次工做呢?也就是建立一个常驻线程。
首先常驻线程既然是常驻,那么咱们能够用GCD
实现一个单例来保存 NSThread
这样建立的thread
就不会销毁了吗?
并无打印,说明 test
方法没有被调用。那么能够用 runloop
来让线程常驻
这时候再去调用performSelector
就有打印了。
自旋锁:
是一种用于保护多线程共享资源的锁,与通常互斥锁(mutex
)不一样之处在于当自旋锁尝试获取锁时以忙等待(busy waiting
)的形式不断地循环检查锁是否可用。当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会一直等待(不会睡眠),当上一个线程的任务执行完毕,下一个线程会当即执行。
在多 CPU
的环境中,对持有锁较短的程序来讲,使用自旋锁代替通常的互斥锁每每可以提升程序的性能。
互斥锁:
当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会进入睡眠状态等待任务执行完毕,当上一个线程的任务执行完毕,下一个线程会自动唤醒而后执行任务。
总结:
自旋锁会忙等: 所谓忙等,即在访问被锁资源时,调用者线程不会休眠,而是不停循环在那里,直到被锁资源释放锁。
互斥锁会休眠:所谓休眠,即在访问被锁资源时,调用者线程会休眠,此时 cpu
能够调度其余线程工做。直到被锁资源释放锁。此时会唤醒休眠线程。
优缺点:
自旋锁的优势在于,由于自旋锁不会引发调用者睡眠,因此不会进行线程调度,CPU
时间片轮转等耗时操做。全部若是能在很短的时间内得到锁,自旋锁的效率远高于互斥锁。
缺点在于,自旋锁一直占用 CPU
,他在未得到锁的状况下,一直运行--自旋,因此占用着 CPU
,若是不能在很短的时 间内得到锁,这无疑会使 CPU
效率下降。自旋锁不能实现递归调用。
自旋锁:atomic
、OSSpinLock
、dispatch_semaphore_t
互斥锁:pthread_mutex
、@ synchronized
、NSLock
、NSConditionLock
、NSCondition
、NSRecursiveLock
好了,以上就是本次内容,感谢观看!