不再安全的 OSSpinLock

jopen 8年前發布 | 14K 次閱讀 IOS

昨天有位開發者在 Github 上給我提了一個 issue ,里面指出 OSSpinLock 在新版 iOS 中已經不能再保證安全了,并提供了幾個相關資料的鏈接。我仔細查了一下相關資料,確認了這個讓人不爽的問題。

OSSpinLock 的問題

2015-12-14 那天, swift-dev 郵件列表里 有人在討論 weak 屬性的線程安全問題,其中有幾位蘋果工程師透露了自旋鎖的問題,對話內容大致如下:

新版 iOS 中,系統維護了 5 個不同的線程優先級 /QoS: background , utility , default , user-initiated , user-interactive 。高優先級線程始終會在低優先級線程前執行,一個線程不會受到比它更低優先級線程的干擾。這種線程調度算法會產生潛在的優先級反轉問題,從而破壞了 spin lock 。

具體來說,如果一個低優先級的線程獲得鎖并訪問共享資源,這時一個高優先級的線程也嘗試獲得這個鎖,它會處于 spin lock 的忙等狀態從而占用大量 CPU 。此時低優先級線程無法與高優先級線程爭奪 CPU 時間,從而導致任務遲遲完不成、無法釋放 lock 。這并不只是理論上的問題, libobjc 已經遇到了很多次這個問題了,于是蘋果的工程師停用了 OSSpinLock 。

蘋果工程師 Greg Parker 提到,對于這個問題,一種解決方案是用 truly unbounded backoff 算法,這能避免 livelock 問題,但如果系統負載高時,它仍有可能將高優先級的線程阻塞數十秒之久;另一種方案是使用 handoff lock 算法,這也是 libobjc 目前正在使用的。鎖的持有者會把線程 ID 保存到鎖內部,鎖的等待者會臨時貢獻出它的優先級來避免優先級反轉的問題。理論上這種模式會在比較復雜的多鎖條件下產生問題,但實踐上目前還沒有遇到問題。

libobjc 里用的是 Mach 內核的 thread_switch() 然后傳遞了一個 mach thread port 來避免優先級反轉,另外它還用了一個私有的參數選項,所以開發者無法自己實現這個鎖。另一方面,由于二進制兼容問題, OSSpinLock 也不能有改動。

最終的結論就是,除非開發者能保證訪問鎖的線程全部都處于同一優先級,否則 iOS 系統中所有類型的自旋鎖都不能再使用了。

OSSpinLock 的替代方案

為了找到一個替代方案,我做了一個簡單的性能測試,對比了一下幾種能夠替代 OSSpinLock 鎖的性能。測試是在 iPhone6 、 iOS9 上跑的,代碼在 這里 。我嘗試了不同的循環次數,結果并不都一樣,我猜這可能是與 CPU Cache 有關,所以這個結果只能當作一個定性分析。

不再安全的 OSSpinLock

可以看到除了 OSSpinLock 外, dispatch_semaphore 和 pthread_mutex 性能是最高的。有 消息 稱,蘋果在新系統中已經優化了 pthread_mutex 的性能,所以它看上去和 OSSpinLock 差距并沒有那么大了。

開源社區的反應

蘋果

查看 CoreFoundation 的源碼能夠發現,蘋果至少在 2014 年就發現了這個問題,并把 CoreFoundation 中的 spinlock 替換成了 pthread_mutex ,具體變化可以查看這兩個文件: CFInternal.h(855.17)CFInternal.h(1151.16) 。 蘋果自己發現問題后,并沒有更新 OSSpinLock 的文檔,也沒有告知開發者,這有些讓人失望。

Google

google/ protobuf 內部的 spinlock 被全部 替換為 dispatch_semaphore ,詳情可以看這個提交: https://github.com/google/protobuf/pull/1060 。用 dispatch_semaphore 而不用 pthread_mutex 應該是出于性能考慮。

其他項目

因為 OSSpinLock 出現這種問題的幾率很小,也沒有引起很大的重視,我所能找到的也只有 ReactiveCocoa 在討論這個問題。

來自: http://blog.ibireme.com/2016/01/16/spinlock_is_unsafe_in_ios/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!