浅谈iOS开发中的锁

本文介绍了iOS开发中常用的锁机制,包括互斥锁、自旋锁、递归锁、条件锁、读写锁和信号量等,并通过示例代码详细解释了每种锁的应用场景和实现方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文博主带领大家一起学习一下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()
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值