Thread Safety in iOS
讓我們聊聊 iOS 上的 thread safe 線程安全吧!
大部分在 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/
