iOS 多線程安全

多線程知識(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ú)立。

wecom-temp-a691821dca665276208b9c85be7a5ee8.png

線程

  • 程是進(jìn)程的基本執(zhí)行單元撩幽,一個(gè)進(jìn)程的所有任務(wù)都在線程中執(zhí)行
  • 進(jìn)程要想執(zhí)行任務(wù)库继,必須得有線程,進(jìn)程至少要有一條線程
  • 程序啟動(dòng)會(huì)默認(rèn)開啟一條線程窜醉,這條線程被稱為主線程或者UI線程
線程生命周期.png

多線程

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

iOS多線程.jpeg
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使用
  1. 第一種創(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ì)將其釋放
  1. 第二種創(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ì)象就代表一條線程


Thread線程測(cè)試.png
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)建方式:
  1. NSInvocationOperation(swift不支持): 此類調(diào)用選擇器方法(selector), 在方法里面編寫任務(wù)代碼.

  2. NSBlockOperation (swift對(duì)應(yīng)BlockOperation): 此類采用block方式, 在block中編寫任務(wù)代碼.

let operation = BlockOperation.init {
        print("\(#function), thread:\(Thread.current)")
}
  1. 定義繼承自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í)行方式:
  1. 不添加到隊(duì)列, 手動(dòng)調(diào)用operation的start方法.不開啟新線程在主線程執(zhí)行

  2. 添加到隊(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ì)列有四種方式:
  1. addOperation:
    添加一個(gè)現(xiàn)有的Operation (或者其子類).
  2. addOperations:waitUntilFinished:
    可添加多個(gè)現(xiàn)有的Operation (或者其子類), 可設(shè)置等待所有操作完成后方可繼續(xù)往下執(zhí)行.
  3. addOperationWithBlock:
    直接添加一個(gè)block
  4. 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)

  1. 能適當(dāng)提高程序的執(zhí)行效率
  2. 能適當(dāng)提高資源的利用率,如CPU欢策、內(nèi)存
  3. 線程上的任務(wù)執(zhí)行完成后吆寨,線程會(huì)自動(dòng)銷毀

多線程的缺點(diǎn)

  1. 創(chuàng)建線程是有開銷的,iOS下主要成本包括:內(nèi)核數(shù)據(jù)結(jié)構(gòu)(1KB)踩寇、椬那澹空間(子線程512KB 、主線程1MB俺孙,也可以使用-setStackSize:設(shè)置辣卒,但必須是4K的倍數(shù)掷贾,而且最小是16K),創(chuàng)建線程大約需要90毫秒的創(chuàng)建時(shí)間
  2. 如果開啟大量線程荣茫,會(huì)降低程序的性能
  3. 線程越多胯盯,CPU在調(diào)度線程上的開銷就越大
  4. 多個(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ì)比:


1212147-ef64a6b791be6074.png

性能從高到低排序: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?
}
  1. lock和-unlock必須在相同的線程調(diào)用她肯,也就是說佳头,他們必須在同一個(gè)線程中成對(duì)調(diào)用
  2. 除 lock 和 unlock 方法外,nslock 還提供了 trylock 和 lockbeforedate:兩個(gè)方法晴氨。
  3. trylock 并不會(huì)阻塞線程康嘉,trylock能加鎖返回 yes,不能加鎖返回 no籽前,然后都會(huì)執(zhí)行后續(xù)代碼亭珍。
  4. trylock 和 lock 使用場(chǎng)景:當(dāng)前線程鎖失敗敷钾,也可以繼續(xù)其它任務(wù),用 trylock 合適块蚌;當(dāng)前線程只有鎖成功后闰非,才會(huì)做一些有意義的工作,那就 lock峭范,沒必要輪詢 trylock财松。
  5. lockbeforedate: 方法會(huì)在所指定 date 之前嘗試加鎖,會(huì)阻塞線程纱控,如果在指定時(shí)間之前都不能加鎖辆毡,則返回 no,指定時(shí)間之前能加鎖甜害,則返回 yes舶掖。
  6. 由于是互斥鎖,當(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   
  1. NSRecursiveLock是對(duì)pthread_mutex的封裝
  2. 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方法

  1. 調(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
  2. 使用objc_sync_enter(_sync_obj)函數(shù)屉更,進(jìn)行加鎖
  3. 使用objc_sync_exit(_sync_obj)函數(shù),進(jìn)行解鎖
  4. 使用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;
}
  1. 如果obj存在瑰谜,則調(diào)用id2data方法獲取對(duì)應(yīng)的SyncData,對(duì)threadCount帝洪、lockCount進(jìn)行遞減操作似舵。
  2. 如果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
  3. 在objc_sync_enter中军援,對(duì)SyncData對(duì)象中的mutex仅淑,調(diào)用lock進(jìn)行加鎖;
  4. 在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;
}

