谈到属性的原子性,即操作读写的原子性(安全的,单一的,完整的) 可以这么理解。有什么方法可以做到?自然,我们想到了锁的概念。

锁的种类

我们在开发Apps的时候,经常会用到多线程。而锁的概念可以抽象成线程同步。主要作用即保护授权某段代码块的原子性执行。iOS中锁有很多种类:

  • 信号量 Semaphore (允许N个有限线程在某一时间执行指定代码块)
  • 线程锁 Mutex 保证在某一时间内执行某代码块只有一个线程进入。
  • 自璇锁 Spinlock 指的是一个线程获取了一个自旋锁后,另外一个线程期望获取该自旋锁,获取不到的情况下会循环等待,不断的判断。
  • 读写锁 Read-write-lock 并发只读,串行写。
  • 递归锁 Recursive-lock 这个锁可以被同一线程多次请求,而不会引起死锁。它主要是用在循环或递归操作中。

    Lock

    NSLock 和 NSRecursiveLock 都是OC类,在Swift中直接使用。 C语言线程锁pthread_mutex_t可以在Swift中直接使用。

    Spinlock

    自璇锁在iOS10被抛弃了,在Swift中没有与之对应的锁。相近的有os_unfair_lock(不会在争夺锁时候循环判断,而是在操作系统内核中等待被解锁唤醒)

    Read-write lock

    读写锁在Swift中可以用pthread_rwlock_t

    Semaphore

    在Swift中,通过DispatchSemaphore来实现信号数控制。

锁举例Demo

所有锁都有加锁解锁行为,我们给它定义这样一个协议动作。

1
2
3
4
protocol Lock {
func lock()
func unlock()
}

Mutex & SpinLock

那么,我们把pthread_mutex_lock进行一下封装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
extension NSLock: Lock {}

//拟自旋锁
final class SpinLock: Lock {
private var unfairLock = os_unfair_lock_s()

func lock() {
os_unfair_lock_lock(&unfairLock)
}

func unlock() {
os_unfair_lock_unlock(&unfairLock)
}
}

//线程锁
final class Mutex: Lock {
private var mutex: pthread_mutex_t = {
var mutex = pthread_mutex_t()
pthread_mutex_init(&mutex, nil)
return mutex
}()

func lock() {
pthread_mutex_lock(&mutex)
}

func unlock() {
pthread_mutex_unlock(&mutex)
}
}

现在我们用以上锁来实现一个属性的原子性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct AtomicProperty {
private var underlyingFoo = 0
private let lock: Lock

init(lock: Lock) {
self.lock = lock
}

var foo: Int {
get {
lock.lock()
let value = underlyingFoo
lock.unlock()
return value
}
set {
lock.lock()
underlyingFoo = newValue
lock.unlock()
}
}
}

// Usage
let sample = AtomicProperty(lock: SpinLock())
_ = sample.foo

当然为了达到更好的性能,读取属性我们提供并发读取。可以用读写锁来实现。

ReadWriteLock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
final class ReadWriteLock {
private var rwlock: pthread_rwlock_t = {
var rwlock = pthread_rwlock_t()
pthread_rwlock_init(&rwlock, nil)
return rwlock
}()

func writeLock() {
pthread_rwlock_wrlock(&rwlock)
}

func readLock() {
pthread_rwlock_rdlock(&rwlock)
}

func unlock() {
pthread_rwlock_unlock(&rwlock)
}
}

class ReadWriteLockAtomicProperty {
private var underlyingFoo = 0
private let lock = ReadWriteLock()

var foo: Int {
get {
lock.readLock()
let value = underlyingFoo
lock.unlock()
return value
}
set {
lock.writeLock()
underlyingFoo = newValue
lock.unlock()
}
}
}

NSRecursiveLock

以下是递归锁的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//递归锁
let lock = NSRecursiveLock() //如果用NSLock() 将发生死锁,递归第二次进入再次lock()
var recursiveMethod: ((Int) -> Void)! = nil

recursiveMethod = { value in

defer {
lock.unlock()
}

lock.lock()

guard value > 0 else {
return
}

print(value)
sleep(2)
recursiveMethod(value - 1)
}

DispatchQueue.global().async {

print("开始")
recursiveMethod(5)
print("结束")
}

DispatchSemaphore

通过信号量来控制线程数

1
2
3
4
5
6
7
8
9
10
11
12
let concurrentTasks = 1

let queue = DispatchQueue(label: "Concurrent queue", attributes: .concurrent)
let sema = DispatchSemaphore(value: concurrentTasks)

for _ in 0..<100 {
queue.async {
// Do work
sema.signal()
}
sema.wait()
}

在另外一篇文章中,创建一个线程安全的字典中讲到,可以用栅栏函数dispatch_barrier_async来将操作任务串行化执行保证原子性。或者直接用NSLock也行,依据业务情况,使用对应符合场景的锁。

评论