二、說說線程同步

線程同步概念想要解決的問題?

應(yīng)用程序里面多個線程的存在引發(fā)了多個執(zhí)行線程安全訪問資源的潛在問題。兩個線程同時(shí)修改同一資源有可能以意想不到的方式互相干擾。這個現(xiàn)象被稱為"數(shù)據(jù)競爭"。

線程之間不能且不應(yīng)該是完全獨(dú)立的,互不干擾的(要不設(shè)計(jì)來干嘛?)。所以在線程必須交互的情況下,你需要使用同步工具,來確保當(dāng)它們交互的時(shí)候是安全的。

同步工具

為了防止不同線程意外修改數(shù)據(jù),你可以設(shè)計(jì)你的程序沒有同步問題,或你也可以使用同步工具。盡管完全避免出現(xiàn)同步問題相對更好一點(diǎn),但是幾乎總是無法實(shí)現(xiàn)。

1.原子操作

原子操作是同步的一個簡單的形式,它處理簡單的數(shù)據(jù)類型。原子操作的優(yōu)勢是它們不妨礙競爭的線程。對于簡單的操作,比如遞增一個計(jì)數(shù)器,原子操作比使用鎖具有更高的性能優(yōu)勢。

2.內(nèi)存屏障和Volatile變量

內(nèi)存屏障(memory barrier)是一個使用來確保內(nèi)存操作按照正確的順序工作的非阻塞的同步工具。內(nèi)存屏障的作用就像一個柵欄,迫使處理器來完成位于障礙前面的任何加載和存儲操作,才允許它執(zhí)行位于屏障之后的加載和存儲操作。內(nèi)存屏障同樣使用來確保一個線程(但對另外一個線程可見)的內(nèi)存操作總是按照預(yù)定的順序完成。如果在這些地方缺少內(nèi)存屏障有可能讓其他線程看到看似不可能的結(jié)果(比如,內(nèi)存屏障的維基百科條目)。為了使用一個內(nèi)存屏障,你只要在你代碼里面需要的地方簡單的調(diào)用OSMemoryBarrier函數(shù)。

Volatile 變量適用于獨(dú)立變量的另一個內(nèi)存限制類型。編譯器優(yōu)化代碼通過加載這些變量的值進(jìn)入寄存器。對于本地變量,這通常不會有什么問題。但是如果一個變量對另外一個線程可見,那么這種優(yōu)化可能會阻止其他線程發(fā)現(xiàn)變量的任何變化。在變量之前加上關(guān)鍵字volatile可以強(qiáng)制編譯器每次使用變量的時(shí)候都從內(nèi)存里面加載。如果一個變量的值隨時(shí)可能給編譯器無法檢測的外部源更改,那么你可以把該變量聲明為volatile變量。

因?yàn)閮?nèi)存屏障和volatile變量降低了編譯器可執(zhí)行的優(yōu)化,因此你應(yīng)該謹(jǐn)慎使用它們,只在有需要的地方時(shí)候,以確保正確性。

3.鎖
  • Mutex(互斥鎖)

一個互斥鎖對于資源來說很像是一個保護(hù)性的barrier。它是一種信號量機(jī)制,在同一時(shí)間只允許一個進(jìn)程來訪問資源。如果一個互斥鎖正在被一個線程獲取使用,另外一個線程如果想要使用的話就必須等待holder線程釋放這個鎖。多個一期請求的時(shí)候也只會有一個被賦予權(quán)限。

  • Recursive lock(遞歸鎖)

遞歸鎖其實(shí)是互斥鎖的一個變種。就是一個線程可以多次持有一個鎖,其它線程像訪問的時(shí)候,這個線程必須把這個多次持有都釋放了。應(yīng)用場景是多個方法,每個都要單獨(dú)獲取鎖。

  • Read-write lock(讀寫鎖)

