2020-11-12 iOS开发多线程

多线程

进程:
1,是一个具有一定独立功能的程序,操作系统分配资源的基本单位
2,在系统中正在运行的一个应用程序,就是一段程序执行过程,我们可以理解为手机上的一个app
3,每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内,拥有独立运行所需的全部资源

线程:
1,程序执行流的最小单位,线程是进程中的一个实体
2,一个进程要想执行任务,必须至少要有一条线程,应用程序启动的时候系统会默认开启一跳线程,这就是主线程

进程和线程的关系

1,线程是进程的执行单元,进程的所有任务都在线程中执行
2,线程是cpu分配资源和调度的最小单位
3,一个程序可以对应多个进程,一个进程可以有多个线程,但至少要有一个线程
4,同一个进程内的线程共享进程资源

多进程,多线程

1,进程是程序在计算机上的一个执行活动,当你运行一个程序,你就启动了一个进程,显然程序是死的(静态的),进程是活的(动态的)
2,进程分为系统进程和用户进程,用于完成操作系统的各种功能的进程是系统进程,所有由用户启动的进程都是用户进程,进程是操作系统进行资源分配的单位
3,进程又被细化为线程,也就是一个进程下有多个能独立运行的更小单位,在同一个时间同一个计算机系统中如果允许两个或两个以上的进程出于运行状态,这便是多线程。

1,同一时间,cpu只能处理一条线程,只有1条线程在执行。多线程并发执行,其实是cpu快速的在多条线程之间调度(切换),如果cpu调度线程的时间足够快,就能造成多线程并发执行的假象
2,如果线程非常非常多,cpu会在N多线程之间调度,消耗大量的cpu资源,每条线程被调度执行的频次会降低(线程的执行效率降低)
3,优点:能适当提高程序的执行效率,能适当提高资源利用率
4,缺点:开启线程会占用一定的内存(主线程1m 子线程512kb),如果开启大量的线程,会占用大量的内存空间,降低程序的性能,线程越多,cpu在调度线程上的开销就越大,程序设计的更加复杂:比如线程之间的通信,多线程的数据共享

任务、队列

任务:就是执行操作的意思,执行任务的方式有两种:同步执行(sync)和异步执行(async)
同步:同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,知道队列里面的任务完成之后再继续执行,即会阻塞线程,只能在当前线程中执行任务(是当前线程,不一定是主线程),不具备开启新线程的能力
异步:线程会立即返回,无需等待就会继续执行下面的任务,不阻塞当前线程,可以在新的线程中执行任务,具备开启新线程的能力(并不一定开启新线程),如果不是添加到主队列上,异步会在子线程中执行任务

队列

这里的队列指执行任务的等待队列,用来存放任务的队列,队列是一种特殊的线性标,采用先进先出的原则,新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取,每读取一个任务,则从队列中释放一个任务
在GCD中有两种队列,串行队列和并发队列,两者都符合先进先出的原则,两者的主要区别是执行顺序不同,以及开启线程数不同。

串行队列:同一时间,队列中只能执行一个任务,只有当前的任务执行完成之后,才能执行下一个任务,主队列是主线程上的一个串行队列,是系统自动为我们创建的
并发队列:同时允许多个任务并发执行,并发队列的并发功能只有在异步函数下才有效

iOS中的多线程

主要有三种:NSThread、NSoperationQueue、GCD

NSThread:轻量级别的多线程技术
是我们自己手动开辟的子线程,如果使用的是初始化方式就需要我们自己启动,如果使用的是构造器方式他就会自动启动,只要是我们手动开辟的线程,都需要我们自己管理该线程,不只是启动,还有该线程使用完毕后的资源回收


image.png

GCD对比NSOprationQueue
1,gcd执行效率更高,而且由于队列中执行的是block构成的任务,这是一个轻量级的数据结构,写起来方便
2,gcd只支持先进先出FIFO的队列,而且NSOperationQueue可以通过设置最大并发数,设置优先级,添加依赖关系等调整执行顺序
3,NSOperationQueue甚至可以跨队列设置依赖关系,但是gcd只能通过设置串行队列,或者在队列内添加barrier任务,才能控制执行顺序,较为复杂
4,NSOperationQueue面向对象,所以支持KVO,可以监测operation是否正在执行 是否结束 是否取消
(十几开发中很多只会用到异步操作,不会有特别复杂的线程关系管理,所以苹果推荐优化完善,运行快速的gcd是首选 如果考虑异步操作之间的事务性,顺序性,依赖关系,gcd需要自己写很多的代码来实现,而NSOperationQueue已经内建了这些支持,NSThread需要我们自己去管理线程的生命周期,还要考虑线程同步,加锁问题,造成一些性能上的开销)

GCD

GCD共有三种队列类型

main queue:通过dispatch-get-main-queue()获得,这是一个与主线程相关的串行队列
global queue:全局队列是并发队列,由整个进程共享,存在高中低三种优先级的全局队列,调用dispath-get-global-queue并传入优先级来访问队列
自定义队列:通过函数dispatch-queue-create创建的队列

