關(guān)于“鎖”的一些事兒

多線程在日常開發(fā)中會(huì)時(shí)不時(shí)遇到少欺。首先APP會(huì)有一個(gè)主線程(UI線程),處理一些UI相關(guān)的邏輯欣福。但是牽扯到網(wǎng)絡(luò)、數(shù)據(jù)庫等耗時(shí)的操作需要新開辟線程處理焦履,避免“卡住”主線程拓劝,給用戶留下不好的印象。多線程的好處不言而喻:幕后做事嘉裤,不影響明面上的事兒郑临。但是也有一些需要注意的地方,其中“資源搶奪”就是需要特別注意的一點(diǎn)价脾。

資源搶奪

所謂資源搶奪就是多個(gè)線程同時(shí)操作一個(gè)數(shù)據(jù)牧抵。

下面這段代碼很簡單,就是往Preferences文件中存一個(gè)值侨把,并讀取出來輸出

    override func viewDidLoad() {
        super.viewDidLoad()

        // 寫
        saveData(key: identifier1, value: 1)
        // 讀
        let result1 = readData(key: identifier1)
        print(" result1: \(String(describing: result1))")
        
        // 寫
        saveData(key: identifier2, value: 2)
        // 讀
        print("result2: \(String(describing: result1))")
    }

輸出結(jié)果毫無疑問是
result1: 1
result2: 2

如果這么寫

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // 線程一操作
        let queue1 = DispatchQueue(label: "queue1");
        queue1.async {[weak self] in
            // 寫
            self?.saveData(key: identifier, value: 1)
            // 讀
            let result = self?.readData(key: identifier) ?? ""
            print("queue1 result: \(String(describing: result))")
        }
        
        // 線程二操作
        let queue2 = DispatchQueue(label: "queue2");
        queue2.async {[weak self] in
            // 寫
            self?.saveData(key: identifier, value: 2)
            // 讀
            let result = self?.readData(key: identifier) ?? ""
            print("queue2 result: \(String(describing: result))")
        }
    }

通常會(huì)認(rèn)為 queue1 先輸出 1犀变, 然后 queue2 再輸出 2。 但實(shí)際上...
循環(huán)打印的結(jié)果
queue1 result: 1
queue2 result: 2
queue2 result: 1
queue2 result: 2
queue1 result: 2
queue2 result: 2
queue2 result: 2
queue1 result: 1

剛才代碼中的 queue1要讀取并寫入秋柄, 但很有可能 queue2 這時(shí)候也運(yùn)行了获枝, 它在 queue1 的寫入操作沒有完成之前就做了讀取操作。 這時(shí)候他們兩個(gè)讀到值都是0骇笔, 就會(huì)造成兩個(gè)都輸出1省店。線程的調(diào)度是由操作系統(tǒng)來控制的,如果 queue2 調(diào)用的時(shí)笨触, queue1 正好寫入完成懦傍,這時(shí)就能得到正確的輸出結(jié)果。 可如果 queue2 調(diào)起的時(shí)候 queue1 還沒寫入完成芦劣,那么就會(huì)出現(xiàn)輸出同樣結(jié)果的現(xiàn)象粗俱。 這一切都是由操作系統(tǒng)來控制。

解決

1虚吟、NSLock

NSLock 是 iOS 提供給我們的一個(gè) API 封裝寸认, 可以很好的解決資源搶奪問題。 NSLock 就是對(duì)線程加鎖機(jī)制的一個(gè)封裝
使用示例:

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let lock = NSLock()
        
        for _ in 0..<100 {
            // 線程一操作
            let queue1 = DispatchQueue(label: "queue1");
            queue1.async {[weak self] in
                lock.lock() // 鎖起來
                // 寫
                self?.saveData(key: identifier, value: 1)
                
                // 讀
                let result = self?.readData(key: identifier) ?? ""
                lock.unlock()  // 解鎖
                
                print("queue1 result: \(String(describing: result))")
            }
            
            // 線程二操作
            let queue2 = DispatchQueue(label: "queue2");
            queue2.async {[weak self] in
                lock.lock() // 鎖起來
                // 寫
                self?.saveData(key: identifier, value: 2)
                
                // 讀
                let result = self?.readData(key: identifier) ?? ""
                lock.unlock()  // 解鎖
                
                print("queue2 result: \(String(describing: result))")
            }
        }
    }

循環(huán)打印的結(jié)果
queue1 result: 1
queue2 result: 2
queue1 result: 1
queue2 result: 2
queue1 result: 2
queue2 result: 2
queue1 result: 1
queue2 result: 2

互斥鎖(pthread_mutex_lock)