主要步驟

  1. 傳入object從sDataLists哈希表中獲取數(shù)據(jù), sDataLists是一個(gè)全局哈希表楔脯,存放的是SyncList撩轰,SyncList綁定了需要加鎖的object,采用的是拉鏈法昧廷,拉鏈的是SyncData


    sDataLists.png
  2. 在當(dāng)前線程的tls(線程局部存儲(chǔ))中尋找
  3. tls中未找到堪嫂,在各自線程的緩存中查找
  4. 遍歷SyncList找到SyncData,相當(dāng)于在所有線程中尋址
  5. 分配一個(gè)新的SyncData并添加到SyncList中
  6. 將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)
  1. 鎖的實(shí)現(xiàn)依賴鎖值和擁有鎖進(jìn)程的地址,因此線程倚聚、進(jìn)程不能通過共享线衫、映射內(nèi)存地址獲取os_unfair_lock
  2. 如果鎖已經(jīng)加鎖,os_unfair_lock_lock()會(huì)休眠惑折,解鎖后由內(nèi)核喚醒
  3. 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)功能
  4. 必須在加鎖的線程調(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);
    });
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末煌恢,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子震庭,更是在濱河造成了極大的恐慌瑰抵,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件器联,死亡現(xiàn)場(chǎng)離奇詭異二汛,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)主籍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門习贫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人千元,你說我怎么就攤上這事苫昌。” “怎么了幸海?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵祟身,是天一觀的道長(zhǎng)奥务。 經(jīng)常有香客問我,道長(zhǎng)袜硫,這世上最難降的妖魔是什么氯葬? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮婉陷,結(jié)果婚禮上帚称,老公的妹妹穿的比我還像新娘。我一直安慰自己秽澳,他們只是感情好闯睹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著担神,像睡著了一般楼吃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上妄讯,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天孩锡,我揣著相機(jī)與錄音,去河邊找鬼亥贸。 笑死躬窜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的砌函。 我是一名探鬼主播斩披,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼讹俊!你這毒婦竟也來了垦沉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤仍劈,失蹤者是張志新(化名)和其女友劉穎厕倍,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贩疙,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡讹弯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了这溅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片组民。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖悲靴,靈堂內(nèi)的尸體忽然破棺而出臭胜,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布耸三,位于F島的核電站乱陡,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏仪壮。R本人自食惡果不足惜憨颠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望积锅。 院中可真熱鬧爽彤,春花似錦、人聲如沸乏沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蹬跃。三九已至,卻和暖如春铆铆,著一層夾襖步出監(jiān)牢的瞬間蝶缀,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來泰國(guó)打工薄货, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留翁都,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓谅猾,卻偏偏與公主長(zhǎng)得像柄慰,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子税娜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 1. 什么情況下會(huì)有線程隱患坐搔? 我們?cè)谑褂枚嗑€程技術(shù)帶來的便利的同時(shí),也需要考慮下多線程所帶來的隱患敬矩。比如概行,我們可...
    沉江小魚閱讀 814評(píng)論 0 11
  • 前沿:什么叫作多線程安全凳忙? 我們?cè)诙嗑€程訪問共享資源的時(shí)候,不會(huì)出現(xiàn)意想不到的結(jié)果禽炬。 我們先來才想一下下面的代碼輸...
    皮皮他爸閱讀 504評(píng)論 0 1
  • 什么會(huì)給多線程的安全造成隱患涧卵? 有了多線程技術(shù)支持,我們可以并發(fā)的進(jìn)行多個(gè)任務(wù)腹尖,因此同一塊資源就有可能在多個(gè)線程中...
    RUNNING_NIUER閱讀 3,325評(píng)論 6 32
  • 聲明:本文主要參考文章iOS多線程安全-13種線程鎖[https://juejin.cn/post/6844903...
    阿飛小伙子閱讀 759評(píng)論 0 0
  • 目錄 1柳恐、為什么要線程安全 2、自旋鎖和互斥鎖 3、鎖的類型1胎撤、OSSpinLock2晓殊、os_unfair_loc...
    SunshineBrother閱讀 1,163評(píng)論 0 20