多線程知識(shí)的簡(jiǎn)單介紹
進(jìn)程
- 進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序
- 每個(gè)進(jìn)程之間是獨(dú)立的上陕,每個(gè)進(jìn)程均運(yùn)行在其專用的且受保護(hù)的內(nèi)存空間內(nèi)
- 通過“活動(dòng)監(jiān)視器”可以查看mac系統(tǒng)中所開啟的線程
- MAC是多進(jìn)程的,iOS是單進(jìn)程的。
- 進(jìn)程中包含多個(gè)線程,進(jìn)程負(fù)責(zé)任務(wù)的調(diào)度,線程負(fù)責(zé)任務(wù)的執(zhí)行。
在iOS中并不支持多進(jìn)程,所有程序都是單一進(jìn)程運(yùn)行蘸吓,進(jìn)程之間相互獨(dú)立。
線程
- 程是進(jìn)程的基本執(zhí)行單元撩幽,一個(gè)進(jìn)程的所有任務(wù)都在線程中執(zhí)行
- 進(jìn)程要想執(zhí)行任務(wù)库继,必須得有線程,進(jìn)程至少要有一條線程
- 程序啟動(dòng)會(huì)默認(rèn)開啟一條線程窜醉,這條線程被稱為主線程或者UI線程
多線程
iOS中多線程同時(shí)執(zhí)行的本質(zhì)是CPU在多個(gè)任務(wù)之間快速的切換帖渠,由于CPU調(diào)度線程的時(shí)間足夠快琼锋,就造成了多線程的“同時(shí)”執(zhí)行的效果窑邦。其中切換的時(shí)間間隔就是時(shí)間片垢油。所以多線程并不是真正的并發(fā),而真正的并發(fā)必須建立在多核CPU的基礎(chǔ)上琅催。
多線程在iOS上的應(yīng)用
在iOS開發(fā)中 居凶,關(guān)于多線程現(xiàn)有的框架有:pthread虫给、NSTread、GCD侠碧、NSOperation
1. pthread(了解)
- 創(chuàng)建phtread_create
- create一次就會(huì)創(chuàng)建一個(gè)新的線程
- 系統(tǒng)會(huì)自動(dòng)在子線程中調(diào)用傳入的函數(shù)
int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine) (void *),void *arg)
2. NSTread(了解/掌握)
Thread使用
- 第一種創(chuàng)建方式
//第一種創(chuàng)建方式
let thread = Thread.init(target: self, selector: #selector(run), object: nil)
// 設(shè)置線程的名稱
thread.name = "線程A"
// 啟動(dòng)線程---線程一啟動(dòng)抹估,就會(huì)在線程thread中執(zhí)行self的run方法
thread.start()
- 注意: 需要手動(dòng)啟動(dòng)線程
- 特點(diǎn): 系統(tǒng)內(nèi)部會(huì)retain當(dāng)前線程
- 只有線程中的方法執(zhí)行完畢, 系統(tǒng)才會(huì)將其釋放
- 第二種創(chuàng)建方式
// 分離出一條子線程,自動(dòng)啟動(dòng)線程,但無法獲得線程對(duì)象
Thread.detachNewThreadSelector(#selector(run), toTarget: self, with: nil)
// 開啟一條后臺(tái)線程,自動(dòng)啟動(dòng)線程,但無法獲得線程對(duì)象
self.performSelector(inBackground: #selector(run), withObject: nil)
- 不用手動(dòng)調(diào)用start方法, 系統(tǒng)會(huì)自動(dòng)啟動(dòng)
- 沒有返回值, 不能對(duì)線程進(jìn)行更多的設(shè)置
- 應(yīng)用場(chǎng)景: 需要快速簡(jiǎn)便的執(zhí)行線程
Thread線程狀態(tài)
線程的狀態(tài):新建-就緒-運(yùn)行-阻塞-死亡
//取消線程--cancel并不能exit線程,只是標(biāo)記為canceled弄兜,但線程并沒有死掉
Thread.current.cancel()
// 程的退出
Thread.exit()
// 線程的休眠1
Thread.sleep(forTimeInterval: 2.0)
// 線程的休眠2
Thread.sleep(until: Date.init(timeIntervalSinceNow: 3.0))
一個(gè)NSThread對(duì)象就代表一條線程
3. GCD(了解/掌握)
GCD 是蘋果公司為多核的并行運(yùn)算提出的解決方案药蜻, GCD會(huì)自動(dòng)利用更多的 CPU 內(nèi)核(比如雙核、四核)來開啟線程執(zhí)行任務(wù)挨队,GCD 會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程谷暮、調(diào)度任務(wù)蒿往、銷毀線程)盛垦,不需要我們程序員手動(dòng)管理內(nèi)存
GCD隊(duì)列
- 并發(fā)隊(duì)列
- 可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行,開啟多個(gè)線程同時(shí)執(zhí)行任務(wù)
- 并發(fā)功能只有在異步async函數(shù)下才有效 - 串行隊(duì)列
- 讓任務(wù)一個(gè)接著一個(gè)地執(zhí)行瓤漏,遵循FIFO
GCD四種隊(duì)列
- 串行隊(duì)列
//OC
dispatch_queue_create("queue.name", DISPATCH_QUEUE_SERIAL);
//swift
let queue = DispatchQueue(label: "queue.name")
所有任務(wù)按順序依次執(zhí)行腾夯,結(jié)束順序固定,符合先進(jìn)先出(FIFO)的基本原則蔬充,隊(duì)列后面的任務(wù)必須等待前面的任務(wù)執(zhí)行完畢后才出隊(duì)列蝶俱。但是,不要認(rèn)為串行隊(duì)列中的所有任務(wù)都在同一個(gè)線程中執(zhí)行饥漫,串行隊(duì)列中的異步任務(wù)榨呆,可能會(huì)開啟新線程去執(zhí)行
- 并發(fā)隊(duì)列
//OC
dispatch_queue_create("queue.name",DISPATCH_QUEUE_CONCURRENT);
//Swift
let queue = DispatchQueue(label: "queue.name", attributes: .concurrent)
所有任務(wù)可以同時(shí)執(zhí)行,結(jié)束順序不固定庸队,只要有可用線程积蜻,則隊(duì)列頭部任務(wù)將持續(xù)出隊(duì)列
- 主隊(duì)列
//OC
dispatch_get_main_queue()
//swift
DispatchQueue.main
主隊(duì)列本質(zhì)是一個(gè)特殊的串行隊(duì)列,主隊(duì)列的任務(wù)都在主線程來執(zhí)行彻消,專門負(fù)責(zé)調(diào)度主線程度的任務(wù)竿拆,無法開辟新的線程。所以宾尚,在主隊(duì)列下的任務(wù)不管是異步任務(wù)還是同步任務(wù)都不會(huì)開辟線程丙笋,任務(wù)只會(huì)在主線程順序執(zhí)行。如果在主線程上已經(jīng)有任務(wù)正在執(zhí)行煌贴,主隊(duì)列會(huì)等到主線程空閑后再調(diào)度任務(wù)
- 全局隊(duì)列
//OC
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//swift
DispatchQueue.global(priority: 0)
全局隊(duì)列本質(zhì)是一個(gè)特殊的并發(fā)隊(duì)列御板。可以設(shè)置“調(diào)度優(yōu)先級(jí)” 參數(shù)
線程與隊(duì)列的組合分析
- 串行隊(duì)列+異步:順序執(zhí)行牛郑,先進(jìn)先出怠肋,可能會(huì)開啟新線程
let queue = DispatchQueue(label: "queue.name")
for i in 0 ..< 5 {
queue.async {
print("這是第 %d 個(gè)任務(wù);線程 %@", i, Thread.current)
if ( i == 1 || i == 3 ){
Thread.sleep(forTimeInterval: 2)
}
}
}
print("-----------------所有任務(wù)執(zhí)行完畢")
====================================================
這是第 %d 個(gè)任務(wù)井濒;線程 %@ 0 <NSThread: 0x6000005bc300>{number = 4, name = (null)}
-----------------所有任務(wù)執(zhí)行完畢
這是第 %d 個(gè)任務(wù)灶似;線程 %@ 1 <NSThread: 0x6000005bc300>{number = 4, name = (null)}
這是第 %d 個(gè)任務(wù)列林;線程 %@ 2 <NSThread: 0x6000005bc300>{number = 4, name = (null)}
這是第 %d 個(gè)任務(wù);線程 %@ 3 <NSThread: 0x6000005bc300>{number = 4, name = (null)}
這是第 %d 個(gè)任務(wù)酪惭;線程 %@ 4 <NSThread: 0x6000005bc300>{number = 4, name = (null)}
- 串行隊(duì)列+同步:順序執(zhí)行希痴,先進(jìn)先出,不會(huì)開啟新線程
let queue = DispatchQueue(label: "queue.name")
for i in 0 ..< 5 {
queue.sync {
print("這是第 %d 個(gè)任務(wù)春感;線程 %@", i, Thread.current)
if ( i == 1 || i == 3 ){
Thread.sleep(forTimeInterval: 2)
}
}
}
print("-----------------所有任務(wù)執(zhí)行完畢")
====================================================
這是第 %d 個(gè)任務(wù)砌创;線程 %@ 0 <_NSMainThread: 0x600003aa41c0>{number = 1, name = main}
這是第 %d 個(gè)任務(wù);線程 %@ 1 <_NSMainThread: 0x600003aa41c0>{number = 1, name = main}
這是第 %d 個(gè)任務(wù)鲫懒;線程 %@ 2 <_NSMainThread: 0x600003aa41c0>{number = 1, name = main}
這是第 %d 個(gè)任務(wù)嫩实;線程 %@ 3 <_NSMainThread: 0x600003aa41c0>{number = 1, name = main}
這是第 %d 個(gè)任務(wù);線程 %@ 4 <_NSMainThread: 0x600003aa41c0>{number = 1, name = main}
-----------------所有任務(wù)執(zhí)行完畢
- 并發(fā)隊(duì)列+異步:同時(shí)執(zhí)行窥岩,每個(gè)任務(wù)的結(jié)束順序不確定甲献,會(huì)開啟新線程
let queue = DispatchQueue(label: "queue.name", attributes: .concurrent)
for i in 0 ..< 5 {
queue.async {
print("這是第 %d 個(gè)任務(wù);線程 %@", i, Thread.current)
if ( i == 1 || i == 3 ){
Thread.sleep(forTimeInterval: 2)
}
}
}
print("-----------------所有任務(wù)執(zhí)行完畢")
====================================================
這是第 %d 個(gè)任務(wù)颂翼;線程 %@ 0 <NSThread: 0x600000f4fc40>{number = 8, name = (null)}
這是第 %d 個(gè)任務(wù)晃洒;線程 %@ 1 <NSThread: 0x600000f54dc0>{number = 7, name = (null)}
這是第 %d 個(gè)任務(wù);線程 %@ 2 <NSThread: 0x600000f543c0>{number = 6, name = (null)}
這是第 %d 個(gè)任務(wù)朦乏;線程 %@ 3 <NSThread: 0x600000f54440>{number = 4, name = (null)}
這是第 %d 個(gè)任務(wù)球及;線程 %@ 4 <NSThread: 0x600000f4e100>{number = 3, name = (null)}
- 并發(fā)隊(duì)列+同步:順序執(zhí)行,先進(jìn)先出呻疹,不會(huì)開啟新線程
let queue = DispatchQueue(label: "queue.name", attributes: .concurrent)
for i in 0 ..< 5 {
queue.sync {
print("這是第 %d 個(gè)任務(wù)吃引;線程 %@", i, Thread.current)
if ( i == 1 || i == 3 ){
Thread.sleep(forTimeInterval: 2)
}
}
}
print("-----------------所有任務(wù)執(zhí)行完畢")
====================================================
這是第 %d 個(gè)任務(wù);線程 %@ 0 <_NSMainThread: 0x6000039a0000>{number = 1, name = main}
這是第 %d 個(gè)任務(wù)刽锤;線程 %@ 1 <_NSMainThread: 0x6000039a0000>{number = 1, name = main}
這是第 %d 個(gè)任務(wù)镊尺;線程 %@ 2 <_NSMainThread: 0x6000039a0000>{number = 1, name = main}
這是第 %d 個(gè)任務(wù);線程 %@ 3 <_NSMainThread: 0x6000039a0000>{number = 1, name = main}
這是第 %d 個(gè)任務(wù)姑蓝;線程 %@ 4 <_NSMainThread: 0x6000039a0000>{number = 1, name = main}
-----------------所有任務(wù)執(zhí)行完畢
- 主隊(duì)列+異步:不會(huì)立即執(zhí)行鹅心,而是等待主隊(duì)列中的所有其他除我們添加到主隊(duì)列的任務(wù)都執(zhí)行完畢之后,才會(huì)執(zhí)行我們添加的任務(wù)
let queue = DispatchQueue.main
for i in 0 ..< 5 {
queue.async {
print("這是第 %d 個(gè)任務(wù)纺荧;線程 %@", i, Thread.current)
if ( i == 1 || i == 3 ){
Thread.sleep(forTimeInterval: 2)
}
}
}
print("-----------------所有任務(wù)執(zhí)行完畢")
====================================================
-----------------所有任務(wù)執(zhí)行完畢
這是第 %d 個(gè)任務(wù)旭愧;線程 %@ 0 <_NSMainThread: 0x60000197c5c0>{number = 1, name = main}
這是第 %d 個(gè)任務(wù);線程 %@ 1 <_NSMainThread: 0x60000197c5c0>{number = 1, name = main}
這是第 %d 個(gè)任務(wù)宙暇;線程 %@ 2 <_NSMainThread: 0x60000197c5c0>{number = 1, name = main}
這是第 %d 個(gè)任務(wù)输枯;線程 %@ 3 <_NSMainThread: 0x60000197c5c0>{number = 1, name = main}
這是第 %d 個(gè)任務(wù);線程 %@ 4 <_NSMainThread: 0x60000197c5c0>{number = 1, name = main}
- 主隊(duì)列+同步:會(huì)相互等待導(dǎo)致死鎖
//同步任務(wù)+主線程 == 相互等待占贫,死鎖
let queue = DispatchQueue.main
for i in 0 ..< 5 {
queue.sync {
print("這是第 %d 個(gè)任務(wù)桃熄;線程 %@", i, Thread.current)
if ( i == 1 || i == 3 ){
Thread.sleep(forTimeInterval: 2)
}
}
}
print("-----------------所有任務(wù)執(zhí)行完畢")
====================================================
運(yùn)行崩潰報(bào)錯(cuò),相互等待型奥,死鎖
綜上所述列表總結(jié)一下吧:
并發(fā)隊(duì)列 | 串行隊(duì)列 | 主隊(duì)列 | |
---|---|---|---|
同步 | 沒有開啟新線程,串行執(zhí)行任務(wù) | 沒有開啟新線程,串行執(zhí)行任務(wù) | 沒有開啟新線程,串行執(zhí)行任務(wù) |
異步 | 有開啟新線程,并行執(zhí)行任務(wù) | 有開啟新線程,串行執(zhí)行任務(wù) | 沒有開啟新線程,串行執(zhí)行任務(wù) |
其他操作
- asyncAfter
asyncAfter函數(shù)可以設(shè)置延遲一段時(shí)間后運(yùn)行閉包瞳收,功能類似于定時(shí)器 - DispatchGroup
4. NSOperation(了解/掌握)
- 基于GCD封裝的抽象類碉京。
- 如果不使用NSOperationQueue單獨(dú)使用為同步操作,不會(huì)開啟線程螟深。異步執(zhí)行需要配合NSOperationQueue使用谐宙。
- 有兩個(gè)子類分別為:NSInvocationOperation和NSBlockOperation。
- 可以添加依賴控制所需要執(zhí)行的任務(wù)界弧。
- 可以通過maxConcurrentOperationCount控制并發(fā)數(shù)量凡蜻。
- NSOperation支持KVO(Key-Value Observing),可以方便的監(jiān)聽任務(wù)的狀態(tài)(完成垢箕、執(zhí)行中划栓、取消等等狀態(tài))
- GCD是按FIFO順序來執(zhí)行的,而NSOperation能夠方便地通過依賴關(guān)系設(shè)置操作執(zhí)行順序条获,可以控制任務(wù)在特定的任務(wù)執(zhí)行完后才執(zhí)行忠荞。
- swfit 中 NSOperation和 NSOperationQueue 都去掉了前綴NS,直接叫 Operation 和OperationQueue月匣。
- 蘋果認(rèn)為 NSInvocationOperation 不是類型安全或者不是 ARC 安全的钻洒,在 Swift中 取消了與之相關(guān)的 API奋姿。
NSOperation(任務(wù))
NSOperation是個(gè)抽象類锄开,實(shí)際運(yùn)用需要使用他的子類。
- 3種創(chuàng)建方式:
NSInvocationOperation(swift不支持): 此類調(diào)用選擇器方法(selector), 在方法里面編寫任務(wù)代碼.
NSBlockOperation (swift對(duì)應(yīng)BlockOperation): 此類采用block方式, 在block中編寫任務(wù)代碼.
let operation = BlockOperation.init {
print("\(#function), thread:\(Thread.current)")
}
- 定義繼承自NSOperation (swift對(duì)應(yīng)Operation)的子類称诗,通過實(shí)現(xiàn)內(nèi)部相應(yīng)的方法來封裝任務(wù)萍悴。
class CustomOperation: Operation {
override func main() {
print("\(#function), thread:\(Thread.current)")
}
}
let operation = CustomOperation.init()
- 2種執(zhí)行方式:
不添加到隊(duì)列, 手動(dòng)調(diào)用operation的start方法.不開啟新線程在主線程執(zhí)行
添加到隊(duì)列, 系統(tǒng)自動(dòng)調(diào)用start方法.
NSOperationQueue(隊(duì)列)
隊(duì)列 (Operation Queue)有兩種: 主隊(duì)列和非主隊(duì)列 (自定義隊(duì)列).
主隊(duì)列通過mainQueue獲得, 主隊(duì)列里的任務(wù)都是放到主線程執(zhí)行 (不包括使用addExecutionBlock:添加的額外操作, 因其可能在其他線程執(zhí)行).
非主隊(duì)列 (自定義隊(duì)列) 即一般 alloc init 出來的隊(duì)列, 默認(rèn)在子線程中異步執(zhí)行. 通多設(shè)置最大并發(fā)數(shù)(maxConcurrentOperationCount)來控制隊(duì)列是串行還是并發(fā).
- 添加操作(任務(wù))到隊(duì)列有四種方式:
- addOperation:
添加一個(gè)現(xiàn)有的Operation (或者其子類). - addOperations:waitUntilFinished:
可添加多個(gè)現(xiàn)有的Operation (或者其子類), 可設(shè)置等待所有操作完成后方可繼續(xù)往下執(zhí)行. - addOperationWithBlock:
直接添加一個(gè)block - addBarrierBlock:
添加?xùn)艡? 順帶一個(gè)任務(wù). 等柵欄前的所有任務(wù)都執(zhí)行完, 再執(zhí)行本柵欄的任務(wù), 起到隔離同步等待的目的.
串行 & 并行
主隊(duì)列是串行隊(duì)列. 自定義隊(duì)列默認(rèn)是并發(fā)隊(duì)列, 但可通多設(shè)置最大并發(fā)數(shù)(maxConcurrentOperationCount)來控制隊(duì)列是串行還是并發(fā).
- maxConcurrentOperationCount
-1, 默認(rèn)值, 并發(fā)隊(duì)列
=0, 不執(zhí)行任何操作
=1, 串行隊(duì)列
<0, 除-1默認(rèn)值外, 其他負(fù)值均報(bào)錯(cuò)
>1, 并發(fā)隊(duì)列, 如果數(shù)值過大, 最終并發(fā)數(shù)由系統(tǒng)決定.
其他操作
- 取消隊(duì)列NSOperationQueue的所有操作,NSOperationQueue對(duì)象方法
cancelAllOperations() - 取消NSOperation的某個(gè)操作寓免,NSOperation對(duì)象方法
cancel() - 使隊(duì)列暫脱⒂眨或繼續(xù)
queue.isSuspended = true - 判斷隊(duì)列是否暫停
isSuspended
暫停和取消不是立刻取消當(dāng)前操作,而是等當(dāng)前的操作執(zhí)行完之后不再進(jìn)行新的操作袜香。 - NSOperation的操作依賴
let queue = OperationQueue.init()
let operation1 = BlockOperation.init {
for _ in 0 ..< 3 {
print("operation1, thread:\(Thread.current)")
}
}
let operation2 = BlockOperation.init {
print("****operation2依賴于operation1撕予,只有當(dāng)operation1執(zhí)行完畢,operation2才會(huì)執(zhí)行****")
for _ in 0 ..< 3 {
print("operation2, thread:\(Thread.current)")
}
}
operation2.addDependency(operation1)
queue.addOperation(operation1)
queue.addOperation(operation2)
====================================================
operation1, thread:<NSThread: 0x600003625000>{number = 7, name = (null)}
operation1, thread:<NSThread: 0x600003625000>{number = 7, name = (null)}
operation1, thread:<NSThread: 0x600003625000>{number = 7, name = (null)}
****operation2依賴于operation1蜈首,只有當(dāng)operation1執(zhí)行完畢实抡,operation2才會(huì)執(zhí)行****
operation2, thread:<NSThread: 0x6000036385c0>{number = 5, name = (null)}
operation2, thread:<NSThread: 0x6000036385c0>{number = 5, name = (null)}
operation2, thread:<NSThread: 0x6000036385c0>{number = 5, name = (null)}
多線程的優(yōu)點(diǎn)
- 能適當(dāng)提高程序的執(zhí)行效率
- 能適當(dāng)提高資源的利用率,如CPU欢策、內(nèi)存
- 線程上的任務(wù)執(zhí)行完成后吆寨,線程會(huì)自動(dòng)銷毀
多線程的缺點(diǎn)
- 創(chuàng)建線程是有開銷的,iOS下主要成本包括:內(nèi)核數(shù)據(jù)結(jié)構(gòu)(1KB)踩寇、椬那澹空間(子線程512KB 、主線程1MB俺孙,也可以使用-setStackSize:設(shè)置辣卒,但必須是4K的倍數(shù)掷贾,而且最小是16K),創(chuàng)建線程大約需要90毫秒的創(chuàng)建時(shí)間
- 如果開啟大量線程荣茫,會(huì)降低程序的性能
- 線程越多胯盯,CPU在調(diào)度線程上的開銷就越大
- 多個(gè)線程同時(shí)訪問同一塊資源時(shí),很容易引發(fā)資源搶奪计露,造成數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問題
造成UI主線程的卡頓原因:
1. 把一些耗時(shí)的任務(wù)博脑,放在了UI主線程中
2. 開啟的子線程太多了,導(dǎo)致UI主線程被CPU調(diào)度的頻率下降了
3. 內(nèi)存這些配置太低了票罐,就算不做耗時(shí)操作叉趣,頻繁的操作UI界面,都讓CPU夠嗆了
多線程的安全隱患
-
資源共享
- 塊資源可能會(huì)被多個(gè)線程共享该押,也就是多個(gè)線程可能會(huì)訪問同一塊資源
- 比如多個(gè)線程訪問同一個(gè)對(duì)象疗杉、同一個(gè)變量、同一個(gè)文件
- 當(dāng)多個(gè)線程訪問同一塊資源時(shí)蚕礼,很容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問題
多個(gè)線程同時(shí)訪問同一塊資源時(shí)烟具,很容易引發(fā)資源搶奪,造成數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問題如何避免奠蹬?
—— 對(duì)資源進(jìn)行加鎖朝聋,以保證線程安全
接下來一起探索下iOS中鎖的實(shí)現(xiàn)和使用
線程鎖
鎖是什么
鎖 -- 是保證線程安全常見的同步工具。鎖是一種非強(qiáng)制的機(jī)制囤躁,每一個(gè)線程在訪問數(shù)據(jù)或者資源前冀痕,要先獲取(Acquire) 鎖,并在訪問結(jié)束之后釋放(Release)鎖狸演。如果鎖已經(jīng)被占用言蛇,其它試圖獲取鎖的線程會(huì)等待,直到鎖重新可用宵距。
為什么要有鎖腊尚?
前面說到了,鎖是用來保護(hù)線程安全的工具满哪。
可以試想一下婿斥,多線程編程時(shí),沒有鎖的情況 -- 也就是線程不安全翩瓜。
當(dāng)多個(gè)線程同時(shí)對(duì)一塊內(nèi)存發(fā)生讀和寫的操作受扳,可能出現(xiàn)意料之外的結(jié)果:
程序執(zhí)行的順序會(huì)被打亂,可能造成提前釋放一個(gè)變量,計(jì)算結(jié)果錯(cuò)誤等情況。
所以我們需要將線程不安全的代碼 “鎖” 起來兔跌。保證一段代碼或者多段代碼操作的原子性勘高,保證多個(gè)線程對(duì)同一個(gè)數(shù)據(jù)的訪問 同步 (Synchronization)。
鎖的分類講解
基本的鎖就包括三類:?旋鎖、互斥鎖华望、讀寫鎖蕊蝗。其他的?如條件鎖、遞歸鎖赖舟、信號(hào)量都是上層的封裝和實(shí)現(xiàn)
?旋鎖
線程反復(fù)檢查鎖變量是否可用蓬戚。由于線程在這一過程中保持執(zhí)行,因此是一種忙等待宾抓。一旦獲取了自旋鎖子漩,線程會(huì)一直保持該鎖,直至顯示釋放自旋鎖石洗。自旋鎖避免了進(jìn)程上下文的調(diào)度開銷幢泼,因此對(duì)于線程只會(huì)阻塞很短時(shí)間的場(chǎng)合是有效的。
- OSSpinLock
- os_unfair_lock
互斥鎖
是一種用于多線程編程中讲衫,防止兩條線程同時(shí)對(duì)同一公共資源(比如全局變量)進(jìn)行讀寫的機(jī)制缕棵。該目的通過將代碼切片成一個(gè)一個(gè)的臨界區(qū)而達(dá)成。
- NSLock
- pthread_mutex
- @synchronized
條件鎖
就是條件變量涉兽,當(dāng)進(jìn)程的某些資源要求不滿足時(shí)就進(jìn)入休眠招驴,也就是鎖住了。當(dāng)資源被分配到了枷畏,條件鎖打開别厘,進(jìn)程繼續(xù)運(yùn)行。
- NSCondition
- NSConditionLock
遞歸鎖
同一個(gè)線程可以加鎖N次而不會(huì)引發(fā)死鎖
- NSRecursiveLock
- pthread_mutex(recursive)
信號(hào)量
一種更高級(jí)的同步機(jī)制矿辽,互斥鎖可以說是semaphore在僅取值0/1時(shí)的特例丹允。信號(hào)量可以有更多的取值空間,用來實(shí)現(xiàn)更加復(fù)雜的同步袋倔,而不單單是線程間互斥。
- dispatch_semaphore
讀寫鎖
一種特殊的自旋鎖折柠。它把對(duì)共享資源的訪問者劃分成讀者和寫者宾娜,讀者只對(duì)共享資源進(jìn)行讀訪問,寫者則需要對(duì)共享資源進(jìn)行寫操作扇售。
- pthread_rwlock
- dispatch_barrier_async
鎖的性能對(duì)比:
性能從高到低排序:OSSpinLock(自旋鎖)> os_unfair_lock(自旋鎖)> NSCondition(條件鎖)> pthread_mutex(互斥鎖)> NSLock(互斥鎖)> dispatch_semaphore_t(信號(hào)量)> pthread_mutex_t(recursive)(遞歸鎖)> NSRecursiveLock(遞歸鎖)> @synchronized(互斥鎖)> NSConditionLock(條件鎖)
測(cè)試方案
循環(huán)十萬次前塔,進(jìn)行加鎖解鎖操作,計(jì)算耗時(shí)
int kc_runTimes = 100000;
<!-- OSSpinLock -->
{
OSSpinLock kc_spinlock = OS_SPINLOCK_INIT;
double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < kc_runTimes; i++) {
OSSpinLockLock(&kc_spinlock); //解鎖
OSSpinLockUnlock(&kc_spinlock);
}
double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
KCLog(@"OSSpinLock: %f ms",(kc_endTime - kc_beginTime)*1000);
}
<!-- dispatch_semaphore_t -->
{
dispatch_semaphore_t kc_sem = dispatch_semaphore_create(1);
double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < kc_runTimes; i++) {
dispatch_semaphore_wait(kc_sem, DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(kc_sem);
}
double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
KCLog(@"dispatch_semaphore_t: %f ms",(kc_endTime - kc_beginTime)*1000);
}
<!-- os_unfair_lock_lock -->
{
os_unfair_lock kc_unfairlock = OS_UNFAIR_LOCK_INIT;
double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < kc_runTimes; i++) {
os_unfair_lock_lock(&kc_unfairlock);
os_unfair_lock_unlock(&kc_unfairlock);
}
double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
KCLog(@"os_unfair_lock_lock: %f ms",(kc_endTime - kc_beginTime)*1000);
}
<!-- pthread_mutex_t -->
{
pthread_mutex_t kc_metext = PTHREAD_MUTEX_INITIALIZER;
double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < kc_runTimes; i++) {
pthread_mutex_lock(&kc_metext);
pthread_mutex_unlock(&kc_metext);
}
double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
KCLog(@"pthread_mutex_t: %f ms",(kc_endTime - kc_beginTime)*1000);
}
<!-- NSLock -->
{
NSLock *kc_lock = [NSLock new];
double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < kc_runTimes; i++) {
[kc_lock lock];
[kc_lock unlock];
}
double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
KCLog(@"NSlock: %f ms",(kc_endTime - kc_beginTime)*1000);
}
<!-- NSCondition -->
{
NSCondition *kc_condition = [NSCondition new];
double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < kc_runTimes; i++) {
[kc_condition lock];
[kc_condition unlock];
}
double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
KCLog(@"NSCondition: %f ms",(kc_endTime - kc_beginTime)*1000);
}
<!-- pthread_mutex_t -->
{
pthread_mutex_t kc_metext_recurive;
pthread_mutexattr_t attr;
pthread_mutexattr_init (&attr);
pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init (&kc_metext_recurive, &attr);
double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < kc_runTimes; i++) {
pthread_mutex_lock(&kc_metext_recurive);
pthread_mutex_unlock(&kc_metext_recurive);
}
double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
KCLog(@"PTHREAD_MUTEX_RECURSIVE: %f ms",(kc_endTime - kc_beginTime)*1000);
}
<!-- NSRecursiveLock -->
{
NSRecursiveLock *kc_recursiveLock = [NSRecursiveLock new];
double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < kc_runTimes; i++) {
[kc_recursiveLock lock];
[kc_recursiveLock unlock];
}
double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
KCLog(@"NSRecursiveLock: %f ms",(kc_endTime - kc_beginTime)*1000);
}
<!-- NSConditionLock -->
{
NSConditionLock *kc_conditionLock = [NSConditionLock new];
double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < kc_runTimes; i++) {
[kc_conditionLock lock];
[kc_conditionLock unlock];
}
double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
KCLog(@"NSConditionLock: %f ms",(kc_endTime - kc_beginTime)*1000);
}
<!-- @synchronized -->
{
double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < kc_runTimes; i++) {
@synchronized(self) {}
}
double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
KCLog(@"@synchronized: %f ms",(kc_endTime - kc_beginTime)*1000);
}
輸出結(jié)果:
<循環(huán)1000次>
2022-12-07 16:37:59.843100+0800 AKLockTest[19581:5626571] OSSpinLock: 0.033975 ms
2022-12-07 16:37:59.843228+0800 AKLockTest[19581:5626571] dispatch_semaphore_t: 0.018954 ms
2022-12-07 16:37:59.843410+0800 AKLockTest[19581:5626571] os_unfair_lock_lock: 0.044942 ms
2022-12-07 16:37:59.843596+0800 AKLockTest[19581:5626571] pthread_mutex_t: 0.064015 ms
2022-12-07 16:37:59.843744+0800 AKLockTest[19581:5626571] NSlock: 0.036001 ms
2022-12-07 16:37:59.843904+0800 AKLockTest[19581:5626571] NSCondition: 0.036001 ms
2022-12-07 16:37:59.844136+0800 AKLockTest[19581:5626571] PTHREAD_MUTEX_RECURSIVE: 0.045896 ms
2022-12-07 16:37:59.844321+0800 AKLockTest[19581:5626571] NSRecursiveLock: 0.058055 ms
2022-12-07 16:37:59.844558+0800 AKLockTest[19581:5626571] NSConditionLock: 0.116944 ms
2022-12-07 16:37:59.844850+0800 AKLockTest[19581:5626571] @synchronized: 0.167966 ms
<循環(huán)100000次>
2022-12-07 16:15:22.587874+0800 AKLockTest[19247:5602920] OSSpinLock: 0.756979 ms
2022-12-07 16:15:22.589268+0800 AKLockTest[19247:5602920] dispatch_semaphore_t: 1.297951 ms
2022-12-07 16:15:22.590893+0800 AKLockTest[19247:5602920] os_unfair_lock_lock: 1.462936 ms
2022-12-07 16:15:22.593120+0800 AKLockTest[19247:5602920] pthread_mutex_t: 2.094984 ms
2022-12-07 16:15:22.596076+0800 AKLockTest[19247:5602920] NSlock: 2.822995 ms
2022-12-07 16:15:22.599409+0800 AKLockTest[19247:5602920] NSCondition: 3.178000 ms
2022-12-07 16:15:22.604141+0800 AKLockTest[19247:5602920] PTHREAD_MUTEX_RECURSIVE: 4.518986 ms
2022-12-07 16:15:22.609652+0800 AKLockTest[19247:5602920] NSRecursiveLock: 5.347967 ms
2022-12-07 16:15:22.617816+0800 AKLockTest[19247:5602920] NSConditionLock: 8.015990 ms
2022-12-07 16:15:22.630761+0800 AKLockTest[19247:5602920] @synchronized: 12.816072 ms
自旋鎖和互斥鎖的區(qū)別和關(guān)系
- 互斥鎖 = 互斥 + 同步
互斥保證線程安全承冰,當(dāng)一條線程執(zhí)行時(shí)华弓,其他線程休眠。同步保證執(zhí)行順序困乒,多線程串行執(zhí)行 - 自旋鎖 = 互斥 + 忙等寂屏,例如do...while循環(huán)。
自旋鎖不會(huì)引起調(diào)用者睡眠,所以不會(huì)進(jìn)行線程調(diào)度迁霎,CPU時(shí)間片輪轉(zhuǎn)等耗時(shí)操作吱抚。而缺點(diǎn)是當(dāng)?shù)却龝r(shí)會(huì)消耗大量CPU資源,所以自旋鎖不適用較長(zhǎng)時(shí)間的任務(wù)
自旋鎖考廉,也是互斥鎖的一種實(shí)現(xiàn)秘豹,而 spin lock和 mutex 兩者都是為了解決某項(xiàng)資源的互斥使用,在任何時(shí)刻只能有一個(gè)保持者。但spin lock和 mutex 調(diào)度機(jī)制上有所不同昌粤。
iOS開發(fā)常用的鎖
通過上面的內(nèi)容既绕,我們對(duì)鎖有了一定的了解,接下來涮坐,讓我們一起看看iOS中常用鎖的實(shí)現(xiàn)吧
atomic(自旋鎖)
atomic 本身就有一把鎖(自旋鎖) 單寫多讀:單個(gè)線程寫入岸更,多個(gè)線程可以讀取
源碼分析:
搜索objc_setProperty的方法實(shí)現(xiàn),源碼如下
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{
bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
bool mutableCopy = (shouldCopy == MUTABLE_COPY);
reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
進(jìn)入reallySetProperty方法查看源碼
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
// atomic修飾膊升,增加了spinlock_t的鎖操作怎炊;
// 所以atomic是標(biāo)示,自身并不是鎖廓译。而atomic所謂的自旋鎖评肆,由底層代碼實(shí)現(xiàn)。
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
pthread_mutex(互斥鎖)
This check detects anytime pthread_mutex_lock(:) or pthread_mutex_unlock(:) is called with a pthread_mutex_t variable that wasn't initialized. Attempting to use an uninitialized mutex results in an error, and removes any guarantees about ordering that would exist while a mutex is locked.
使用pMutex之前一定要初始化非区,否則不生效
// 初始化屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
// 初始化鎖
pthread_mutex_init(mutex, &attr);
// 銷毀屬性
pthread_mutexattr_destroy(&attr);
/加鎖
pthread_mutex_lock(&_mutex);
//解鎖
pthread_mutex_unlock(&_mutex);
- pthread_mutexattr_settype可以設(shè)置鎖的類型(PTHREAD_MUTEX_NORMAL代表它是一把普通的鎖瓜挽,PTHREAD_MUTEX_RECURSIVE代表遞歸鎖)
- pthread_mutex_lock`給互斥量加鎖。當(dāng)另外一個(gè)線程來獲取這個(gè)鎖的時(shí)候征绸,發(fā)現(xiàn)這個(gè)鎖已經(jīng)加鎖久橙,那么這個(gè)線程就會(huì)進(jìn)入休眠狀態(tài),直到這個(gè)互斥量被解鎖管怠,線程才會(huì)從新被喚醒淆衷。
- pthread_mutex_trylock當(dāng)互斥量已經(jīng)被鎖住時(shí)調(diào)用該函數(shù)將返回錯(cuò)誤代碼EBUSY,若是當(dāng)前互斥量沒有被鎖渤弛,則會(huì)執(zhí)行和pthread_mutex_lock同樣的效果祝拯。
- pthread_mutex_unlock對(duì)互斥量進(jìn)行解鎖
NSLock(非遞歸互斥鎖)
NSLock非遞歸 互斥鎖,不能多次調(diào)用 lock方法,會(huì)造成死鎖。NSLock內(nèi)部封裝了pthread_mutex
遵循 NSLocking 協(xié)議
public protocol NSLocking {
func lock()
func unlock()
}
open class NSLock : NSObject, NSLocking {
open func `try`() -> Bool
open func lock(before limit: Date) -> Bool
@available(iOS 2.0, *)
open var name: String?
}
- lock和-unlock必須在相同的線程調(diào)用她肯,也就是說佳头,他們必須在同一個(gè)線程中成對(duì)調(diào)用
- 除 lock 和 unlock 方法外,nslock 還提供了 trylock 和 lockbeforedate:兩個(gè)方法晴氨。
- trylock 并不會(huì)阻塞線程康嘉,trylock能加鎖返回 yes,不能加鎖返回 no籽前,然后都會(huì)執(zhí)行后續(xù)代碼亭珍。
- trylock 和 lock 使用場(chǎng)景:當(dāng)前線程鎖失敗敷钾,也可以繼續(xù)其它任務(wù),用 trylock 合適块蚌;當(dāng)前線程只有鎖成功后闰非,才會(huì)做一些有意義的工作,那就 lock峭范,沒必要輪詢 trylock财松。
- lockbeforedate: 方法會(huì)在所指定 date 之前嘗試加鎖,會(huì)阻塞線程纱控,如果在指定時(shí)間之前都不能加鎖辆毡,則返回 no,指定時(shí)間之前能加鎖甜害,則返回 yes舶掖。
- 由于是互斥鎖,當(dāng)一個(gè)線程進(jìn)行訪問的時(shí)候尔店,該線程獲得鎖眨攘,其他線程進(jìn)行訪問的時(shí)候,將被操作系統(tǒng)掛起嚣州,直到該線程釋放鎖鲫售,其他線程才能對(duì)其進(jìn)行訪問,從而卻確保了線程安全该肴。但是如果連續(xù)鎖定兩次情竹,則會(huì)造成死鎖問題。
NSLock原理分析
private typealias _MutexPointer = UnsafeMutablePointer<pthread_mutex_t>
private typealias _RecursiveMutexPointer = UnsafeMutablePointer<pthread_mutex_t>
private typealias _ConditionVariablePointer = UnsafeMutablePointer<pthread_cond_t>
open class NSLock: NSObject, NSLocking {
internal var mutex = _MutexPointer.allocate(capacity: 1)
public override init() {
//初始化互斥鎖匀哄,沒有遞歸類型的屬性
pthread_mutex_init(mutex, nil)
}
open func lock() {
//使用mutex加鎖
pthread_mutex_lock(mutex)
}
open func unlock() {
//使用mutex解鎖
pthread_mutex_unlock(mutex)
}
}
open func `try`() -> Bool {
return pthread_mutex_trylock(mutex) == 0
}
open func lock(before limit: Date) -> Bool {
if pthread_mutex_trylock(mutex) == 0 {
return true
}
return timedLock(mutex: mutex, endTime: limit, using: timeoutCond, with: timeoutMutex)
}
private func timedLock(mutex: _MutexPointer, endTime: Date,
using timeoutCond: _ConditionVariablePointer,
with timeoutMutex: _MutexPointer) -> Bool {
var timeSpec = timeSpecFrom(date: endTime)
while var ts = timeSpec {
let lockval = pthread_mutex_lock(timeoutMutex)
precondition(lockval == 0)
let waitval = pthread_cond_timedwait(timeoutCond, timeoutMutex, &ts)
precondition(waitval == 0 || waitval == ETIMEDOUT)
let unlockval = pthread_mutex_unlock(timeoutMutex)
precondition(unlockval == 0)
if waitval == ETIMEDOUT {
return false
}
let tryval = pthread_mutex_trylock(mutex)
precondition(tryval == 0 || tryval == EBUSY)
if tryval == 0 { // The lock was obtained.
return true
}
// pthread_cond_timedwait didn't timeout so wait some more.
timeSpec = timeSpecFrom(date: endTime)
}
return false
}
通過源碼可知驗(yàn)證 NSLock 是對(duì) pthread 中互斥鎖 的封裝秦效。
其他都好理解,這里列一下 timedLock() 的實(shí)現(xiàn)流程:
1涎嚼、設(shè)定超時(shí)時(shí)間阱州,進(jìn)入while循環(huán)。
2铸抑、pthread_cond_timedwait()在本次循環(huán)中計(jì)時(shí)等待贡耽,線程進(jìn)入休眠
3、等待超時(shí)鹊汛,直接返回 false;
4阱冶、如果等待沒有超時(shí)刁憋,期間鎖被釋放,線程會(huì)被喚醒木蹬,再次嘗試獲取鎖 pthread_mutex_trylock(),如果獲取成功返回true
5至耻、即沒有超時(shí),被喚醒后也沒有成功獲取到鎖(被其他線程搶先獲得鎖),重新計(jì)算超時(shí)時(shí)間進(jìn)入下一次while循環(huán)
NSLock局限性
let lock = NSLock()
DispatchQueue.global().async {
func test(value: Int) {
lock.lock()
if value > 0 {
print("current value = \(value)")
test(value: value - 1)
}
lock.unlock()
}
test(value: 10)
}
====================================================
current value = 10
遞歸調(diào)用 test 輸出尘颓,正常應(yīng)該是10 9 8 .....,但是實(shí)際只輸出了10.
原因分析:因?yàn)樵趇f 之前進(jìn)行鎖了之后走触,在if 里面有遞歸調(diào)用了 test 方法,又一次進(jìn)來有鎖了疤苹,這樣被鎖了多次都沒去執(zhí)行解鎖一直處理阻塞互广。
這里應(yīng)該換成遞歸鎖.
例如:NSRecursiveLock * lock = [[NSRecursiveLock alloc] init];
NSRecursiveLock(遞歸鎖)
NSRecursiveLock內(nèi)部封裝了pthread_mutex
let lock = NSRecursiveLock()
DispatchQueue.global().async {
func test(value: Int) {
lock.lock()
if value > 0 {
print("current value = \(value)")
test(value: value - 1)
}
lock.unlock()
}
test(value: 10)
}
====================================================
current value = 10
current value = 9
current value = 8
current value = 7
current value = 6
current value = 5
current value = 4
current value = 3
current value = 2
current value = 1
那為什么NSRecursiveLock就可以支持可遞歸加鎖呢?why 卧土?為什么呢惫皱!請(qǐng)繼續(xù)往下看:
public override init() {
super.init()
var attrib = pthread_mutexattr_t()
withUnsafeMutablePointer(to: &attrib) { attrs in
pthread_mutexattr_init(attrs)
// 設(shè)置為遞歸屬性
pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))
pthread_mutex_init(mutex, attrs)
}
pthread_cond_init(timeoutCond, nil)
pthread_mutex_init(timeoutMutex, nil)
}
deinit {
pthread_mutex_destroy(mutex)
mutex.deinitialize(count: 1)
mutex.deallocate()
deallocateTimedLockData(cond: timeoutCond, mutex: timeoutMutex)
}
open func lock() {
pthread_mutex_lock(mutex)
}
open func unlock() {
pthread_mutex_unlock(mutex)
// Wakeup any threads waiting in lock(before:)
pthread_mutex_lock(timeoutMutex)
pthread_cond_broadcast(timeoutCond)
pthread_mutex_unlock(timeoutMutex)
}
open func `try`() -> Bool {
return pthread_mutex_trylock(mutex) == 0
}
open func lock(before limit: Date) -> Bool {
if pthread_mutex_trylock(mutex) == 0 {
return true
}
return timedLock(mutex: mutex, endTime: limit, using: timeoutCond, with: timeoutMutex)
}
#define PTHREAD_MUTEX_NORMAL 0 //普通非遞歸鎖
#define PTHREAD_MUTEX_ERRORCHECK 1
#define PTHREAD_MUTEX_RECURSIVE 2 //遞歸鎖
#define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL
- NSRecursiveLock是對(duì)pthread_mutex的封裝
- NSRecursiveLock支持可遞歸加鎖,在init方法中尤莺,通過pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))進(jìn)行了遞歸的設(shè)置
NSRecursiveLock局限性
如果在遞歸的基礎(chǔ)上加上多線程旅敷,使用NSRecursiveLock加鎖,結(jié)果會(huì)怎么樣颤霎?
let lock = NSRecursiveLock()
let queue = DispatchQueue(label: "queue.name", attributes: .concurrent)
for i in 0 ..< 10 {
queue.async {
func test(value: Int) {
lock.lock()
if value > 0 {
print("current value = \(value)")
test(value:value - 1)
}
lock.unlock()
}
test(value: 10)
}
}
====================================================
current value = 10
多線程訪問的遞歸操作媳谁。此時(shí)運(yùn)行程序,我們會(huì)發(fā)現(xiàn)友酱,程序會(huì)崩潰在[lock unlock]這個(gè)方法晴音,這是因?yàn)榘l(fā)生了死鎖。多線程加鎖解鎖的時(shí)候粹污,會(huì)出現(xiàn)互相等待解鎖的情況段多,比如當(dāng)線程2調(diào)用lock方法,在還沒unlock的時(shí)候壮吩,線程3也調(diào)用了lock方法进苍,這時(shí)候線程2已經(jīng)lock,所以線程3需要等待線程2來unlock鸭叙,線程2要等到線程3來unlock
這里應(yīng)該換成多線程遞歸鎖:
例如:@synchonized
@synchonized(互斥遞歸鎖)
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
@synchronized (appDelegateClassName) {
}
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
在使用過程中你是否也有這樣幾個(gè)疑問觉啊?
- @synchronized (參數(shù)),這里的參數(shù)該傳什么沈贝?一般會(huì)傳self杠人,那么傳self的意義是什么?
- @synchronized (參數(shù))宋下,參數(shù)可以傳nil嗎嗡善?傳nil的話會(huì)有什么結(jié)果?
- @synchronized (參數(shù))可以起到加鎖的效果学歧,并且能夠起到遞歸可重入的效果罩引,遞歸可重入是什么樣的結(jié)構(gòu)?簡(jiǎn)單理解遞歸可重入就是鎖里面嵌套鎖
源碼解析
- clang編譯出C++代碼
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main.cpp
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
{
id _rethrow = 0;
id _sync_obj = (id)appDelegateClassName;
objc_sync_enter(_sync_obj);
try {
struct _SYNC_EXIT {
_SYNC_EXIT(id arg) : sync_exit(arg) {}
~_SYNC_EXIT() {
objc_sync_exit(sync_exit);
}
id sync_exit;
}
_sync_exit(_sync_obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_qy__11tvxy50djf0ztzh2z1vg2m0000gn_T_main_d0f51c_mi_0);
} catch (id e) {_rethrow = e;}
{ struct _FIN { _FIN(id reth) : rethrow(reth) {}
~_FIN() { if (rethrow) objc_exception_throw(rethrow); }
id rethrow;
} _fin_force_rethow(_rethrow);}
}
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
通過分析main.cpp文件枝笨,發(fā)現(xiàn)@synchronized (appDelegateClassName){} 執(zhí)行了objc_sync_enter與objc_sync_exit方法
- 調(diào)用_sync_exit傳入_sync_obj袁铐,相當(dāng)于調(diào)用結(jié)構(gòu)體的構(gòu)造函數(shù)和析構(gòu)函數(shù)揭蜒。構(gòu)造函數(shù)中沒有代碼,而析構(gòu)函數(shù)中調(diào)用objc_sync_exit函數(shù)剔桨,傳入的sync_exit等同于_sync_obj
- 使用objc_sync_enter(_sync_obj)函數(shù)屉更,進(jìn)行加鎖
- 使用objc_sync_exit(_sync_obj)函數(shù),進(jìn)行解鎖
- 使用try...catch洒缀,說明鎖的使用有可能出現(xiàn)異常
- 查看objc_sync_enter, objc_sync_exit函數(shù)
# define BREAKPOINT_FUNCTION(prototype) \
OBJC_EXTERN __attribute__((noinline, used, visibility("hidden"))) \
prototype { asm(""); }
BREAKPOINT_FUNCTION(
void objc_sync_nil(void)
);
// Begin synchronizing on 'obj'.
// Allocates recursive mutex associated with 'obj' if needed.
// Returns OBJC_SYNC_SUCCESS once lock is acquired.
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, ACQUIRE);
ASSERT(data);
data->mutex.lock();
} else {
// @synchronized(nil) does nothing
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil();
}
return result;
}
int objc_sync_exit(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, RELEASE);
if (!data) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
} else {
bool okay = data->mutex.tryUnlock();
if (!okay) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
}
}
} else {
// @synchronized(nil) does nothing
}
return result;
}
- 如果obj存在瑰谜,則調(diào)用id2data方法獲取對(duì)應(yīng)的SyncData,對(duì)threadCount帝洪、lockCount進(jìn)行遞減操作似舵。
- 如果obj為nil,什么也不做葱峡。
objc_sync_enter加鎖時(shí)傳入nil對(duì)象時(shí)執(zhí)行 objc_sync_nil()砚哗,相當(dāng)于將void objc_sync_nil(void)傳入宏,等同于void objc_sync_nil(void) { }無效代碼不進(jìn)行加鎖操作砰奕。這里就解答了上面?zhèn)魅雗il的問題蛛芥,@synchronized (參數(shù))參數(shù)傳nil,什么都不做does nothing - 在objc_sync_enter中军援,對(duì)SyncData對(duì)象中的mutex仅淑,調(diào)用lock進(jìn)行加鎖;
- 在objc_sync_exit中胸哥,對(duì)SyncData對(duì)象中的mutex涯竟,調(diào)用tryUnlock進(jìn)行解鎖。
** synchronized數(shù)據(jù)結(jié)構(gòu)**
typedef struct alignas(CacheLineSize) SyncData {
// SyncData屬于單項(xiàng)鏈表
struct SyncData* nextData;//指向下一條數(shù)據(jù)
DisguisedPtr<objc_object> object;//偽裝空厌,用于封裝類型
int32_t threadCount; // number of THREADS using this block 多線程訪問庐船,記錄多線程操作數(shù)
recursive_mutex_t mutex;//遞歸鎖,可以遞歸使用
} SyncData;
static SyncData* id2data(id object, enum usage why)
{
//1嘲更、傳入object筐钟,從哈希表中獲取數(shù)據(jù)
//傳入object,從哈希表中獲取lock赋朦,用于保證分配SyncData代碼的線程安全
spinlock_t *lockp = &LOCK_FOR_OBJ(object);
//傳入object篓冲,從哈希表中獲取SyncData的地址,等同于SyncList
SyncData **listp = &LIST_FOR_OBJ(object);
SyncData* result = NULL;
#if SUPPORT_DIRECT_THREAD_KEYS
bool fastCacheOccupied = NO;
//2宠哄、在當(dāng)前線程的tls(線程局部存儲(chǔ))中尋找
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY); // 通過KVC方式對(duì)線程進(jìn)行獲取 線程綁定的data
if (data) {
fastCacheOccupied = YES;
//SyncData中的對(duì)象和傳入的對(duì)象相同
if (data->object == object) {
//可以進(jìn)入到這里壹将,應(yīng)該是同一線程中對(duì)同一對(duì)象,進(jìn)行嵌套@synchronized
uintptr_t lockCount;
result = data;
lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
if (result->threadCount <= 0 || lockCount <= 0) {
_objc_fatal("id2data fastcache is buggy");
}
switch(why) {
case ACQUIRE: {
//鎖的次數(shù)+1
lockCount++;
//存儲(chǔ)到tls中
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
break;
}
case RELEASE:
//鎖的次數(shù)-1
lockCount--;
//存儲(chǔ)到tls中
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
if (lockCount == 0) {
//刪除tls線程局部存儲(chǔ)
tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
//對(duì)SyncData對(duì)象的threadCount進(jìn)行-1毛嫉,因?yàn)楫?dāng)前線程中的對(duì)象已經(jīng)解鎖
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
break;
}
return result;
}
}
#endif
//3瞭恰、tls中未找到,在各自線程的緩存中查找
SyncCache *cache = fetch_cache(NO);
if (cache) {
unsigned int I;
//遍歷緩存
for (i = 0; i < cache->used; i++) {
SyncCacheItem *item = &cache->list[I];
//item中的對(duì)象和傳入的對(duì)象不一致狱庇,跳過
if (item->data->object != object) continue;
result = item->data;
if (result->threadCount <= 0 || item->lockCount <= 0) {
_objc_fatal("id2data cache is buggy");
}
switch(why) {
case ACQUIRE:
//鎖的次數(shù)+1
item->lockCount++;
break;
case RELEASE:
//鎖的次數(shù)-1
item->lockCount--;
if (item->lockCount == 0) {
//從緩存中刪除
cache->list[i] = cache->list[--cache->used];
//對(duì)SyncData對(duì)象的threadCount進(jìn)行-1惊畏,因?yàn)楫?dāng)前線程中的對(duì)象已經(jīng)解鎖
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
//加鎖,保證下面分配SyncData代碼的線程安全(第一次進(jìn)來密任,所有緩存都找不到)
lockp->lock();
{
SyncData* p;
SyncData* firstUnused = NULL;
//4颜启、遍歷SyncList,如果無法遍歷浪讳,證明當(dāng)前object第一次進(jìn)入缰盏,需要分配新的SyncData
for (p = *listp; p != NULL; p = p->nextData) {
//遍歷如果鏈表中存在SyncData的object和傳入的object相等
if ( p->object == object ) {
//將p賦值給result
result = p;
//對(duì)threadCount進(jìn)行+1
OSAtomicIncrement32Barrier(&result->threadCount);
//跳轉(zhuǎn)至done
goto done;
}
//找到一個(gè)未使用的SyncData
if ( (firstUnused == NULL) && (p->threadCount == 0) )
firstUnused = p;
}
//未找到與對(duì)象關(guān)聯(lián)的SyncData,如果當(dāng)前非ACQUIRE邏輯淹遵,直接進(jìn)入done
if ( (why == RELEASE) || (why == CHECK) )
goto done;
//從SyncList中找到未使用的SyncData口猜,進(jìn)行覆蓋
if ( firstUnused != NULL ) { //第一次進(jìn)來,沒有找到
//賦值給result
result = firstUnused;
result->object = (objc_object *)object;
result->threadCount = 1;
//跳轉(zhuǎn)至done
goto done;
}
}
//5透揣、分配一個(gè)新的SyncData并添加到SyncList中济炎,并且支持內(nèi)存對(duì)齊
posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));
result->object = (objc_object *)object;
result->threadCount = 1;
new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
//使用單鏈表頭插法,新增節(jié)點(diǎn)總是插在頭部
result->nextData = *listp;
*listp = result;
done:
//解鎖辐真,保證上面分配SyncData代碼的線程安全
lockp->unlock();
if (result) {
//一些錯(cuò)誤處理须尚,應(yīng)該只有ACQUIRE時(shí),產(chǎn)生新SyncData時(shí)進(jìn)入這里
//所有的RELEASE和CHECK和遞歸ACQUIRE侍咱,都應(yīng)該由上面的線程緩存處理
if (why == RELEASE) {
return nil;
}
if (why != ACQUIRE) _objc_fatal("id2data is buggy");
if (result->object != object) _objc_fatal("id2data is buggy");
//6耐床、保存到tls線程或者緩存中
if (!fastCacheOccupied) {
//保存到當(dāng)前線程的tls中
tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
} else
#endif
{
//tls還在占用,保存到緩存
if (!cache) cache = fetch_cache(YES);
cache->list[cache->used].data = result;
cache->list[cache->used].lockCount = 1;
cache->used++;
}
}
return result;
}
主要步驟
-
傳入object從sDataLists哈希表中獲取數(shù)據(jù), sDataLists是一個(gè)全局哈希表楔脯,存放的是SyncList撩轰,SyncList綁定了需要加鎖的object,采用的是拉鏈法昧廷,拉鏈的是SyncData
- 在當(dāng)前線程的tls(線程局部存儲(chǔ))中尋找
- tls中未找到堪嫂,在各自線程的緩存中查找
- 遍歷SyncList找到SyncData,相當(dāng)于在所有線程中尋址
- 分配一個(gè)新的SyncData并添加到SyncList中
- 將SyncData保存到tls線程或者緩存中
synchronized的注意事項(xiàng)
- @synchronized為遞歸互斥鎖麸粮,lockCount表示可遞歸使用溉苛,threadCount表示可在多線程中使用;
- 使用@synchronized時(shí)弄诲,不能傳入nil愚战,使用nil鎖的功能無法生效;
在日常開發(fā)中齐遵,經(jīng)常會(huì)傳入self寂玲,它的好處可以保證生命周期同步,對(duì)象不會(huì)提前釋放梗摇; - 不能使用非OC對(duì)象作為加鎖對(duì)象拓哟,因?yàn)槠鋙bject的參數(shù)為id類型;
- 底層的緩存和鏈表都使用循環(huán)遍歷查找伶授,所以性能偏低断序。但開發(fā)中使用方便簡(jiǎn)單癞季,并且不用解鎖辛燥,所以使用頻率較高。
OSSpinLock/os_unfair_lock(自旋鎖)
OSSpinLock等待鎖的線程會(huì)處于忙等(busy-wait)狀態(tài),一直占用著CPU資源
OSSpinLock在iOS10之后棄用了奈应,使用os_unfair_lock代替
OSSpinLock不再安全
系統(tǒng)使用基于優(yōu)先級(jí)(priority)的搶占式調(diào)度算法時(shí)录平,high priority線程始終會(huì)在low priority線程前執(zhí)行塔鳍,可能會(huì)出現(xiàn)優(yōu)先級(jí)反轉(zhuǎn)的問題
舉個(gè)??:
task1:high priority汉矿,要使用資源data
task2:low priority,要使用資源data
spin_lock:一把自旋鎖
1.先執(zhí)行task2阵苇,拿到spin_lock壁公,由于任務(wù)比較耗時(shí),未執(zhí)行完绅项,繼續(xù)持有鎖紊册。
2.緊接著執(zhí)行task1,等待鎖釋放趁怔。由于優(yōu)先級(jí)高湿硝,得到系統(tǒng)分配更多的CPU。
3.task1得不到CPU時(shí)間润努,無法正常執(zhí)行关斜,因此形成短暫死鎖。
os_unfair_lock
os_unfair_lock是一種底層鎖铺浇,用于取代OSSpinLock痢畜,嘗試獲取已加鎖的線程無需忙等,解鎖時(shí)由內(nèi)核喚醒鳍侣。os_unfair_lock鎖的線程會(huì)處于休眠狀態(tài)丁稀,并非忙等
private var lock: os_unfair_lock = os_unfair_lock_s()
os_unfair_lock_lock(&lock)
//dosomthing
os_unfair_lock_unlock(&lock)
- 鎖的實(shí)現(xiàn)依賴鎖值和擁有鎖進(jìn)程的地址,因此線程倚聚、進(jìn)程不能通過共享线衫、映射內(nèi)存地址獲取os_unfair_lock
- 如果鎖已經(jīng)加鎖,os_unfair_lock_lock()會(huì)休眠惑折,解鎖后由內(nèi)核喚醒
- os_unfair_lock_trylock()遇到已經(jīng)加鎖的鎖授账,會(huì)直接返回 false。不要在循環(huán)中調(diào)用os_unfair_lock_trylock()函數(shù)惨驶,os_unfair_lock_lock()函數(shù)已經(jīng)實(shí)現(xiàn)了循環(huán)功能
- 必須在加鎖的線程調(diào)用os_unfair_lock_unlock()解鎖白热,從其他線程解鎖會(huì)產(chǎn)生運(yùn)行時(shí)錯(cuò)誤
信號(hào)量dispatch_semaphore_t
dispatch_semaphore是GCD用來同步的一種方式,當(dāng)信號(hào)量設(shè)置為1的時(shí)候相當(dāng)于一把同步鎖粗卜,與他相關(guān)的共有三個(gè)函數(shù)屋确,分別是:
- dispatch_semaphore_create(創(chuàng)建一個(gè)semaphore)
- dispatch_semaphore_signal(發(fā)送一個(gè)信號(hào))
- dispatch_semaphore_wait(等待信號(hào))
#define INIT(...) self = super.init; \
if (!self) return nil; \
__VA_ARGS__; \
if (!_arr) return nil; \
_lock = dispatch_semaphore_create(1); \
return self;
#define LOCK(...) dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \
__VA_ARGS__; \
dispatch_semaphore_signal(_lock);
讀寫鎖
多讀單寫,即,寫入操作只能串行執(zhí)行攻臀,且寫入時(shí)焕数,不能讀取,而讀取需支持多線程操作茵烈,且讀取時(shí)百匆,不能寫入
實(shí)現(xiàn)方式這里就提供兩種:pthread_rwlock、GCD的barrier來實(shí)現(xiàn)
** pthread_rwlock**
pthread_rwlock_init呜投,初始化鎖
pthread_rwlock_rdlock,阻斷性的讀鎖定讀寫鎖
pthread_rwlock_tryrdlock存璃,非阻斷性的讀鎖定讀寫鎖
pthread_rwlock_wrlock仑荐,阻斷性的寫鎖定讀寫鎖
pthread_rwlock_trywrlock,非阻斷性的寫鎖定讀寫鎖
pthread_rwlock_unlock纵东,解鎖
pthread_rwlock_destroy粘招,銷毀鎖釋放
使用如下
//初始化pthread讀寫鎖
- (void)setupPhreadRW {
pthread_rwlock_init(&_lock, NULL);
//使用完畢銷毀讀寫鎖
//pthread_rwlock_destroy(&_lock);
}
#pragma mark --通過pthread讀寫鎖來設(shè)置
- (void)setLock1:(NSString *)lock1 {
pthread_rwlock_wrlock(&_lock);
_lock1 = lock1;
pthread_rwlock_unlock(&_lock);
}
- (NSString *)lock1 {
NSString *lock1 = nil;
pthread_rwlock_rdlock(&_lock);
lock1 = [_lock1 copy]; //copy到新的地址,避免解鎖后拿到舊值
pthread_rwlock_unlock(&_lock);
return lock1;
}
GCD的barrier讀寫鎖
GCD的barrier柵欄功能在一個(gè)新創(chuàng)建的隊(duì)列中,barrier功能可以保證偎球,在他之前的異步隊(duì)列執(zhí)行完畢才指定barrier中間的內(nèi)容洒扎,且還能保證barrier執(zhí)行完畢后,才之后barrier之后的任務(wù)衰絮,且一個(gè)隊(duì)列可以有多個(gè)barrier
因此此特性可以用于完成一個(gè)讀寫鎖功能袍冷,即 barrier的代碼塊作為 寫入操作模塊
- (void)setupGCDRW {
_queue = dispatch_queue_create("RWLockQueue", DISPATCH_QUEUE_CONCURRENT);
}
#pragma mark --通過GCD的barrier柵欄功能實(shí)現(xiàn)
//通過GCD的barrier柵欄功能實(shí)現(xiàn),缺點(diǎn)是需要借助自定義隊(duì)列實(shí)現(xiàn)猫牡,且get方法無法重寫系統(tǒng)的胡诗,只能以回調(diào)的方式獲取值
//barrier功能使用global隊(duì)列會(huì)失效,全局隊(duì)列是無法阻塞的淌友,里面有系統(tǒng)的一些任務(wù)執(zhí)行
- (void)setLock2:(NSString *)lock2 {
dispatch_barrier_async(_queue, ^{
self->_lock2 = lock2;
});
}
- (void)getLock2WithBlock:(void(^)(NSString *))block {
dispatch_async(_queue, ^{
block(self->_lock2);
});
}