共享互斥鎖。這個在客戶端和服務(wù)端有不同,對服務(wù)端而言,一個連接或者請求就是一個并發(fā)操作,而客戶端,因?yàn)橹挥幸粋€人,并發(fā)操作多指一個程序進(jìn)程內(nèi)的多個場景。就是多個讀操作可以一起整,而寫操作就必須等所有讀操作都完了。共享,并互斥。只有POSIX threads才支持。

  • Distributed lock(分布鎖)

提供了process級別的互斥access。它不會真的把process鎖住。只是提個建議,告訴process自己最近很忙,要不要停一停。

  • Spin lock(自旋鎖)

它會反復(fù)poll自己的lock conditon,直到lock conditon 變成 true。多是設(shè)計(jì)多核心應(yīng)用切換時(shí)候用的。

  • Double-checked lock(雙重檢查鎖)

來回監(jiān)測,不安全,系統(tǒng)不提供

4.條件

條件是信號量的另外一個形式,它允許在條件為真的時(shí)候線程間互相發(fā)送信號。條件通常被使用來說明資源可用性,或用來確保任務(wù)以特定的順序執(zhí)行。當(dāng)一個線程測試一個條件時(shí),它會被阻塞直到條件為真。它會一直阻塞直到其他線程顯式的修改信號量的狀態(tài)。條件和互斥鎖(mutex lock)的區(qū)別在于多個線程被允許同時(shí)訪問一個條件。條件更多是允許不同線程根據(jù)一些指定的標(biāo)準(zhǔn)通過的守門人。

一個方式是你使用條件來管理掛起事件的池。事件隊(duì)列可能使用條件變量來給等待線程發(fā)送信號,此時(shí)它們在事件隊(duì)列中的時(shí)候。如果一個事件到達(dá)時(shí),隊(duì)列將給條件發(fā)送合適信號。如果一個線程已經(jīng)處于等待,它會被喚醒,屆時(shí)它將會取出事件并處理它。如果兩個事件到達(dá)隊(duì)列的時(shí)間大致相同,隊(duì)列將會發(fā)送兩次信號喚醒兩個線程。

系統(tǒng)通過幾個不同的技術(shù)來支持條件。

5.執(zhí)行slector

Cocoa程序包含了一個在一個線程以同步的方式傳遞消息的方便方法。NSObject類聲明方法來在應(yīng)用的一個活動線程上面執(zhí)行selector的方法。這些方法允許你的線程以異步的方式來傳遞消息,以確保它們在同一個線程上面執(zhí)行是同步的。比如,你可以通過執(zhí)行selector消息來把一個從你分布計(jì)算的結(jié)果傳遞給你的應(yīng)用的主線程或其他目標(biāo)線程。每個執(zhí)行selector的請求都會被放入一個目標(biāo)線程的run loop的隊(duì)列里面,然后請求會按照它們到達(dá)的順序被目標(biāo)線程有序的處理。

同步的成本

同步幫助確保你代碼的正確性,但同時(shí)將會犧牲部分性能。甚至在無競態(tài)的情況下,同步工具的使用將在后面介紹。鎖和原子操作通常包含了內(nèi)存屏障和內(nèi)核級別同步的使用來確保代碼正確被保護(hù)。如果,發(fā)生鎖的爭奪,你的線程有可能進(jìn)入阻塞,在體驗(yàn)上會產(chǎn)生更大的遲延。

  • mutex獲得時(shí)間(0.2ms)

這個時(shí)間是沒有競爭條件下的。如果lock被另一個線程持有,時(shí)間就更長了。

  • Atomic compare-and-swap(0.05)

如何進(jìn)行同步(注意事項(xiàng))

  • 當(dāng)心死鎖和活鎖

死鎖:當(dāng)兩個不同的線程分別保持一個鎖(而該鎖是另外一個線程需要的)又試圖獲得另外線程保持的鎖時(shí)就會發(fā)生死鎖。結(jié)果是每個線程都會進(jìn)入持久性阻塞狀態(tài),因?yàn)樗肋h(yuǎn)不可能獲得另外那個鎖。

