本文博主带领大家一起学习一下iOS开发中的锁。
为什么用到锁?
当一个线程访问数据的时候,其他的线程不能对其进行访问,直到该线程访问完毕。即确保线程安全。比如:iOS中的读写文件,当一个线程在写文件时,如果另一个线程去读或者去写,这样都会导致数据紊乱。为了线程安全,我们使用锁的机制来确保,同一时刻只有同一个线程来对一个数据源进行访问。
iOS中都用什么锁?
osspinLock废弃后,os_unfair_lock效率是最高的锁
互斥锁
1.NSLock
2.pthread_mutex
3.@synchronized
4.os_unfair_lock
是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。该目的通过将代码切片成一个一个的临界区而达成。
自旋锁
1.OSSpinLock
是用于多线程同步的一种锁,线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁。 自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。
自旋锁和互斥锁的区别对比:
自旋锁的效率高于互斥锁
相同点:
都能保证同一时间只有一个线程访问共享资源,都能保证线程安全。
不同点:
互斥锁:如果共享数据已经有其他线程加锁了,线程会进入休眠状态等待锁,一旦被访问的资源被解锁,则等待资源的线程会被唤醒。
自旋锁:如果共享数据已经有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问的资源被解锁,则等待资源的线程会立即执行。
递归锁
1.NSRecursiveLock:
2.pthread_mutex
递归锁有一个特点,就是同一个线程可以加锁N次而不会引发死锁。
条件锁
1. NSCondition:
2.NSConditionLock:
就是条件变量,当进程的某些资源要求不满足时就进入休眠,也就是锁住了。当资源被分配到了,条件锁打开,进程继续运行。
读写锁
pthread_rwlock
读写锁通常用互斥锁、条件变量、信号量实现。
是计算机程序的并发控制的一种同步机制,也称“共享-互斥锁”、多读者-单写者锁) 用于解决多线程对公共资源读写问题。读操作可并发重入,写操作是互斥的。
信号量
1. dispatch_semaphore
遵循NSLocking协议,使用的时候同样是lock,unlock加解锁,wait是傻等,waitUntilDate:方法是等一会,都会阻塞掉线程,signal是唤起一个在等待的线程,broadcast是广播全部唤起。
1.NSLock
NSLock实现了最基本的互斥锁,使用时需要遵循 NSLocking协议,通过lock和unlock来进行锁定和解锁。
例子如下:在线程A 调用unlock方法之前,另一个线程B调用了同一锁对象的lock方法。那么,线程B只有等待。直到线程A调用了unlock。
实际项目中:NSLock在AFNetworking的AFURLSessionManager.m中应用如下:
-(void)nsLockDemo1 {
NSLock *_lock = [[NSLock alloc] init];
//ThreadA
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[_lock lock];
[self performSelector:@selector(threadA) withObject:nil];
sleep(5);
[_lock unlock];
});
//ThreadB
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[_lock lock];
[self performSelector:@selector(threadB) withObject:nil];
sleep(5);
[_lock unlock];
});
//ThreadC
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[_lock lock];
[self performSelector:@selector(threadC) withObject:nil];
sleep(5);
[_lock unlock];
});
}
-(void)threadA {
NSLog(@"name A: %@",[NSThread currentThread]);
}
-(void)threadB {
NSLog(@"name B: %@",[NSThread currentThread]);
}
-(void)threadC {
NSLog(@"name C: %@",[NSThread currentThread]);
}
2.@synchronized
@synchronized要一个参数,这个参数相当于信号量。
实际项目中:AFNetworking中 isNetworkActivityOccurring属性的getter方法
- (void)synchronizedAction {
NSObject *obj = [[NSObject alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized (obj) {
NSLog(@"线程同步的操作1 开始");
sleep(3);
NSLog(@"线程同步的操作1 结束");
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized (obj) {
NSLog(@"线程同步的操作2 开始");
}
});
}
//打印结果
线程同步的操作1 开始
线程同步的操作1 结束
线程同步的操作2 开始
swift中没有synchronize,使用objc_sync_enter和objc_sync_exit
private func synchronizedAction() {
DispatchQueue.global().async {
objc_sync_enter(self)
for _ in 0..<3 {
print("执行操作")
Thread.sleep(forTimeInterval: 1)
}
objc_sync_exit(self)
}
}
3.dispatch_semaphore
dispatch_semaphore是GCD用来同步的一种方式,与他相关的共有三个函数,分别是:
dispatch_semaphore_create(创建一个semaphore),dispatch_semaphore_signal(发送一个信号),dispatch_semaphore_wait(等待信号)。
dispatch_semaphore在YYKit中的YYThreadSafeArray.m有所应用
-(void)gcdDemo {
dispatch_semaphore_t signale = dispatch_semaphore_create(1);
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 12 * NSEC_PER_SEC);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(signale, overTime);
NSLog(@"需要线程同步的操作1 开始");
sleep(10);
NSLog(@"需要线程同步的操作1 结束");
dispatch_semaphore_signal(signale);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(signale, overTime);
NSLog(@"需要线程同步的操作2 开始");
dispatch_semaphore_signal(signale);
});
}
这个例子中,为线程设置了最大等待时间10秒,所以当上面的线程sleep10秒的时候,下面的线程仍然要等待。
4.phtread_mutex
phtread_mutex实现互斥锁比递归锁效率高。
在YYKit的YYMemoryCach中可以看到
5.pthread_rwlock
//加读锁
pthread_rwlock_rdlock(&rwlock);
//解锁
pthread_rwlock_unlock(&rwlock);
//加写锁
pthread_rwlock_wrlock(&rwlock);
//解锁
pthread_rwlock_unlock(&rwlock);
6.NSCondition
代码分析:condition 进入到判断条件中,当products == 0 的时候,condition 调用wait 时当前线程处于等待状态;其他线程开始访问products,当NSObject 创建完成并加入到products时,cpu发出single的信号时,处于等待的线程被唤醒,开始执行[products removeObjectAtIndex:0];
7.OSSpinLock
首先要提的是OSSpinLock已经出现了BUG,导致并不能完全保证是线程安全的。
8.os_unfair_lock
os_unfair_lock 是苹果官方推荐的替换OSSpinLock的方案,它是一个互斥锁,但是它在iOS10.0以上的系统才可以调用。os_unfair_lock是一种互斥锁,它不会向自旋锁那样忙等,而是等待线程会休眠。
导入头文件
#import <os/lock.h>
//初始化锁
os_unfair_lock unfairLock = OS_UNFAIR_LOCK_INIT;
//加锁
os_unfair_lock_lock(&unfairLock);
//解锁
os_unfair_lock_unlock(&unfairLock);
9.NSConditionLock
NSConditionLock定义了一个可以指定条件的互斥锁,用于线程之间的互斥与同步,性能较低。
10.NSRecursiveLock
NSRecursiveLock在YYKit中YYWebImageOperation.m中有用到
iOS 死锁形成及解决方案
在iOS开发中,死锁是一种常见的问题,它通常发生在多个线程间资源的竞争与互斥情况下。死锁会导致应用程序卡死,用户体验极为不佳。因此,了解死锁的形成机理以及有效的解决方案是非常重要的。
一、死锁的形成
死锁是指两个或多个线程在执行过程中,因为争夺资源而造成的一种互相等待的状态。在iOS中,尤其是在使用多线程、GCD(Grand Central Dispatch)或NSLock等锁机制时,容易发生死锁现象。
死锁的典型场景包括:
线程A请求资源1,持有资源2。
线程B请求资源2,持有资源1。
这样,两个线程就形成了互相等待,从而导致死锁。
代码示例
import Foundation
let lock1 = NSLock()
let lock2 = NSLock()
func threadA() {
lock1.lock()
print("Thread A acquired lock1")
// Simulate some work
sleep(1)
lock2.lock()
print("Thread A acquired lock2")
// Release locks
lock2.unlock()
lock1.unlock()
}
func threadB() {
lock2.lock()
print("Thread B acquired lock2")
// Simulate some work
sleep(1)
lock1.lock()
print("Thread B acquired lock1")
// Release locks
lock1.unlock()
lock2.unlock()
}
// Create threads
let a = DispatchQueue(label: "Thread A", attributes: .concurrent)
let b = DispatchQueue(label: "Thread B", attributes: .concurrent)
a.async {
threadA()
}
b.async {
threadB()
}
在上面的代码中,线程A在获取lock1后试图获取lock2,而线程B在获取lock2后试图获取lock1,这就造成了死锁。
二、解决方案
为了解决死锁问题,可以采取以下策略:
1.避免锁的嵌套:尽量避免一个线程在持有某个锁的时候再去请求其他锁。
2.使用定时锁:可以使用NSLock的tryLock()方法来尝试获取锁,如果获取失败,则可以进行相应的处理,避免长时间的等待。
3.请求锁的顺序化:制定锁的请求顺序,确保每个线程都按照相同的顺序请求锁。
4.使用高层次的同步机制:例如使用GCD的队列,避免直接使用锁。
改进后的代码示例
import Foundation
let lock1 = NSLock()
let lock2 = NSLock()
func safeThreadA() {
lock1.lock()
print("Thread A acquired lock1")
// Simulate some work
sleep(1)
// Attempt to acquire lock2
if lock2.lock(timeout: .now() + 2) {
print("Thread A acquired lock2")
// Release locks
lock2.unlock()
} else {
print("Thread A could not acquire lock2, releasing lock1")
}
lock1.unlock()
}
func safeThreadB() {
lock2.lock()
print("Thread B acquired lock2")
// Simulate some work
sleep(1)
// Attempt to acquire lock1
if lock1.lock(timeout: .now() + 2) {
print("Thread B acquired lock1")
// Release locks
lock1.unlock()
} else {
print("Thread B could not acquire lock1, releasing lock2")
}
lock2.unlock()
}