從實(shí)現(xiàn)原理上來講串慰,Mutex(互斥鎖)屬于sleep-waiting類型的鎖偏塞。例如在一個(gè)多核的機(jī)器上有兩個(gè)線程p1和p2,分別運(yùn)行在Core1和 Core2上邦鲫。假設(shè)線程p1想要通過pthread_mutex_lock操作去得到一個(gè)臨界區(qū)(Critical Section)的鎖灸叼,而此時(shí)這個(gè)鎖正被線程p2所持有,那么線程p1就會(huì)被阻塞 (blocking),Core1 會(huì)在此時(shí)進(jìn)行上下文切換(Context Switch)將線程p1置于等待隊(duì)列中怜姿,此時(shí)Core1就可以運(yùn)行其他的任務(wù)(例如另一個(gè)線程p3)慎冤,而不必進(jìn)行忙等待。

自旋鎖(Spin lock)

先插個(gè)話題:在OC中定義屬性時(shí)沧卢,很多人會(huì)認(rèn)為如果屬性具備 nonatomic 特質(zhì)蚁堤,則不使用 “同步鎖”。其實(shí)在屬性設(shè)置方法中使用的是自旋鎖但狭。

旋鎖與互斥鎖有點(diǎn)類似披诗,只是自旋鎖不會(huì)引起調(diào)用者睡眠,如果自旋鎖已經(jīng)被別的執(zhí)行單元保持立磁,調(diào)用者就一直循環(huán)在那里看是 否該自旋鎖的保持者已經(jīng)釋放了鎖呈队,"自旋"一詞就是因此而得名。其作用是為了解決某項(xiàng)資源的互斥使用唱歧。因?yàn)樽孕i不會(huì)引起調(diào)用者睡眠宪摧,所以自旋鎖的效率遠(yuǎn) 高于互斥鎖。

雖然它的效率比互斥鎖高颅崩,但是它也有些不足之處:

1几于、自旋鎖一直占用CPU,他在未獲得鎖的情況下沿后,一直運(yùn)行--自旋沿彭,所以占用著CPU,如果不能在很短的時(shí) 間內(nèi)獲得鎖尖滚,這無疑會(huì)使CPU效率降低喉刘。
2、在用自旋鎖時(shí)有可能造成死鎖漆弄,當(dāng)遞歸調(diào)用時(shí)有可能造成死鎖睦裳,調(diào)用有些其他函數(shù)也可能造成死鎖,如 copy_to_user()撼唾、copy_from_user()廉邑、kmalloc()等。
因此我們要慎重使用自旋鎖券坞,自旋鎖只有在內(nèi)核可搶占式或SMP的情況下才真正需要,在單CPU且不可搶占式的內(nèi)核下肺素,自旋鎖的操作為空操作恨锚。自旋鎖適用于鎖使用者保持鎖時(shí)間比較短的情況下。

總結(jié)

這里貼一張ibireme做的測(cè)試圖倍靡,介紹了一些iOS 中的鎖的API猴伶,及其效率


674591-176434d65ad6f5b6.png

挑幾個(gè)我們常用且熟悉的啰嗦幾句

@synchronized (屬:互斥鎖)

顯然,這是我們最熟悉的加鎖方式,因?yàn)檫@是OC層面的為我們封裝的他挎,使用起來簡單粗暴筝尾。使用時(shí) @synchronized 后面需要緊跟一個(gè) OC 對(duì)象,它實(shí)際上是把這個(gè)對(duì)象當(dāng)做鎖來使用办桨。這是通過一個(gè)哈希表來實(shí)現(xiàn)的筹淫,OC 在底層使用了一個(gè)互斥鎖的數(shù)組(也就是鎖池),通過對(duì)象的哈希值來得到對(duì)應(yīng)的互斥鎖呢撞。

-(void)criticalMethod  
{  
    @synchronized(self)  
    {  
        //關(guān)鍵代碼;  
    }  
}  
NSLock(屬:互斥鎖)

NSLock 是OC 以對(duì)象的形式暴露給開發(fā)者的一種鎖损姜,它的實(shí)現(xiàn)非常簡單,通過宏殊霞,定義了 lock 方法:
#define MLOCK - (void) lock{\ int err = pthread_mutex_lock(&_mutex);\ // 錯(cuò)誤處理 ……}

