不論線程經過如何調度或線程如何交替執行,在不須要作任何干涉的狀況下,其執行結果保持一致符合預期,則稱之爲線程安全。編程
通俗解釋:在多線程中,一段代碼會被多個線程執行。假如一個線程執行了某段代碼的一部分後,被另外一個線程搶走時間片又去執行該段代碼並修改其中內容,當原線程再次回來繼續執行時裏面的內容已經被別人改動了但它並不知道,最終致使錯誤的運行結果,這種線程就是不安全的。而安全的線程是指執行一段代碼時,只要還沒有執行完,其餘線程就不能來執行這段代碼直到執行完畢。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"
}
}
}
複製代碼