不论线程经过如何调度或线程如何交替执行,在不须要作任何干涉的状况下,其执行结果保持一致符合预期,则称之为线程安全。编程
通俗解释:在多线程中,一段代码会被多个线程执行。假如一个线程执行了某段代码的一部分后,被另外一个线程抢走时间片又去执行该段代码并修改其中内容,当原线程再次回来继续执行时里面的内容已经被别人改动了但它并不知道,最终致使错误的运行结果,这种线程就是不安全的。而安全的线程是指执行一段代码时,只要还没有执行完,其余线程就不能来执行这段代码直到执行完毕。swift
同步和异步主要区别:是否开启新的线程。安全
iOS 中的多线程技术主要分为 3 种,分别为 Thread、GCD 和 Operation。markdown
// Target-Action形式
let thread1 = Thread(target: self, selector: #selector(task), object: nil)
// 设置名字
thread1.name = "thread1"
// 启动
thread1.start()
复制代码
// 闭包形式
let thread2 = Thread {
sleep(1)
print(Thread.current)
}
thread2.name = "thread2"
thread2.start()
复制代码
// 类方法,也有3种形式,以闭包形式为例
// 会直接启动线程,不须要手动调用start方法来启动线程执行任务
Thread.detachNewThread {
sleep(1)
print(Thread.current)
}
复制代码
队列类型 | 功能描述 |
---|---|
串行队列 | 按照任务添加到队列的顺序执行,一次只能执行一个任务。 |
并发队列 | 同时执行一个或多个任务,但任务仍按其添加到队列的顺序启动。 |
主队列 | 特殊的串行队列,会在主线程上执行任务。 |
// 主队列
let main = DispatchQueue.main
复制代码
// label:队列的名称
// 除label之外的参数都使用默认值时,返回的是串行队列。
let serialQueue = DispatchQueue(label: "serialQueue")
复制代码
// global并发队列
let defaultGlobalDipatchQueue = DispatchQueue.global()
// 带qos的global并发队列
let globalDipatchQueue = DispatchQueue.global(qos: .default)
// 建立一个并发队列,参数attributes须要设置为.concurrent
let concurrentDispatchQueue = DispatchQueue(label: "concurrentQueue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil)
复制代码
(1)concurrent:标识队列为并发队列。(通常使用该选项) (2)initiallyInactive:标识队列中的任务须要开发者手动调用activate()
来触发。若是未添加此标识,向队列中添加的任务会自动运行。网络
autorelease pool
的自动释放频率。包含三个类型:(1)inherit:继承目标队列的该属性。 (2)workItem:跟随每一个任务的执行周期进行自动建立和释放。(通常使用该选项) (3)never:不会自动建立autorelease pool
,须要手动管理。多线程
(1)初始化时指定。 (2)初始化方法中,attributes 设定为 initiallyInactive,而后在队列执行 activate() 以前指定。闭包
(1)group:关联任务的 DispatchGroup。 (2)flags:控制任务执行的环境。(该参数 sync 方法也有)并发
queue.sync {
// 当前线程执行任务
}
queue.async {
// 新线程执行任务
}
复制代码
注意app
- 不管是并发队列仍是串行队列,若是是同步执行,都不会开辟新线程,只有异步执行才会开辟新线程。
- 并发队列在执行多个任务的时候,会开辟多个线程执行。而串行队列不会,它会执行完一个再去执行另一个。
在当前队列中延迟任务的执行时间,参数为DispatchTime
,通常会在当前时间的基础上加上一个延迟时间(以秒为单位)。dom
func dispatchAfter() {
queue.asyncAfter(deadline: DispatchTime.now() + 2) {
print("延迟2s执行")
}
// 主队列延迟执行
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
print("主队列延迟3s执行的任务")
}
}
复制代码
func concurrentPerform() {
print("任务开始执行")
DispatchQueue.concurrentPerform(iterations: 5) { index in
for i in 0 ... 3 {
Thread.sleep(forTimeInterval: 0.1)
print("这是\(Thread.current)第\(index)次打印:\(i)")
}
}
print("任务执行完毕")
}
复制代码
func barrier() {
let queue = DispatchQueue(label: "queue001", attributes: .concurrent)
queue.async {
sleep(1)
print("\(Thread.current)执行任务一")
}
queue.async {
sleep(1)
print("\(Thread.current)执行任务二")
}
// 任务四和五会在三以后执行
queue.async(flags: .barrier) {
sleep(1)
print("\(Thread.current)执行任务三")
}
queue.async {
sleep(1)
print("\(Thread.current)执行任务四")
}
queue.async {
sleep(1)
print("\(Thread.current)执行任务五")
}
}
复制代码
注意:若是队列是
DispatchQueue.global()
,barrier 不起做用。
func group() {
let group = DispatchGroup()
queue.async(group: group) {
print("网络请求任务一")
}
queue.async(group: group) {
print("网络请求任务二")
}
queue.async(group: group) {
print("网络请求任务三")
}
// 执行完前面的任务后回到主线程执行后续任务
group.notify(queue: DispatchQueue.main) {
print("完成任务1、2、三, 更新UI")
}
queue.async {
print("其余任务四")
}
group.notify(queue: DispatchQueue.main) {
print("完成任务1、2、3、四, 更新UI")
}
}
复制代码
enter()
和leave()
方法显式代表任务是否执行完成,enter()
必须在leave()
以前且两者必须成对出现。func group2() {
let group = DispatchGroup()
group.enter()
queue.async(group: group) {
print("网络请求任务一")
group.leave()
}
group.enter()
queue.async(group: group) {
print("网络请求任务二")
group.leave()
}
group.enter()
queue.async(group: group) {
print("网络请求任务三")
group.leave()
}
group.notify(queue: DispatchQueue.main) {
print("完成任务1、2、三, 更新UI")
}
queue.async {
print("其余任务四")
}
}
复制代码
perform()
方法执行任务。func dispatchWorkItem() {
var value = 10
// 初始化方法传入一个闭包,闭包中就是须要执行的任务
let workItem = DispatchWorkItem {
value += 5
print(Thread.current) // 主线程
}
// 经过perform()方法来唤起DispatchWorkItem执行任务
workItem.perform()
print(value)
}
复制代码
perform()
方法。let workItem = DispatchWorkItem {
for i in 0 ... 10 {
sleep(1)
print(i)
print(Thread.current) // 子线程
}
}
DispatchQueue.global().async(execute: workItem)
复制代码
(1)若是任务已经开始执行,即便取消也依然会执行。
let workItem = DispatchWorkItem {
for i in 0 ... 10 {
sleep(1)
print(i)
print(Thread.current)
}
}
// 先执行
DispatchQueue.global().async(execute: workItem)
// 后取消
workItem.cancel()
// 查看取消状态
print(workItem.isCancelled)
复制代码
(2)若是任务还没有开始执行,取消后则不会再执行。
let workItem = DispatchWorkItem {
for i in 0 ... 10 {
sleep(1)
print(i)
print(Thread.current)
}
}
// 先取消
workItem.cancel()
// 再执行
DispatchQueue.global().async(execute: workItem)
// 查看取消状态
print(workItem.isCancelled)
复制代码
(1)无参数:阻塞当前线程直到任务完成。
let workItem = DispatchWorkItem {
for i in 0 ... 10 {
sleep(1)
print(i)
print(Thread.current)
}
}
DispatchQueue.global().async(execute: workItem)
// 等待
workItem.wait()
// 任务完成后才会执行
print("继续执行任务")
复制代码
(2)timeout 参数:阻塞当前线程直到 timeout,若是任务完成 timeoutResult 为 success,不然为 timeOut。
let workItem = DispatchWorkItem {
for i in 0 ... 10 {
sleep(1)
print(i)
print(Thread.current)
}
}
DispatchQueue.global().async(execute: workItem)
// 设置等待时间
let timeoutResult = workItem.wait(timeout: .now() + 3)
// 3秒内执行完任务则为success,不然timeOut
switch timeoutResult {
case .success:
print("success")
case .timedOut:
print("timedOut")
}
// 3秒之后执行
print("继续执行任务")
复制代码
let workItem = DispatchWorkItem {
for i in 0 ... 10 {
sleep(1)
print(i)
print(Thread.current)
}
}
DispatchQueue.global().async(execute: workItem)
// 任务完成之后回到指定队列执行任务
workItem.notify(queue: DispatchQueue.main) {
print("任务完成")
}
print("继续执行任务")
复制代码
func operationUseOne() {
// 建立OperationQueue
let operationQueue = OperationQueue()
// 添加Operation
operationQueue.addOperation {
sleep(1)
print("\(Thread.current)执行任务一")
}
operationQueue.addOperation {
sleep(1)
print("\(Thread.current)执行任务二")
}
operationQueue.addOperation {
sleep(1)
print("\(Thread.current)执行任务三")
}
}
复制代码
func operationUseTwo() {
let operationQueue = OperationQueue()
// BlockOperation
let operation1 = BlockOperation {
print("\(Thread.current)执行任务一")
sleep(1)
}
let operation2 = BlockOperation {
print("\(Thread.current)执行任务二")
sleep(1)
}
let operation3 = BlockOperation {
print("\(Thread.current)执行任务三")
sleep(1)
}
// 逐个添加到OperationQueue
// operationQueue.addOperation(operation1)
// operationQueue.addOperation(operation2)
// operationQueue.addOperation(operation3)
// 一次性添加到OperationQueue
operationQueue.addOperations([operation1, operation2, operation3], waitUntilFinished: false)
// waitUntilFinished
// 若是为false,不会等任务完成再执行后续任务
// 若是为true,阻塞当前线程,等待任务完成后再执行后续任务
print("\(Thread.current)执行其余任务")
}
复制代码
let mainQueue = OperationQueue.main
// 在没有指定任何队列的状况下调用start方法启动的BlockOperation默认会在主线程执行任务
let op = BlockOperation {
sleep(1)
print("\(Thread.current)执行任务一")
}
op.start()
复制代码
设置 OperationQueue 的最大并发数,表示的是能同时执行的 Operation 的最大数量,而不是开启线程的最大数量。
func setOperationQueue() {
// 并发数
operationQueue.maxConcurrentOperationCount = 2
}
复制代码
注意:OperationQueue 没法直接建立串行队列(除主队列,主队列的最大并发数始终为 1 ),但能够设置最大并发数为 1 来实现串行队列的执行效果。
func setOperation(op:Operation){
// 优先级
op.queuePriority = .high
}
复制代码
func dependency() {
let operationQueue = OperationQueue()
let operation1 = BlockOperation {
print("\(Thread.current)执行任务一")
sleep(1)
}
// 监听Operation完成
operation1.completionBlock = {
print("\(Thread.current)完成任务一")
}
let operation2 = BlockOperation {
print("\(Thread.current)执行任务二")
sleep(1)
}
operation2.completionBlock = {
print("\(Thread.current)完成任务二")
}
// 添加依赖
// operation2在operation1执行完再执行(并非等completionBlock执行完再执行,而是BlockOperation体执行完就开始执行)
operation2.addDependency(operation1)
let operation3 = BlockOperation {
print("\(Thread.current)执行任务三")
sleep(1)
}
operation3.completionBlock = {
print("\(Thread.current)完成任务三")
}
// operation3在operation2执行完再执行
operation3.addDependency(operation2)
operationQueue.addOperations([operation1, operation2, operation3], waitUntilFinished: false)
print("\(Thread.current)执行其余任务")
}
复制代码
注意:串行队列与依赖关系之间的区别?
- 依赖关系所处的队列依旧是并发而非串行。
- 串行队列是将任务添加到队列之后串行执行,而依赖关系是并行执行。
相似 GCD 的 barrier。
func barrier() {
let operationQueue = OperationQueue()
operationQueue.addOperation {
sleep(1)
print("\(Thread.current)执行任务一")
}
operationQueue.addOperation {
sleep(1)
print("\(Thread.current)执行任务二")
}
// 任务四和五会在三以后执行
operationQueue.addBarrierBlock {
sleep(1)
print("\(Thread.current)执行任务三")
}
operationQueue.addOperation {
sleep(1)
print("\(Thread.current)执行任务四")
}
operationQueue.addOperation {
sleep(1)
print("\(Thread.current)执行任务五")
}
}
复制代码
func suspend() {
if operationQueue.operationCount != 0 && operationQueue.isSuspended == false {
operationQueue.isSuspended = true
}
}
复制代码
func resume() {
if operationQueue.operationCount != 0 && operationQueue.isSuspended == true {
operationQueue.isSuspended = false
}
}
复制代码
(1)取消单个。 (2)取消全部。
func cancel() {
// Operation 取消
operation.cancel()
// OperationQueue 取消全部
operationQueue.cancelAllOperations()
}
复制代码
多线程编程中,应该尽可能避免资源在线程之间共享,以减小线程间的相互影响。有两个重要的概念:
在实际开发中,常常存在多个线程访问同一个共享资源的状况,那么如何保证多线程执行结果的正确性?在 iOS 中主要提供了 2 种技术 — 锁和信号量。
(1)调用者在未得到锁的状况下会一直运行,若是不能在很短的时间内得到锁,会使CPU效率下降。因此自旋锁就适用于临界区持锁时间很是短且CPU资源不紧张的场景。 (2)在用自旋锁时(如递归调用)有可能形成死锁。
注意:锁操做是成对出现,有加锁就必定有解锁。
var mutex: pthread_mutex_t = {
// 初始化锁属性
var mutexattr = pthread_mutexattr_t()
// 锁属性赋值
pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_DEFAULT)
// 初始化锁
var mutex = pthread_mutex_t()
// pthread_mutex_init(&mutex, nil)
// mutexattr传nil表示default
pthread_mutex_init(&mutex, &mutexattr)
// 使用锁属性以后要释放
pthread_mutexattr_destroy(&mutexattr)
// 返回锁
return mutex
}()
// 线程业务代码
DispatchQueue.global().async {
// 加锁
pthread_mutex_lock(&mutex)
// 临界区
// 解锁
pthread_mutex_unlock(&mutex)
}
复制代码
deinit {
// 销毁锁
pthread_mutex_destroy(&mutex)
}
复制代码
包括NSLock、NSCondition、NSConditionLock、NSRecursiveLock
,都遵照了NSLocking
协议,。
public protocol NSLocking {
func lock() // 加锁
func unlock() // 解锁
}
复制代码
// 初始化
let lock = NSLock()
// 加锁
lock.lock()
// 临界区
// 解锁
lock.unlock()
复制代码
// 初始化
let lock = NSCondition()
var products = [Int]()
// 消费者
func consume() {
DispatchQueue.global().async {
// 加锁
lock.lock()
// 没有商品挂起线程
while products.count == 0 {
lock.wait()
}
// 消费产品
let product = products.remove(at: 0)
print("消费产品\(product)")
// 解锁
lock.unlock()
}
}
// 生产者
func produce() {
DispatchQueue.global().async {
// 加锁
lock.lock()
// 生产产品
let product = Int.random(in: 0 ... 100)
products.append(product)
print("生产产品\(product)")
// 唤醒消费者
lock.signal()
// 解锁
lock.unlock()
}
}
while true {
consume()
sleep(1)
produce()
}
复制代码
// 初始化时condition为0
let lock = NSConditionLock(condition: 0)
var products = [Int]()
// 消费者
func consume() {
DispatchQueue.global().async {
// 加锁,当参数与初始化时condition不一致时进行等待
lock.lock(whenCondition: 1)
// 消费产品
let product = products.remove(at: 0)
print("消费产品\(product)")
// 解锁,修改condition的值为0
lock.unlock(withCondition: 0)
}
}
// 生产者
func produce() {
DispatchQueue.global().async {
// 加锁,与初始化时condition一致,继续执行
lock.lock(whenCondition: 0)
// 生产产品
let product = Int.random(in: 0 ... 100)
products.append(product)
print("生产产品\(product)")
// 解锁,修改condition的值为1
lock.unlock(withCondition: 1)
}
}
while true {
consume()
sleep(1)
produce()
}
复制代码
// 初始化
let lock = NSRecursiveLock()
var count = 5
func recursive(value: Int) {
// 加锁(换成其余的锁会死锁)
lock.lock()
// 大于0才继续后面的操做
guard value > 0 else {
return
}
// 打印
print(value)
// 休眠
sleep(1)
// 递归次数减1
count -= 1
// 递归调用
recursive(value: count)
// 解锁
lock.unlock()
}
DispatchQueue.global().async {
print("开始")
recursive(value: count)
print("结束")
}
复制代码
let lock: Int = 0
// 加锁
objc_sync_enter(lock) // 不少时候参数为self
// 临界区
// 解锁
objc_sync_exit(lock)
复制代码
因为存在由于低优先级争夺资源致使死锁的问题,因此在 iOS 10 以后已废弃,替换它的是 os_unfair_lock。
一种互斥锁,内置于os
模块。
var lock = os_unfair_lock()
// 加锁
os_unfair_lock_lock(&lock)
// 临界区
// 解锁
os_unfair_lock_unlock(&lock)
复制代码
DispatchSemaphore 是一种基于计数的信号量。它能够设定一个阀值,多个线程竞争获取许可信号,超过阀值后,线程申请许可信号将会被阻塞。主要用于线程之间的数据同步。
(1)若大于 0,则将信号量减 1 ,继续执行后续任务。 (2)若小于等于 0,则阻塞当前线程,直到信号量大于 0 或者通过一个阈值时间才会执行后续任务。
// 建立信号量,初始值为0
let semaphore = DispatchSemaphore(value: 0)
// 线程业务代码
DispatchQueue.global().async {
// 临界区
semaphore.signal()
}
semaphore.wait(timeout: .distantFuture)
复制代码
主线程最早执行到
semaphore.wait
,此时信号量为 0 且阈值时间为.distantFuture
,所以会阻塞当前主线程,直到子线程的代码块执行到signal()
语句,将信号量加 1,此时被阻塞的主线程继续执行,从而保证线程之间的同步。
实现:模拟卖票操做,要求很多于 3 个线程同时操做。
// 初始化为100
var number = 100
// 锁
var lock = os_unfair_lock()
// 第一个线程
let thread1 = Thread {
saleTicket()
}
thread1.name = "thread1"
// 第二个线程
let thread2 = Thread {
saleTicket()
}
thread2.name = "thread2"
// 第三个线程
let thread3 = Thread {
saleTicket()
}
thread3.name = "thread3"
// 启动三个线程
thread1.start()
thread2.start()
thread3.start()
// 打印
func saleTicket() {
while true {
// 加锁
os_unfair_lock_lock(&lock)
// 卖完
guard number > 0 else {
os_unfair_lock_unlock(&lock)
print("票已卖完")
return
}
// number减1
number -= 1
print("\(Thread.current.name ?? "unknown")卖出去一张票,还剩\(number)张")
// 解锁
os_unfair_lock_unlock(&lock)
}
}
复制代码
import UIKit
// MARK:- Thread模式
func threadMode(){
let thread = Thread {
print("\(Thread.current)执行任务")
// 休眠
sleep(3)
// 更新UI
self.perform(#selector(self.updateUI), on: Thread.main, with: nil, waitUntilDone: false)
}
thread.start()
}
@objc func updateUI() {
self.infoLb.text = "Thread方式更新UI"
}
// MARK:- GCD模式
func gcdMode(){
DispatchQueue.global().async {
print("\(Thread.current)执行任务")
// 休眠
sleep(3)
// 更新UI
DispatchQueue.main.async {
self.infoLb.text = "GCD方式更新UI"
}
}
}
// MARK:- Operation模式
func operationMode(){
OperationQueue().addOperation {
print("\(Thread.current)执行任务")
// 休眠
sleep(3)
// 更新UI
OperationQueue.main.addOperation {
self.infoLb.text = "Operation方式更新UI"
}
}
}
复制代码