什么叫原子操作
對(duì)于一個(gè)資源横朋,在寫入或讀取時(shí),只允許在一個(gè)時(shí)刻一個(gè)角色進(jìn)行操作兑燥,則為原子操作亮瓷。
你可以簡單粗暴地這么理解,我的銀行帳號(hào)里面有100塊錢降瞳,假如兩個(gè)人同時(shí)在不同的ATM機(jī)上操作嘱支,他們的操作都是取100塊錢,那ATM會(huì)不會(huì)都吐出100塊錢出來呢挣饥?
假如是除师,那么,取錢這個(gè)操作就是非原子性的扔枫。
假如不是汛聚,那么,取錢這個(gè)操作就是原子性的短荐。
Swift
對(duì)于 let 聲明的資源倚舀,永遠(yuǎn)是原子性的。
對(duì)于 var 聲明的資源搓侄,是非原子性的瞄桨,對(duì)其進(jìn)行讀寫時(shí),必須使用一定的手段讶踪,確保其值的正確性芯侥。
那么,在什么情況下,需要用這些手段去保證原子操作柱查。
- 如果你對(duì)資源的一致性要求不高廓俭,則不需要
- 如果資源可能在多個(gè)線程中被讀取或者寫入
- 如果資源的訪問或?qū)懭胄枰挥行虻貓?zhí)行
一個(gè)栗子
假設(shè)我們需要一個(gè) ID 發(fā)生器,它的發(fā)生機(jī)制是從 0 ~ Int.max唉工,它生成的 ID 不能是重復(fù)的研乒,它生成的ID必須是正序的。
我們在 Playground 下輸入以下代碼淋硝,并查看結(jié)果雹熬。
import Foundation
struct TaskIDGenerater {
static var value: Int = 0
static func generate() -> Int {
TaskIDGenerater.value++
return TaskIDGenerater.value
}
}
for _ in 0...10 {
print(TaskIDGenerater.generate())
}
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 5))
可以看到,控制臺(tái)輸出
1
2
3
4
5
6
7
8
9
10
11
現(xiàn)在谣膳,我們分開三個(gè)線程進(jìn)行 ID 生成操作竿报。
import Foundation
struct TaskIDGenerater {
static var value: Int = 0
static func generate() -> Int {
TaskIDGenerater.value++
return TaskIDGenerater.value
}
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in
for _ in 0...10 {
print(TaskIDGenerater.generate())
}
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in
for _ in 0...10 {
print(TaskIDGenerater.generate())
}
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in
for _ in 0...10 {
print(TaskIDGenerater.generate())
}
}
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 5))
現(xiàn)在,輸出變得奇怪無比了继谚!
2
2
2
5
5
5
8
8
8
11
11
11
14
14
14
17
17
17
19
20
20
23
22
23
26
26
26
29
29
29
31
32
32
解決方案
在 Objective-C 中烈菌, 我們可以使用以下方法解決問題。
@synchronized(<#token#>) {
<#statements#>
}
在 Swift 中花履,我們使用類似的方法芽世。
objc_sync_enter(lock)
TaskIDGenerater.value++
objc_sync_exit(lock)
使用 objc_sync_enter 和 objc_sync_exit 包裹的代碼會(huì)被有序、同步地執(zhí)行诡壁。
同時(shí)济瓢,你需要給這兩個(gè)方法指定一個(gè)參考變量,這個(gè)參考變量可以是任意 var 類型的值妹卿。
但是葬荷,需要謹(jǐn)記,一旦 sync_enter 以后纽帖,整個(gè)應(yīng)用就會(huì)被鎖定,直至 sync_exit举反。
所以懊直,在 lock 的代碼區(qū)域中,要多加留意火鼻,看是否存在死鎖的現(xiàn)象室囊。
修改后的代碼如下
import Foundation
struct TaskIDGenerater {
static var value: Int = 0
static var lock: Int = 0
static func generate() -> Int {
objc_sync_enter(lock)
TaskIDGenerater.value++
let value = TaskIDGenerater.value
objc_sync_exit(lock)
return value
}
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in
for _ in 0...10 {
print(TaskIDGenerater.generate())
}
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in
for _ in 0...10 {
print(TaskIDGenerater.generate())
}
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in
for _ in 0...10 {
print(TaskIDGenerater.generate())
}
}
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 5))
輸出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
25
24
26
27
28
29
30
31
32
33
結(jié)語
在 Objective-C 的時(shí)代,我們使用 Copy() 以及非可變類型保證線程安全魁索。
在 Swift 的世界融撞,因?yàn)閘et的存在,可變性的線程安全問題經(jīng)常被開發(fā)者忽略粗蔚,如果哪一天尝偎,你的應(yīng)用出現(xiàn)了難以重現(xiàn)的問題,不妨從原子操作問題查起。