引言
锁是线程编程中基本的同步工具。锁使你能够轻松地保护大段代码,以确保这些代码的正确性。OS X 和 iOS 为所有应用程序类型提供了基本的互斥锁,Foundation 框架还为特定情况定义了一些互斥锁的变体。
-
互斥锁
-
POSIX Mutex Lock
POSIX 互斥锁在任何应用程序中都非常容易使用。要创建互斥锁,你需要声明并初始化一个 pthread_mutex_t 结构体。要锁定和解锁互斥锁,你需要使用 pthread_mutex_lock 和 pthread_mutex_unlock 函数。当你不再需要该锁时,只需调用 pthread_mutex_destroy 来释放锁的数据结构。
class Counter {
private var value = 0
private var mutex = pthread_mutex_t()
init() {
//初始化
pthread_mutex_init(&mutex, nil)
}
func increment() {
//获取锁
pthread_mutex_lock(&mutex)
value += 1
//释放锁
pthread_mutex_unlock(&mutex)
}
deinit {
//销毁锁
pthread_mutex_destroy(&mutex)
}
}
let counter = Counter()
for _ in 0..<6 {
DispatchQueue.global().async {
counter.increment()
}
}
-
NSLock
NSLock 对象为 Cocoa 应用程序实现了一个基本的互斥锁。所有锁(包括 NSLock)的接口实际上是由 NSLocking 协议定义的,该协议定义了 lock 和 unlock 方法。你可以像使用任何互斥锁一样使用这些方法来获取和释放锁。
//改写上个案例
class Counter {
private var value = 0
private var lock = NSLock()
func increment() {
//尝试获取锁,阻塞线程的执行直到锁能够被获取。
lock.lock()
value += 1
lock.unlock()
}
}
let counter = Counter()
for _ in 0..<6 {
DispatchQueue.global().async {
counter.increment()
}
}
除了标准的锁定行为外,NSLock 类还增加了 tryLock 和 lockBeforeDate: 方法。tryLock 方法尝试获取锁,但如果锁不可用则不会阻塞;相反,该方法只是简单地返回 NO。lockBeforeDate: 方法尝试获取锁,但如果在指定时间内未能获取锁,则会解除线程阻塞并返回 NO。
class DataHandler {
private var sharedData = [Int]()
private let lock = NSLock()
// 读取数据的方法
func readData() -> [Int]? {
if lock.try() {
// 成功获取锁
let data = sharedData
lock.unlock()
return data
} else {
// 未能获取锁,返回 nil
return nil
}
}
// 写入数据的方法
func writeData(_ data: Int) {
// 尝试获取锁
if lock.try() {
// 成功获取锁
sharedData.append(data)
print("Data written: \(data) by \(Thread.current)")
lock.unlock()
} else {
// 未能获取锁,进行其他操作或稍后再试
print("Write lock not acquired by \(Thread.current), will try again later.")
}
}
}
let dataHandler = DataHandler()
// 读取线程
let readThread = Thread {
while true {
if let data = dataHandler.readData() {
print("Data read by \(Thread.current): \(data)")
} else {
print("Read lock not acquired by \(Thread.current)")
}
// 模拟其他工作
Thread.sleep(forTimeInterval: 1)
}
}
// 写入线程
let writeThread = Thread {
var counter = 0
while true {
dataHandler.writeData(counter)
counter += 1
// 模拟其他工作
Thread.sleep(forTimeInterval: 0.5)
}
}
// 启动线程
readThread.start()
writeThread.start()
-
递归锁
-
NSRecursiveLock
-
NSRecursiveLock 类定义了一种锁,这种锁可以被同一个线程多次获取而不会导致线程死锁。递归锁会记录成功获取锁的次数。每次成功获取锁都必须通过相应的解锁调用来平衡。只有当所有的锁定和解锁调用平衡时,锁才真正被释放,以便其他线程可以获取它。
顾名思义,这种类型的锁通常用于递归函数内部,以防止递归阻塞线程。你也可以在非递归情况下使用它,来调用需要获取锁的函数。这是一个简单递归函数的例子,该函数通过递归获取锁。如果不使用 NSRecursiveLock 对象,此代码在函数再次调用时会导致线程死锁。
let recursiveLock = NSRecursiveLock()
var value = 15
func recuriveFunction() {
recursiveLock.lock()
if value > 0 {
print("value: \(value) \(Thread.current)")
value -= 1
recuriveFunction()
}
recursiveLock.unlock()
}
func testFunction() {
recursiveLock.lock()
value -= 1
print("value: \(value) \(Thread.current)")
recursiveLock.unlock()
}
DispatchQueue.global().async {
recuriveFunction()
}
DispatchQueue.global().async {
testFunction()
}
条件锁
-
NSConditionLock
NSConditionLock 对象定义了一种可以使用特定值进行加锁和解锁的互斥锁。通常,当线程需要按特定顺序执行任务时,你会使用 NSConditionLock 对象,例如一个线程生成数据,另一个线程消费数据。当生产者线程在执行时,消费者线程使用一个特定于你的程序的条件来获取锁(条件本身只是一个你定义的整数值)。当生产者完成时,它会解锁并将锁的条件设置为适当的整数值以唤醒消费者线程,然后消费者线程继续处理数据。
// 定义条件
enum BufferCondition: Int {
case empty = 0
case full = 1
}
// 创建一个 NSConditionLock 实例,初始条件为 0
let conditionLock = NSConditionLock(condition: BufferCondition.empty.rawValue)
var buffer: [Int] = []
// 定义缓冲区最大容量
let bufferSize = 5
// 生产者线程
DispatchQueue.global().async {
var item = 0
while (true) {
// 当传入条件是 0, 获取锁,其他情况阻塞线程
conditionLock.lock(whenCondition: BufferCondition.empty.rawValue)
Thread.sleep(forTimeInterval: 1.0)
print("produce: \(item)")
buffer.append(item)
item += 1
let newCondition = buffer.count == bufferSize ? BufferCondition.full.rawValue: BufferCondition.empty.rawValue
// 解锁,当buffer满了之后,设置条件为1,其他情况设置条件为 0
conditionLock.unlock(withCondition: newCondition)
}
}
// 消费者线程
DispatchQueue.global().async {
while (true) {
// 当传入条件是 1, 获取锁,其他情况阻塞线程
conditionLock.lock(whenCondition: BufferCondition.full.rawValue)
Thread.sleep(forTimeInterval: 0.5)
let item = buffer.removeFirst()
print("consume: \(item)")
let newCondition = buffer.count == 0 ? BufferCondition.empty.rawValue: BufferCondition.full.rawValue
// 解锁,当buffer为空时,设置条件为 0,其他情况设置条件为 1
conditionLock.unlock(withCondition: newCondition)
}
}
-
NSCondition
Conditions是一种特殊类型的锁,可以用于同步操作执行的顺序。他们与互斥锁在细微之处有不同,等待条件的线程会保持阻塞状态,直到该条件被另一个线程显示信号通知
let condition = NSCondition()
var buffer: [Int] = []
// 定义缓冲区最大容量
let bufferSize = 10
let producer = Thread {
var item = 0
while true {
// 获取锁,如果没有成功阻塞线程
condition.lock()
Thread.sleep(forTimeInterval: 1.0)
print("Produced: \(item)")
buffer.append(item)
// 当buffer充满时,唤醒消费者线程,然后调用`wait()`阻塞线程
if buffer.count == bufferSize {
condition.signal()
condition.wait()
}
item += 1
// 释放锁
condition.unlock()
}
}
let consumer = Thread {
while true {
// 获取锁,如果没有成功阻塞线程
condition.lock()
if buffer.isEmpty {
// 当buffer为空时,唤醒生产者线程,然后调用`wait()`阻塞线程
condition.signal()
condition.wait()
}
let value = buffer.removeFirst()
Thread.sleep(forTimeInterval: 1.0)
print("Consumed: \(value)")
// 释放锁
condition.unlock()
}
}
consumer.start()
producer.start()
-
其他锁
-
Dispatch Semaphore
class Counter { private var value = 0 let semaphore = DispatchSemaphore(value: 1) func increment() { semaphore.wait() value += 1 print("value: \(value)") semaphore.signal() } } let counter = Counter() for _ in 0..<6 { DispatchQueue.global().async { counter.increment() } }-
Dispatch 串行队列
class Counter { private var value = 0 let serialQueue = DispatchQueue(label: "searialQueue") func increment() { serialQueue.async { self.value += 1 print("vlaue: \(self.value)") } } } let counter = Counter() for _ in 0..<6 { DispatchQueue.global().async { counter.increment() } }-
os_unfair_lock
os_unfair_lock是 Apple 提供的一种轻量级、高性能的锁,用于替代不推荐使用的OSSpinLock。它主要用于多线程编程中,确保共享资源的安全访问。os_unfair_lock的设计目标是提供一种低开销的锁机制,同时避免OSSpinLock可能出现的优先级反转问题。class Counter { private var value = 0 private var lock = os_unfair_lock_s() func increment() { os_unfair_lock_lock(&lock) value += 1 print("value: \(value)") os_unfair_lock_unlock(&lock) } } let counter = Counter() for _ in 0..<6 { DispatchQueue.global().async { counter.increment() } } -
结语
Swift 提供了丰富的锁机制来帮助开发者处理多线程编程中的同步问题。每种锁都有其独特的特点和适用场景:
- NSLock和NSRecursiveLock适合一般的线程同步需求,递归锁则更适用于递归调用的场景。
- NSCondition 和NSConditionLock 提供了更高级的条件同步机制,适用于复杂的线程等待和通知场景。
- DispatchSemaphore 和 DispatchQueue (串行队列) 提供了基于 GCD 的高效并发控制方式。
- os_unfair_lock 提供了高性能的锁机制,适合低开销、避免优先级反转的问题。
- pthread_mutex 提供了底层的 POSIX 线程锁,用于更细粒度的控制。
通过学习和掌握这些不同的锁机制,你可以根据具体的应用场景选择最合适的工具,确保程序的正确性和性能。多线程编程虽然复杂,但合理使用这些锁机制可以帮助你有效地管理并发和同步,编写出高效、可靠的 Swift 应用程序。希望本文的介绍能帮助你更好地理解和使用 Swift 中的各种锁,实现稳定和高效的多线程编程。
如果有任何问题,请不吝赐教。