活鎖:當(dāng)兩個線程競爭同一個資源的時(shí)候就可能發(fā)生活鎖。在發(fā)生活鎖的情況里,一個線程放棄它的第一個鎖并試圖獲得第二個鎖。一旦它獲得第二個鎖,它返回并試圖再次獲得一個鎖。線程就會被鎖起來,因?yàn)樗ㄙM(fèi)所有的時(shí)間來釋放一個鎖,并試圖獲取其他鎖,而不做實(shí)際的工作。

  • 正確使用Volatile變量

如果你已經(jīng)使用了一個互斥鎖來保護(hù)一個代碼段,不要自動假設(shè)你需要使用關(guān)鍵詞volatile來保護(hù)該代碼段的重要的變量。一個互斥鎖包含了內(nèi)存屏障來確保加載和存儲操作是按照正確順序的。在一個臨界區(qū)添加關(guān)鍵字volatile到變量上面會強(qiáng)制每次訪問該變量的時(shí)候都要從內(nèi)存里面從加載。這兩種同步技巧的組合使用在一些特定區(qū)域是必須的,但是同樣會導(dǎo)致顯著的性能損失。如果單獨(dú)使用互斥鎖已經(jīng)可以保護(hù)變量,那么忽略關(guān)鍵字volatile。

為了避免使用互斥鎖而不使用volatile變量同樣很重要。通常情況下,互斥鎖和其他同步機(jī)制是比volatile變量更好的方式來保護(hù)數(shù)據(jù)結(jié)構(gòu)的完整性。關(guān)鍵字volatile只是確保從內(nèi)存加載變量而不是使用寄存器里面的變量。它不保證你代碼訪問變量是正確的。

  • 使用NSLock或相關(guān)協(xié)議類

互斥鎖要注意的是同一個鎖對象,被多個線程獲取的時(shí)候,多個線程都需要等待釋放,才能分配執(zhí)行。@synchorize其實(shí)是和NSLock一樣的東西,獲取了一個對象相關(guān)連的鎖, 但是編譯階段優(yōu)化回去還是個mutex。當(dāng)一個線程想要多次獲得一個鎖對象的時(shí)候要用遞歸鎖,要不就deadlock,大家可以挑一個。

  • @synchorize

功能上像個語法糖。使用起來很簡單。但是有這么個過程,就是作為一種預(yù)防措施,@synchronized塊隱式的添加一個異常處理例程來保護(hù)代碼。該處理例程會在異常拋出的時(shí)候自動的釋放互斥鎖。這意味著為了使用@synchronized指令,你必須在你的代碼中啟用異常處理。了如果你不想讓隱式的異常處理例程帶來額外的開銷,你應(yīng)該考慮使用鎖的類。

  • 使用條件

下篇有個簡單的例子

  • 信號量機(jī)制

邏輯上是個PV操作可控制的值,具體使用就很多了。希望有機(jī)會補(bǔ)上。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 引用自多線程編程指南應(yīng)用程序里面多個線程的存在引發(fā)了多個執(zhí)行線程安全訪問資源的潛在問題。兩個線程同時(shí)修改同一資源有...
    Mitchell閱讀 2,116評論 1 7
  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,895評論 0 11
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,790評論 11 349
  • Java-Review-Note——4.多線程 標(biāo)簽: JavaStudy PS:本來是分開三篇的,后來想想還是整...
    coder_pig閱讀 1,772評論 2 17
  • 工作中遇到了頭像上傳的壓縮,發(fā)現(xiàn)inSampleSize 相鄰整數(shù)之間壓縮差別太大了。比如2和3,前者會壓縮不夠,...
    安卓小吳閱讀 822評論 0 51

友情鏈接更多精彩內(nèi)容