死锁

死锁就是队列引起的循环等待
一个比较常见的死锁例子:主队列同步


image.png

同样下面的代码也会造成死锁:


image.png

外面的函数无论是同步还是异步都会造成死锁
这是因为里面的任务和外面的任务都在同一个队列内
又是同步,这就和上边主队列同步的例子一样造成了死锁
解决方法也和上边一样,将里面的同步改成异步或者将队列换成串行或并行

GCD任务执行顺序

串行队列先异步后同步


image.png

打印顺序: 13245
原因是: 首先先打印 1 接下来将任务 2 其添加至串行队列上,由于任务 2 是异步,不会阻塞线程,继续向下执行,打印 3 然后是任务 4,将任务 4 添加至串行队列上,因为任务 4 和任务 2 在同一串行队列,根据队列先进先出原则, 任务 4 必须等任务 2 执行后才能执行,又因为任务 4 是同步任务,会阻塞线程,只有执行完任务 4 才能继 续向下执行打印 5 所以最终顺序就是 13245。 这里的任务 4 在主线程中执行,而任务 2 在子线程中执行。 如果任务 4 是添加到另一个串行队列或者并行队列,则任务 2 和任务 4 无序执行(可以添加多个任务看效果)

performSelector

这个方法要创建提交任务到runloop上的,而gcd底层创建的线程是默认没有开启对应runloop的,所有的这个方法就会失效
如果改为主队列,所在的主线程是默认开启runloop的

延时函数

dispatch-after能让我们添加进队列的任务延时执行,该函数并不是在指定时间后执行处理,而只是在指定时间追加处理到dispatch-queue

使用dispatch-once实现单例

image.png

NSOperationQueue的有点

NSOperation、NSOperationQueue是苹果提供给我们的一套多线程解决方案,十几上他们是基于GCD更高一层的封装,完全面对对象,但是比GCD更简单易用、代码可读性也更高
1,可以添加任务依赖,方便控制执行顺序
2,可以设定操作执行的优先级
3,任务执行状态控制:isready,isexecuting,isfinished,iscancelled
如果只是重写NSOperation的main方法,由底层控制变更任务执行及完成状态,以及任务退出。
如果重写了NSOperation的start方法,自行控制任务状态
系统通过KVO的方式移除isfinished==YES的NSOperation
4,可以设置最大并发量

NSOperation 和 NSOperationQueue

操作(operation):
执行操作的意思,换句话说就是你在线程中执行的那段代码
在GCD中是放在block中的,在NSOperation中,使用NSOperation子类NSInvocationOperation、 NSBlockOperation,或者自定义子类来封装操作。

操作队列(operation queue):
这里的队列指操作队列,即用来存放操作的队列。不同于 GCD 中的调度队列 FIFO(先进先出)的原则。 NSOperationQueue 对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖 关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优 先级是操作对象自身的属性)。
操作队列通过设置最大并发操作数(maxConcurrentOperationCount)来控制并发、串行。 NSOperationQueue 为我们提供了两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上, 而自定义队列在后台执行。

自旋锁与互斥锁

自旋锁: 是一种用于保护多线程共享资源的锁,与一般互斥锁(mutex)不同之处在于当自旋锁尝试获取锁时以忙等 待(busy waiting)的形式不断地循环检查锁是否可用。当上一个线程的任务没有执行完毕的时候(被锁住), 那么下一个线程会一直等待(不会睡眠),当上一个线程的任务执行完毕,下一个线程会立即执行。 在多 CPU 的环境中,对持有锁较短的程序来说,使用自旋锁代替一般的互斥锁往往能够提高程序的性能。

互斥锁: 当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会进入睡眠状态等待任务执行完毕, 当上一个线程的任务执行完毕,下一个线程会自动唤醒然后执行任务。

总结: 自旋锁会忙等: 所谓忙等,即在访问被锁资源时,调用者线程不会休眠,而是不停循环在那里,直到被锁 资源释放锁。 互斥锁会休眠: 所谓休眠,即在访问被锁资源时,调用者线程会休眠,此时 cpu 可以调度其他线程工 作。直到被锁资源释放锁。此时会唤醒休眠线程。

优缺点:
自旋锁的优点在于,因为自旋锁不会引起调用者睡眠,所以不会进行线程调度,CPU 时间片轮转等耗时操 作。所有如果能在很短的时间内获得锁,自旋锁的效率远高于互斥锁。 缺点在于,自旋锁一直占用 CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着 CPU,如果不 能在很短的时 间内获得锁,这无疑会使 CPU 效率降低。自旋锁不能实现递归调用。 自旋锁:atomic、OSSpinLock、dispatch_semaphore_t 互斥锁:pthread_mutex、@ synchronized、NSLock、NSConditionLock 、NSCondition、NSRecursiveLock

你可能感兴趣的:(2020-11-12 iOS开发多线程)