前言
多线程在开发过程中使用非常频繁,但是由于 OC GCD的强大和客户端对于多线程的需求没有那么复杂。很多时候就是一个简单的调用就可以满足我们的需求,但是随着项目的增大,简单的开启异步线程已经不能瞒住我们的需要。今天我来简单学些一下多线程的知识。
多线程
![](https://img.haomeiwen.com/i2279791/2131fc1a9081dbc3.png)
同步,异步,并发,串行
- 同步和异步的主要影响: 能不能开启新的线程
- 同步: 在当前线程中执行任务,不具备开启新线程的能力
- 异步: 在新的线程中执行任务,具备开启新线程的能力
- 并发和串行的主要影响: 任务的执行方式
- 并发: 多个任务同时执行,只有在异步函数下才有效
- 串行: 一个任务执行完毕后,开始执行下一个任务
![](https://img.haomeiwen.com/i2279791/dac27804946203e0.png)
多线程隐患
多线程最常见的隐患是资源共享,当多个线程同时访问同一块资源的时候,容易引发数据错乱和数据安全问题。
解决办法是线程同步技术,实现同时只会有一个线程访问资源已解决资源共享的问题。
最常见的线程同步技术是 加锁
线程同步方案
以下说明按照性能从高到低排序:
- os_unfair_lock
- 用于取代不安全的
OSSpinLock
,从iOS 10
才开始支持 - 属于互斥锁,会让等待锁的线程处于休眠状态,并非忙等
- 需要导入头文件
<os/lock.h>
- 用于取代不安全的
- OSSpinLock
- 自旋锁,等待锁的线程会处于忙等状态,一直占用
CPU
资源 - 已经不再安全,会出现优先级反转导致死锁问题
- 需要导入头文件
<libkern/OSAtomic.h>
- 自旋锁,等待锁的线程会处于忙等状态,一直占用
- dispatch_semaphore
- 信号量,初始值可以控制线程并发访问的最大数量
- 如果初始值为1,代表同时只能有1个线程访问资源,保持同步
- pthread_mutex
- 互斥锁,等待锁的线程会处于休眠状态
- 需要导入头文件
<pthread.h>
- 递归锁也是通过互斥锁实现的
- 通过设置不同的属性值,实现不同类型的锁 PTHREAD_MUTEX_RECURSIVE 就是递归锁
- dispatch_queue(DISPATCH_QUEUE_SERIAL)
-
GCD
串行队列,可以实现锁的功能
-
- NSLock
- 对 mutex 普通锁的封装
- NSCondition
- 对 mutex 和 cond 的封装
- pthread_mutex(recursive)
- 递归锁
- NSRecursiveLock
- 对 mutex 递归锁的封装,API 跟 NSLock 基本一致
- NSConditionLock
- NSCondition 进一步的封装
- @synchronized
- 对 mutex 递归锁的封装
- @synchronized(obj) 内部会生成 obj 对应的递归锁,然后进行加锁,解锁操作
属性的 atomic 修饰符用于保证属性的 setter、getter 的原子性操作,相当于在 getter、setter 内部价了线程同步的锁,但是它并不能保证使用属性的过程中是线程安全的
互斥锁,自旋锁比较
- 什么时候选用自旋锁比较划算
- 预计线程等待锁的时间很短
- 加锁代码(临界区)经常被调用,但竞争很少发生
- CPU 资源不紧张
- 多核处理器
- 什么时候选用互斥锁比较划算
- 预计线程等待时间比较长
- 单核处理器
- 临界区有 IO 操作
- 临界区代码复杂或者循环量大
- 临界区竞争非常激烈
- 递归锁是特殊的互斥锁,允许同一线程重复加锁
iOS中的读写安全方案
典型的应用场景是多读单写
,经常用于文件等数据的读写操作,iOS中的实现方案有:
- pthread_rwlock:读写锁
- 等待锁的线程会进入睡眠
- dispatch_barrier_async:异步栅栏调用
- 必须传入自己创建的并发队列
- 如果传入一个串行队列或者全局的并发队列,这个函数的效果相当于 dispatch_async 函数
最后
以上就是本篇的内容,势必会有一些遗漏和错误,欢迎斧正~
网友评论