05 知识点梳理:多线程
1. GCD实现多读单写
比如在内存中维护一份数据,有多处地方可能会同时操作这块数据,怎么能保证数据安全? 这道题目总结得到要满足以下三点:
读写互斥
写写互斥
读读并发
首先我们要维系一个GCD 队列,最好不用全局队列,毕竟大家都知道全局队列遇到栅栏函数是有坑点的,这里就不分析了!
因为考虑性能 死锁 堵塞的因素不考虑串行队列,用的是自定义的并发队列!
_concurrentQueue = dispatch_queue_create("com.kc_person.syncQueue", DISPATCH_QUEUE_CONCURRENT);
首先我们来看看读操作:
kc_safeObjectForKey
我们考虑到多线程影响是不能用异步函数的!说明:线程2 获取:
name
线程3 获取age
如果因为异步并发,导致混乱 本来读的是
name
结果读到了age
我们允许多个任务同时进去! 但是读操作需要同步返回,所以我们选择:
同步函数
(读读并发)
我们再来看看写操作,在写操作的时候对key进行了copy, 关于此处的解释,插入一段来自参考文献的引用:
函数调用者可以自由传递一个
NSMutableString
的key
,并且能够在函数返回后修改它。因此我们必须对传入的字符串使用copy
操作以确保函数能够正确地工作。如果传入的字符串不是可变的(也就是正常的NSString
类型),调用copy
基本上是个空操作。
这里我们选择
dispatch_barrier_async
, 为什么是栅栏函数而不是异步函数或者同步函数,下面分析:栅栏函数任务:
之前所有的任务执行完毕,并且在它后面的任务开始之前,期间不会有其他的任务执行,这样比较好的促使 写操作一个接一个写
(写写互斥)
,不会乱!
为什么不是异步函数?
应该很容易分析,毕竟会产生混乱!
为什么不用同步函数?
如果读写都操作了,那么用同步函数,就有可能存在:我写需要等待读操作回来才能执行,显然这里是不合理!
2 多线程和锁
**进程:**是资源分配的基本单位,它是程序执行时的一个实例,在程序运行时创建。
**线程:**是程序执行的最小单位,是进程的一个执行流,一个进程由多个线程组成。
多线程:
一个进程中并发执行多个线程,叫做多线程。在一个时间片内,CPU只能处理一个线程中的一个任务,对于一个单核CPU来说,在不同的时间片来执行不同线程中的任务,就形成了多个任务在同时执行的“假象”。
多线程的几种方式
pthread
:即
POSIX Thread
,缩写称为pthread
,是线程的POSIX标准,是一套通用的多线程API,可以在Unix/Linux/Windows
等平台跨平台使用。iOS中基本不使用。
NSThread
:苹果封装的面向对象的线程类,可以直接操作线程,比起
GCD
,NSThread
效率更高,由程序员自行创建,当线程中的任务执行完毕后,线程会自动退出,程序员也可手动管理线程的生命周期。使用频率较低。
GCD
:全称
Grand Central Dispatch
,由C语言实现,是苹果为多核并行运算提出的解决方案,CGD会自动利用更多的CPU内核,自动管理线程的生命周期,程序员只需要告诉GCD需要执行的任务,无需编写任何管理线程的代码。GCD也是iOS使用频率最高的多线程技术。
NSOperation
:基于GCD封装的面向对象的多线程技术,常配合NSOperationQueue使用,使用频率较高。
GCD 和 NSOperation 区别
GCD
仅仅支持先进先出FIFO队列
,不支持异步操作之间的依赖关系设置。而NSOperation
中的队列可以被重新设置优先级,从而实现不同操作的执行顺序调整。NSOperation
支持KVO
,可以观察任务的执行状态。GCD
更接近底层,GCD
在追求性能的底层操作来说,是速度最快的。从异步操作之间的事务性,顺序行,依赖关系。
GCD
需要自己写更多的代码来实现,而NSOperation
已经内建了这些支持。如果异步操作的过程需要更多的被交互和UI呈现出来,
NSOperation
更好。底层代码中,任务之间不太互相依赖,而需要更高的并发能力,GCD
则更有优势。
线程池原理
使用线程执行任务的时候,需要到线程池中去取线程进行任务分配。
首先判断线程池大小是否小于核心线程池大小,如果小于的话,创建新的线程执行任务;
如果当前线程池大小大于了核心线程池大小,然后开始判断工作队列是否已满,如果没满,将任务提交到工作队列。
如果工作队列已满,判断线程池的线程是否都在工作,如果有空闲线程没有在工作,就交给它去执行任务。
如果线程池中的线程都在工作,那么就交给饱和策略去执行。
饱和策略分为下面四种:
AbortPolicy
直接抛出RejectedExecutionExeception
异常来阻止系统正常运行;CallerRunsPolicy
将任务回退到调用者;DisOldestPolicy
丢掉等待最久的任务‘;DisCardPolicy
直接丢弃任务。
线程间通讯
直接同步或者异步向任务队列添加任务。
通过NSPort端口的形式进行发送消息,实现不同的线程间的通信。使用的时候注意需要将NSPort加入的线程的RunLoop中去。
**直接消息传递:**通过
performSelector
的一系列方法,可以实现由某一线程指定在另外的线程上执行任务。因为任务的执行上下文是目标线程,这种方式发送的消息将会自动的被序列化。**全局变量、共享内存块和对象:**在两个线程之间传递信息的另一种简单方法是使用全局变量,共享对象或共享内存块。尽管共享变量既快速又简单,但是它们比直接消息传递更脆弱。必须使用锁或其他同步机制仔细保护共享变量,以确保代码的正确性。 否则可能会导致竞争状况,数据损坏或崩溃。
**条件执行:**条件是一种同步工具,可用于控制线程何时执行代码的特定部分。您可以将条件视为关守,让线程仅在满足指定条件时运行。
Runloop sources:一个自定义的
Runloop source配置
可以让一个线程上收到特定的应用程序消息。由于Runloop source
是事件驱动的,因此在无事可做时,线程会自动进入睡眠状态,从而提高了线程的效率。**Ports and sockets:**基于端口的通信是在两个线程之间进行通信的一种更为复杂的方法,但它也是一种非常可靠的技术。更重要的是,端口和套接字可用于与外部实体(例如其他进程和服务)进行通信。为了提高效率,使用
Runloop source
来实现端口,因此当端口上没有数据等待时,线程将进入睡眠状态。**消息队列:**传统的多处理服务定义了
先进先出(FIFO)队列
抽象,用于管理传入和传出数据。尽管消息队列既简单又方便,但是它们不如其他一些通信技术高效。**Cocoa 分布式对象:**分布式对象是一种 Cocoa 技术,可提供基于端口的通信的高级实现。尽管可以将这种技术用于线程间通信,但是强烈建议不要这样做,因为它会产生大量开销。分布式对象更适合与其他进程进行通信,尽管在这些进程之间进行事务的开销也很高。
锁
线程安全就是在同一时刻,对同一个数据操作的线程只有一个。这时就用到了锁。
**锁:**是保证线程安全的同步工具。每一个线程在访问数据资源之前,要先获取(acquire)锁,然后在访问结束之后释放(release)锁。如果锁已经被占用,其它要获取锁的线程会等待,直到锁重新可用。
iOS中的锁分为互斥锁、自旋锁、信号量这三种。
互斥锁
就是在多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写。
@synchronized
:是递归锁
调用
synchronzied
的每个对象,runtime
都会为其分配一个递归锁并存储在哈希表中。如果在
synchronzied
内部对象被释放或为nil,会执行类似objc_sync_nil
的空方法。注意不要像
synchronzied
传入nil
, 这将会从代码中移走线程安全。
NSLock:
遵循 NSLocking 协议,lock
方法是加锁,unlock
是解锁,tryLock
是尝试加锁,如果失败的话返回 NO,lockBeforeDate
: 是在指定Date之前尝试加锁,如果在指定时间之前都不能加锁,则返回NO。注意不能多次调用lock方法,会造成死锁。
自旋锁
线程会反复检查锁变量是否可用,线程在这一过程中保持执行,是一种忙等待,一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁,自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。atomic
就是通过个set和get方法添加一个自旋锁。
信号量
dispatch_semaphore
使用:
1). 通过
dispatch_semaphore_create(value)
创建一个信号量,初始为1。2). 等待信号量
dispatch_semaphore_wait
,可以理解为lock加锁,会使信号量-1。3). 发送信号量
dispatch_semaphore_signal
,可以理解为 unlock解锁,会使signal
信号量+1。
互斥锁是 dispatch_semaphore
在取值0/1时的特例。信号量可以有更多的取值空间,用来实现更加复杂的同步,而不仅仅是线程间互斥。
3 通知,能不能跨线程
不能跨线程,在哪个线程发送的通知,就会在哪个线程接收。所以需要手动切换到主线程更新UI。
测试过程
发出通知的线程决定接收通知处理代码线程主线程
发通知 —子线程
监听通知:接收通知代码在主线程处理子线程
发通知 —主线程
监听通知:接收通知代码在子线程处理
4
Last updated