iOS中的常見多線程方案
GCD的常用函數(shù)
- GCD中有2個用來執(zhí)行任務(wù)的函數(shù)
- 用同步的方式執(zhí)行任務(wù)
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block)
- 用異步的方式執(zhí)行任務(wù)
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
- queue:隊列
- block:任務(wù)
GCD的隊列
-
GCD的隊列可以分為2大類型
- 并發(fā)隊列(Concurrent Dispatch Queue)
- 可以讓多個任務(wù)并發(fā)(同時)執(zhí)行(自動開啟多個線程同時執(zhí)行任務(wù))
- 并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效
- 串行隊列(Serial Dispatch Queue)
- 讓任務(wù)一個接著一個地執(zhí)行(一個任務(wù)執(zhí)行完畢后忌锯,再執(zhí)行下一個任務(wù))
容易混淆的術(shù)語
-
有4個術(shù)語比較容易混淆:同步杜跷、異步晾捏、并發(fā)、串行
- 同步和異步主要影響:能不能開啟新的線程
- 同步:在當(dāng)前線程中執(zhí)行任務(wù)帆精,不具備開啟新線程的能力
- 異步:在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力
- 并發(fā)和串行主要影響:任務(wù)的執(zhí)行方式
- 并發(fā):多個任務(wù)并發(fā)(同時)執(zhí)行
- 串行:一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù)
各種隊列的執(zhí)行效果
-
使用sync函數(shù)往當(dāng)前串行隊列中添加任務(wù)翩概,會卡住當(dāng)前的串行隊列(產(chǎn)生死鎖)
隊列組的使用
- 思考:如何用gcd實現(xiàn)以下功能
- 異步并發(fā)執(zhí)行任務(wù)1、任務(wù)2
- 等任務(wù)1返咱、任務(wù)2都執(zhí)行完畢后钥庇,再回到主線程執(zhí)行任務(wù)3
多線程的安全隱患
- 資源共享
- 1塊資源可能會被多個線程共享,也就是多個線程可能會訪問同一塊資源
- 比如多個線程訪問同一個對象咖摹、同一個變量评姨、同一個文件
- 當(dāng)多個線程訪問同一塊資源時,很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題
多線程安全隱患示例01 – 存錢取錢
多線程安全隱患示例02 – 賣票
多線程安全隱患分析
多線程安全隱患的解決方案
- 解決方案:使用線程同步技術(shù)(同步楞艾,就是協(xié)同步調(diào)参咙,按預(yù)定的先后次序進(jìn)行)
-
常見的線程同步技術(shù)是:加鎖
iOS中的線程同步方案
OSSpinLock
os_unfair_lock
pthread_mutex
dispatch_semaphore
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSRecursiveLock
NSCondition
NSConditionLock
@synchronized
GNUstep
GNUstep是GNU計劃的項目之一,它將Cocoa的OC庫重新開源實現(xiàn)了一遍
雖然GNUstep不是蘋果官方源碼硫眯,但還是具有一定的參考價值
OSSpinLock
- OSSpinLock叫做”自旋鎖”蕴侧,等待鎖的線程會處于忙等(busy-wait)狀態(tài),一直占用著CPU資源
- 目前已經(jīng)不再安全两入,可能會出現(xiàn)優(yōu)先級反轉(zhuǎn)問題
如果等待鎖的線程優(yōu)先級較高净宵,它會一直占用著CPU資源,優(yōu)先級低的線程就無法釋放鎖
優(yōu)先級低的線程就無法釋放鎖
需要導(dǎo)入頭文件#import <libkern/OSAtomic.h>
os_unfair_lock
- os_unfair_lock用于取代不安全的OSSpinLock ,從iOS10開始才支持
- 從底層調(diào)用看择葡,等待os_unfair_lock鎖的線程會處于休眠狀態(tài)紧武,并非忙等
-
需要導(dǎo)入頭文件#import <os/lock.h>
pthread_mutex
- mutex叫做”互斥鎖”,等待鎖的線程會處于休眠狀態(tài)
-
需要導(dǎo)入頭文件#import <pthread.h>
pthread_mutex – 遞歸鎖
pthread_mutex – 條件
NSLock敏储、NSRecursiveLock
-
NSLock是對mutex普通鎖的封裝
- NSRecursiveLock也是對mutex遞歸鎖的封裝阻星,API跟NSLock基本一致
NSCondition
-
NSCondition是對mutex和cond的封裝
NSConditionLock
-
NSConditionLock是對NSCondition的進(jìn)一步封裝,可以設(shè)置具體的條件值
dispatch_semaphore
- semaphore叫做”信號量”
- 信號量的初始值已添,可以用來控制線程并發(fā)訪問的最大數(shù)量
-
信號量的初始值為1妥箕,代表同時只允許1條線程訪問資源,保證線程同步
dispatch_queue
-
直接使用GCD的串行隊列更舞,也是可以實現(xiàn)線程同步的
@synchronized
- @synchronized是對mutex遞歸鎖的封裝
- 源碼查看:objc4中的objc-sync.mm文件
- @synchronized(obj)內(nèi)部會生成obj對應(yīng)的遞歸鎖烂完,然后進(jìn)行加鎖抄谐、解鎖操作
iOS線程同步方案性能比較
- 性能從高到低排序
os_unfair_lock
OSSpinLock
dispatch_semaphore
pthread_mutex
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSCondition
pthread_mutex(recursive)
NSRecursiveLock
NSConditionLock
@synchronized
自旋鎖、互斥鎖比較
- 什么情況使用自旋鎖比較劃算?
- 預(yù)計線程等待鎖的時間很短
- 加鎖的代碼(臨界區(qū))經(jīng)常被調(diào)用吻谋,但競爭情況很少發(fā)生
- CPU資源不緊張
- 多核處理器
- 什么情況使用互斥鎖比較劃算属铁?
- 預(yù)計線程等待鎖的時間較長
- 單核處理器
- 臨界區(qū)有IO操作
- 臨界區(qū)代碼復(fù)雜或者循環(huán)量大
- 臨界區(qū)競爭非常激烈
atomic
- atomic用于保證屬性setter骄噪、getter的原子性操作佛舱,相當(dāng)于在getter和setter內(nèi)部加了線程同步的鎖
- 可以參考源碼objc4的objc-accessors.mm
- 它并不能保證使用屬性的過程是線程安全的
iOS中的讀寫安全方案
- 思考如何實現(xiàn)以下場景
- 同一時間,只能有1個線程進(jìn)行寫的操作
- 同一時間芽偏,允許有多個線程進(jìn)行讀的操作
- 同一時間雷逆,不允許既有寫的操作,又有讀的操作
- 上面的場景就是典型的“多讀單寫”污尉,經(jīng)常用于文件等數(shù)據(jù)的
讀寫操作膀哲,iOS中的實現(xiàn)方案有
- pthread_rwlock:讀寫鎖
- dispatch_barrier_async:異步柵欄調(diào)用
pthread_rwlock
-
等待鎖的線程會進(jìn)入休眠
dispatch_barrier_async
- 這個函數(shù)傳入的并發(fā)隊列必須是自己通過dispatch_queue_cretate創(chuàng)建的
-
如果傳入的是一個串行或是一個全局的并發(fā)隊列,那這個函數(shù)便等同于dispatch_async函數(shù)的效果