NSLock只是在內(nèi)部封裝了一個(gè)pthread_mutex摧阅,屬性為PTHREAD_MUTEX_ERRORCHECK,它會(huì)損失一定性能換來錯(cuò)誤提示绷蹲。這里使用宏定義的原因是棒卷,OC 內(nèi)部還有其他幾種鎖,他們的 lock 方法都是一模一樣祝钢,僅僅是內(nèi)部pthread_mutex互斥鎖的類型不同比规。通過宏定義,可以簡化方法的定義太颤。NSLock比pthread_mutex略慢的原因在于它需要經(jīng)過方法調(diào)用苞俘,同時(shí)由于緩存的存在涝开,多次方法調(diào)用不會(huì)對(duì)性能產(chǎn)生太大的影響港柜。

atomic原子操作(屬:自旋鎖)

即不可分割開的操作;該操作一定是在同一個(gè)cpu時(shí)間片中完成鲫惶,這樣即使線程被切換做裙,多個(gè)線程也不會(huì)看到同一塊內(nèi)存中不完整的數(shù)據(jù)岗憋。如果屬性具備 atomic 特質(zhì),則在屬性設(shè)置方法中使用的是“自旋鎖”锚贱。

什么情況下用什么鎖仔戈?

1、總的來看拧廊,推薦pthread_mutex作為實(shí)際項(xiàng)目的首選方案监徘;
2、對(duì)于耗時(shí)較大又易沖突的讀操作吧碾,可以使用讀寫鎖代替pthread_mutex凰盔;
3、如果確認(rèn)僅有set/get的訪問操作倦春,可以選用原子操作屬性户敬;
4落剪、對(duì)于性能要求苛刻,可以考慮使用OSSpinLock尿庐,需要確保加鎖片段的耗時(shí)足夠兄也馈;
5抄瑟、條件鎖基本上使用面向?qū)ο蟮腘SCondition和NSConditionLock即可凡泣;
6、@synchronized則適用于低頻場(chǎng)景如初始化或者緊急修復(fù)使用锐借;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末问麸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子钞翔,更是在濱河造成了極大的恐慌严卖,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件布轿,死亡現(xiàn)場(chǎng)離奇詭異哮笆,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)汰扭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門稠肘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人萝毛,你說我怎么就攤上這事项阴。” “怎么了笆包?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵环揽,是天一觀的道長。 經(jīng)常有香客問我庵佣,道長歉胶,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任巴粪,我火速辦了婚禮通今,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘肛根。我一直安慰自己辫塌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布派哲。 她就那樣靜靜地躺著臼氨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪狮辽。 梳的紋絲不亂的頭發(fā)上一也,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音喉脖,去河邊找鬼椰苟。 笑死,一個(gè)胖子當(dāng)著我的面吹牛树叽,可吹牛的內(nèi)容都是我干的舆蝴。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼题诵,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼洁仗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起性锭,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤赠潦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后草冈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體她奥,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年怎棱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了哩俭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拳恋,死狀恐怖凡资,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情谬运,我是刑警寧澤隙赁,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站吩谦,受9級(jí)特大地震影響鸳谜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜式廷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一咐扭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧滑废,春花似錦蝗肪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至俺陋,卻和暖如春豁延,著一層夾襖步出監(jiān)牢的瞬間昙篙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來泰國打工诱咏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留苔可,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓袋狞,卻偏偏與公主長得像焚辅,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子苟鸯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

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

  • 引用自多線程編程指南應(yīng)用程序里面多個(gè)線程的存在引發(fā)了多個(gè)執(zhí)行線程安全訪問資源的潛在問題同蜻。兩個(gè)線程同時(shí)修改同一資源有...
    Mitchell閱讀 1,990評(píng)論 1 7
  • iOS線程安全的鎖與性能對(duì)比 一、鎖的基本使用方法 1.1早处、@synchronized 這是我們最熟悉的枷鎖方式湾蔓,...
    Jacky_Yang閱讀 2,222評(píng)論 0 17
  • 前言 iOS開發(fā)中由于各種第三方庫的高度封裝,對(duì)鎖的使用很少砌梆,剛好之前面試中被問到的關(guān)于并發(fā)編程鎖的問題卵蛉,都是一知...
    喵渣渣閱讀 3,703評(píng)論 0 33
  • 鎖是一種同步機(jī)制,用于多線程環(huán)境中對(duì)資源訪問的限制iOS中常見鎖的性能對(duì)比圖(摘自:ibireme): iOS鎖的...
    LiLS閱讀 1,518評(píng)論 0 6
  • 教授講,不是認(rèn)識(shí)字的人诉儒,都能教語文葡缰!這是對(duì)自認(rèn)為誰都能教語文,評(píng)價(jià)語文課的人錯(cuò)誤認(rèn)識(shí)的糾正忱反,同時(shí)也是對(duì)語文教師提了...
    小水月閱讀 452評(píng)論 1 8