Thread Safety in iOS

讓我們聊聊 iOS 上的 thread safe 線程安全吧!

David Lin
David Lin
Nov 4 · 4 min read

大部分在 Swift 中可以 mutate 的東西,都可能不是 thread safe 的,舉一個例子:

假設有一個 Person 的物件,裡面有 name 跟 age 這兩個屬性,且同時有兩個人在不同執行緒中要更改 person 的 name,且在更改的同時有一個人要讀取該 person 的 name 屬性,最後讀取的人會得到什麼值?

不知道。因為我們不知道這些人誰先完成了讀取或者寫入的動作。

最直接的例子就是資料庫,在現實中的 app 裡,我們可能有很多的執行緒同時在讀取跟寫入,在這個情況下同時讀取寫入就會造成資料庫的 exception 進而讓 app 閃退。一個比較好的作法可能是寫入的時候一個一個寫入,讀取等到所有的寫入完成後才從資料庫讀取最新的資料出來,如此一來讀取時就不會讀取到寫入到一半的資料,或者讀取到舊的資料了。

概念雖然簡單,但具體上應該怎麼做呢?

來做一個簡單的 Thread Safe Array 吧

我們先來看一下多個執行緒同時操作同一個 array 會發生什麼事:

結果就是閃退:

error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).

因為 Swift Array 底層實作的關係,會去操作 pointer(這裡不贅述,有興趣的朋友歡迎看 Swift source code),多執行緒同時操作 array 就會有這樣的問題。

那我們能做的事情就是讓操作 array 的地方統一在一個執行緒中發生,因此我們可以在操作 array 前,先進到一個固定的執行緒後才執行 array 操作。

在同一個執行緒中執行 array 操作後就不會有 exception 了。

Read Write Lock

在上面提到,我們希望所有的讀取發生在寫入之後,如此一來就可以讀取到寫入之後的最新資料。GCD 提供了一些方便的方法讓我們可以做到這樣的效果,就是使用 DispatchWorkItemFlags 中的 .barrier 可以達到這個效果:

可以試著自己跑跑看這個 sample code,可以看到讀取寫入一起發生時,write 會優先執行且 block read。

有興趣閱讀更多關於 Read/Write Lock 可以點進連結看

完成 Thread Safe Array

Array 常用的操作不外乎就是 append, subscript 等等:

如果想要讓這個 array thread safe 的話,我們必須對他做一層包裝:

接著實作可能會用到的一些操作(包含 read/write):

最後就可以放心的直接操作 array 囉:

Performance Measure

再來我們來看一下一般的 array 跟封裝過確定 thread safe 的 array 效能差多少:

先看一下我們的 measure block:

單純讀取

thread safe 讀取效能上降低了 50%。

單純寫入

thread safe 寫入效能上降低了 86%。

同時讀寫

thread safe 讀寫效能上降低了 94%。


雖然效能明顯降低很多,但可以注意到的是每個 iteration 都有 5 萬次,如果單純看一般 array 跟 thread safe array 一次讀寫時間,以下是只讀寫 100 次的時間:

normal array average time:
0.00014765262603759765

thread safe array average time:
0.002648663520812988

兩者的時間都很小,以手機 60 fps 的更新率來算,1 fps 你有 0.016 秒的運算時間可以使用,相比起來 thread safe 的運算時間小很多。如果怕讀取會卡住等寫入完成的話,可以先到背景讀取,等到取得你要的資料時再回到 main 即可。

thread safe array 一次讀寫操作約耗時 0.000026487 秒,在 0.016 秒中可以執行一樣的操作 600 次。但整體速度也要取決於裝置的 cpu,這裡只是大略測試。

補充: 在實機測試時同時讀寫效能略好於 simulator(以 iPhone 8 @ iOS 13.1 為例)使用 debug scheme。 讀:慢 50% 寫:慢 75% 讀寫:慢 91%

補充: 在實機測試時同時讀寫效能略好於 simulator(以 iPhone 8 @ iOS 13.1.3 為例)且使用 release scheme。 讀:慢 99.88% 寫:慢 99.83% 讀寫:慢 99.92%

如果有興趣看更完整的實作可以到這個 gist: SafeArray.swift

blog post link: http://yoxisem544.github.io/thread-safety-in-ios/

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade