什么叫原子操作
对于一个资源,在写入或读取时,只允许在一个时刻一个角色进行操作,则为原子操作。
你可以简单粗暴地这么理解,我的银行帐号里面有100块钱,假如两个人同时在不同的ATM机上操作,他们的操作都是取100块钱,那ATM会不会都吐出100块钱出来呢?
假如是,那么,取钱这个操作就是非原子性的。
假如不是,那么,取钱这个操作就是原子性的。
Swift
对于 let 声明的资源,永远是原子性的。
对于 var 声明的资源,是非原子性的,对其进行读写时,必须使用一定的手段,确保其值的正确性。
那么,在什么情况下,需要用这些手段去保证原子操作。
- 如果你对资源的一致性要求不高,则不需要
- 如果资源可能在多个线程中被读取或者写入
- 如果资源的访问或写入需要被有序地执行
一个栗子
假设我们需要一个 ID 发生器,它的发生机制是从 0 ~ Int.max,它生成的 ID 不能是重复的,它生成的ID必须是正序的。
我们在 Playground 下输入以下代码,并查看结果。
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))
可以看到,控制台输出
1
2
3
4
5
6
7
8
9
10
11
现在,我们分开三个线程进行 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))
现在,输出变得奇怪无比了!
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 包裹的代码会被有序、同步地执行。
同时,你需要给这两个方法指定一个参考变量,这个参考变量可以是任意 var 类型的值。
但是,需要谨记,一旦 sync_enter 以后,整个应用就会被锁定,直至 sync_exit。
所以,在 lock 的代码区域中,要多加留意,看是否存在死锁的现象。
修改后的代码如下
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
结语
在 Objective-C 的时代,我们使用 Copy() 以及非可变类型保证线程安全。
在 Swift 的世界,因为let的存在,可变性的线程安全问题经常被开发者忽略,如果哪一天,你的应用出现了难以重现的问题,不妨从原子操作问题查起。