本文版权归公众号【一个老码农】所有。
-
NSLock
NSLock是一种互斥锁,可以保证同一个资源,在同一时间只有一个线程进行操作和访问。 NSLock实现了NSLocking协议,NSLocking协议中有两个方法lock()和unlock(),即上锁和解锁,实现了NSLocking协议的类都具有互斥锁的特性,NSLocking协议如下
public protocol NSLocking {
func lock()
func unlock()
}
举例说明,NSLock的用法
private var count = 0
private let lock = NSLock()
private func testNSLock() {
DispatchQueue.global().async {
self.printCount()
}
DispatchQueue.global().async {
self.printCount()
}
}
private func printCount() {
lock.lock()
count += 1
print(count)
sleep(5)
lock.unlock()
}
如上代码,两个线程同时调用printCount方法,在printCount方法内部对count进行加1操作并sleep5秒,第一个线程获得锁,第二个线程只能等待5秒,等第一个线程unlock之后才能开始执行方法。打印结果为先打印1,隔5秒后打印2。
NSLock还有另一个特点:lock()方法不能重复调用(即锁重入),比如以下当A、B两个方法中都使用了锁,且A方法中调用了B方法,这个时候就会产生死锁。
以下代码便会产生死锁:
private func func1() {
lock.lock()
func2()
lock.unlock()
}
private func func2() {
lock.lock()
print(count)
lock.unlock()
}
-
NSRecursiveLock
NSRecursiveLock 我们称之为递归锁,NSRecursiveLock本身也实现NSLocking协议,但不同的是它可以解决上面NSLock锁重入问题,既然是递归锁,它也可以进行递归调用。 以下代码,可以正常打印出两个方法中的字符串
private let recursiveLock = NSRecursiveLock()
private func testRecursiveLock() {
recursiveLock.lock()
print("testRecursiveLock")
testRecursiveLock2()
recursiveLock.unlock()
}
private func testRecursiveLock2() {
recursiveLock.lock()
print("testRecursiveLock2")
recursiveLock.unlock()
}
-
NSCondition
NSCondition一般称之为条件锁,它也实现了NSLocking协议,所以它也有lock和unlock两个方法,如果调用这两个方法效果与NSLock一样,除此之外它还为我们提供了另外几个方法: wait() 阻塞当前线程。 signal()通知释放第一阻塞的线程。 broadcast() 释放每个线程中的第一个阻塞。 用法如下:
private let condition = NSCondition()
private func testCondition() {
DispatchQueue.global().async {
self.conditionFunc()
}
DispatchQueue.global().async {
self.conditionFunc()
}
DispatchQueue.global().asyncAfter(deadline: .now() + 2, execute: {
self.condition.signal()
})
DispatchQueue.global().asyncAfter(deadline: .now() + 4, execute: {
self.condition.signal()
})
}
private func conditionFunc() {
condition.lock()
print("conditionFunc")
count += 1
print(count)
condition.wait()
count += 1
print(count)
condition.unlock()
}
解释:两个线程同时调用conditionFunc方法,第一个线程获得锁,先打印conditionFunc,然后打印1,第二个线程处于等待状态。 接着condition调用了wait,第一个线程进入阻塞,第二个线程可以进入执行,再打印conditionFunc、2,紧接着condition又调用了wait,第二个线程进入阻塞,两秒后condition又调用一signal,第一个线程释放阻塞,count自加1,打印3,又两秒后,condition又调用了signal,第二个线程释放,count又自加1,则打印4。如果把singnal改为broadcast,则两个线程会全部释放阻塞。
-
NSConditionLock
NSConditionLock是对NSCondition做了一层封装,自带条件探测,根据设置condition的值能够更灵活的阻塞和释放线程。 conditionLock.lock(whenCondition: 1) 方法是当满足condition为1的条件时加锁,否则阻塞线程
conditionLock.unlock(withCondition: 0),代表解锁并将condition值设置为0
private func testConditionLock() {
let conditionLock = NSConditionLock(condition: 2)
DispatchQueue.global().async {
conditionLock.lock(whenCondition: 1)
print("任务1")
conditionLock.unlock(withCondition: 0)
}
DispatchQueue.global().asyncAfter(deadline: .now() + 10, execute: {
print("condition的值\(conditionLock.condition)")
conditionLock.lock(whenCondition: 2)
print("任务2")
conditionLock.unlock(withCondition: 1)
print("condition的值\(conditionLock.condition)")
})
}
解释:首先我们创建一个NSConditionLock对象,并把condition值设为2。 然后在第一个线程中调用lock(whenCondition: 1),代表condition等于1时上锁,显然这时候codition值为2,所以线程1阻塞。10秒钟后线程二调用打印 “condition的值2”,紧接着满足codition等于2的条件,上锁,打印“任务2”,调用unlock(withCondition: 1)解锁,并将codition值设为1,然后线程一释放阻塞,打印任务1,然后解锁,并将condition值设为0
-
@synchronized
@synchronized也是一种互斥锁,一般用于单例,但是在Swift中没有@synchronized,Swift中与之对应的是objc_sync_enter和objc_sync_exit,用法如下:
OC代码:
@synchronized (self) {
//todo someting
}
Swift代码:
objc_sync_enter(self)
//todo someting
objc_sync_exit(self)
-
信号量
信号量是属于gcd中的内容,可根据信号量的值来阻塞线程。当信号量量 >=0 时候不会阻塞当前线程,信号量小于 0 时候会阻塞当前线程。当调用wait()方法后信号量减1,调用signal()方法后信号量加1。
var number = 0
///信号量
let semaphare = DispatchSemaphore(value: 1)
DispatchQueue.global().async {
semaphare.wait()
number += 1
print(number)
semaphare.signal()
}
DispatchQueue.global().async {
semaphare.wait()
number += 1
print(number)
semaphare.signal()
}
同时启动两个线程(以上代码两个线程执行顺序随机),当第一个线程调用wait()时,信号量 减1。这时候信号量为0,所以第一个线程正常执行。而第二个线程开始执行时调用wait(),这时候信号量为减1,所以第二个线程阻塞。 当第一个线程调用signal()方法后,信号量加1,第二个线程开始执行。
-
栅栏函数
栅栏函数也是gcd中常用的函数,它可以通过阻塞当前队列的形式,来达到锁的目的,使用如下:
let queue = DispatchQueue(label: "1111")
queue.async {
print("1")
}
queue.async {
print("2")
}
queue.async(group: nil, qos: .default, flags: .barrier, execute: {
print("栅栏")
})
queue.async {
print("3")
}
queue.async {
print("4")
}
解释:如果没有栅栏函数阻塞,则1、2、3、4打印顺序随机,加了栅栏函数之后,会阻塞当前队列,所以3、4只能在1、2打印完成之后再执行打印。 打印顺序为1、2、栅栏、3、4,其中1和2、3和4打印顺序随机。
-
pthread_mutex
pthread_mutex属于C语言函数,是一个互斥锁,同时也是一个递归锁,用法如下:
var mutex = pthread_mutex_t()
//初始化
pthread_mutex_init(&mutex,nil)
//递归锁,可支持递归调用
private func testMutex() {
pthread_mutex_lock(&mutex)
count += 1
print(count)
testMutex()
pthread_mutex_unlock(&mutex)
}
deinit {
//要注意释放
pthread_mutex_destroy(&mutex)
}
-
atomic
atomic一般用于oc声明属性的关键字描述。代表原子性操作,本身是属于线程安全的,与之相反的是nonatomic,非原子性操作。 atomic在特定情况下并不能保证线程安全,例如以下代码:
@property(atomic, strong)NSMutableArray *array;
在多线程对array赋值时可以保证线程安全,如:xx.array = xxx; 但是当多个线程对array进行添加或删除元素操作时,仍然是非线程安全的。 [xx.array addObject: xxx];
-
自旋锁(OSSpinLock)
自旋锁与互斥锁的区别在于,当线程尝试获取锁但没有获取到时,互斥锁会使线程进入休眠状态,等到锁被释放,线程会被唤醒同时获取到锁。从而继续执行任务。 而自旋锁,则不会进入休眠状态,而是一直循环看是否可用。
由于自旋锁一直等待会消耗较多CPU 资源,但是它的效率较高,一旦锁释放立刻就能执行无需唤醒。所以适用于短时间内的轻量级任务锁定。 在iOS开发中,苹果为我们提供了一种自旋锁OSSpinLock,但是OSSpinLock已经于iOS10.0之后被废弃了,所以用法不再介绍。
-
os_unfair_lock
os_unfair_lock是iOS 10.0以后用来取代OSSpinLock的,与OSSpinLock不同的是,os_unfair_lock尝试获取的线程也会进入休眠,解锁时由内核唤醒。
os_unfair_lock从字面意思理解,它是一个不公平锁,即释放锁的线程可能立即再次获得锁,而之前等待锁的线程唤醒后可能无法尝试加锁。用法如下:
private var unfairLock = os_unfair_lock()
func testUnfairLock() {
os_unfair_lock_lock(&unfairLock)
//todo someting
os_unfair_lock_unlock(&unfairLock)
}
本文首发于公众号【一个老码农】,关注公众号免费获取学习视频
原文地址:iOS中的各种线程锁介绍