iOS 常见锁及其底层实现

一、OSSpinLock(已废弃⚠️)

头文件: <libkern/OSAtomic.h>

底层: 原子[自旋锁(CPU 自旋 + LDR / STR 指令)](#自旋锁(CPU 自旋 + LDR / STR 指令) "#102")

objc 复制代码
#import <libkern/OSAtomic.h>
OSSpinLock lock = OS_SPINLOCK_INIT;
// 加锁
OSSpinLockLock(&lock);
// 解锁
OSSpinLockUnlock(&lock);

底层实现:

objc 复制代码
Lspinlock:
    LDREX  R1, [R0]  ; 加载锁的值到 R1
    CMP    R1, #0    ; 如果锁是 0(未加锁)
    STREX  R2, R3, [R0] ; 尝试设置为 1(加锁)
    CMP    R2, #0
    BNE    Lspinlock  ; 如果失败,继续自旋

✅ 优点:性能极高(无 syscall 进入内核)。

❌ 缺点:容易造成"优先级反转"(高优先级线程被低优先级线程饿死)。

❌ iOS 10+ 已被 os_unfair_lock 取代。

二、os_unfair_lock

objc 复制代码
#import <os/lock.h>
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;

os_unfair_lock_lock(&lock);   // 加锁
os_unfair_lock_unlock(&lock); // 解锁

🔹 底层实现

objc 复制代码
Llock:
    LDREX  R1, [R0]  ; 加载锁值
    CMP    R1, #0    ; 检查是否可用
    STREX  R2, R3, [R0] ; 尝试加锁
    CMP    R2, #0
    BNE    Llock  ; 如果失败,自旋等待

    ; 如果锁被占用,则进入内核等待
    BL     _os_unfair_lock_wait

os_unfair_lock 先尝试自旋

• 如果竞争激烈,会进入 syscall 休眠

• 避免 CPU 过度消耗,提高系统吞吐量

✅ 优点:比 OSSpinLock 更安全(不会优先级反转)。

✅ 推荐替代 OSSpinLock,用于多线程同步。

三、pthread_mutex(互斥锁)

objc 复制代码
#import <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// 加锁
pthread_mutex_lock(&mutex);

// 解锁
pthread_mutex_unlock(&mutex);

🔹 底层实现

assembly 复制代码
Lmutex:
    LDREX  R1, [R0]  ; 读取锁状态
    CMP    R1, #0
    STREX  R2, R3, [R0] ; 尝试锁定
    BNE    Lmutex  ; 失败则等待
    BL     _futex_wait  ; 进入系统调用

• 第一次尝试自旋(适用于短时间锁定)

• 如果锁不可用,进入 futex_wait 系统调用

• 性能比 NSLock

✅ 优点:比 NSLock 快,适用于 C 代码。

✅ 适用于 POSIX 兼容的锁定需求。

四、NSLock

objc 复制代码
NSLock *lock = [[NSLock alloc] init];
[lock lock];   // 加锁
[lock unlock]; // 解锁

🔹 底层

• 封装 pthread_mutexpthread_mutex 稍慢

• 适用于 Objective-C 代码

✅ 优点:Objective-C 友好,适用于 Foundation 框架。

五、dispatch_semaphore(信号量)

objc 复制代码
dispatch_semaphore_t sema = dispatch_semaphore_create(1);

// 线程 1
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

// 线程 2
dispatch_semaphore_signal(sema);

🔹 底层

• 使用 semaphore_wait() 进入 mach_syscall

• 基于 Mach 机制的 semaphore,比 NSLock 更轻量

• 可用于线程池、资源控制、线程同步、线程池控制

六、NSRecursiveLock

✅ 递归 pthread_mutex,递归锁,比NSLock慢。

允许同一线程多次加锁。

七、dispatch_queue (serial)

串行队列,用于线程同步。

八、NSCondition

基于 pthread_cond,条件锁。

九、NSConditionLock

基于 NSCondition ,比NSCondition慢。

十、什么是原子(atomic)

原子表示操作不可被中断,即多个线程同时访问变量时,不会被读取到中间状态,保证数据一致性。

@property (atomic, strong) NSString *name;

编译器会自动生成:

objc 复制代码
- (void)setName:(NSString *)name {
    @synchronized(self) {  // 保护 name 赋值操作
        _name = name; // ARC
    }
}

atomic保证setName:线程安全。

atomic不能保证多个操作的整体原子性(比如 name.length + name.uppercaseString 仍然可能导致数据竞争)。

✅ 原子操作的底层实现

assembly 复制代码
Latomic_add:
    LDREX  R1, [R0]  ; 读取 _name
    ADD    R1, R1, #1 ; R1 = R1 + 1
    STREX  R2, R1, [R0] ; 尝试写回 _name
    CMP    R2, #0
    BNE    Latomic_add  ; 如果失败,重试

• LDREX(Load Exclusive)读取变量。

• STREX(Store Exclusive)写入变量,并检查是否有其他线程修改过它。

• 如果 STREX 失败,重新执行(自旋),直到成功。

适用于:

• 多线程数据一致性

• 需要保证单个属性的安全性

• 高并发环境

十一、什么是非原子(nonatomic)

非原子表示操作可能被中断,即多个线程同时访问变量时,可能读取到不完整的数据。

比如:

@property (nonatomic, strong) NSString *name;

编译器不会自动加锁:

objc 复制代码
- (void)setName:(NSString *)name {
    _name = name; // ARC
}

nonatomic 不保证setName:线程安全。

nonatomic 性能更高,因为不需要锁定和等待。

比如:

objc 复制代码
self.name = @"Hello";
dispatch_async(queue, ^{
    self.name = @"World";
});
NSLog(@"%@", self.name);  // 可能是 "Hello",可能是 "World",也可能是崩溃

多个线程访问name,可能导致数据竞争。

适用于:

• 单线程环境

• 不涉及数据竞争的情况

• 性能优先的情况

十二、什么是自旋(Spinlock)

自旋(Spinlock)一种高性能的锁 ,线程 不会立即进入休眠 ,而是 持续循环检查锁是否可用(自旋等待)。

objc 复制代码
#import <libkern/OSAtomic.h>
OSSpinLock lock = OS_SPINLOCK_INIT;

OSSpinLockLock(&lock);   // 自旋等待
// 线程安全的操作
OSSpinLockUnlock(&lock); // 释放锁

✅ 自旋锁的核心特点

• 线程不会立即挂起,而是死循环检查锁状态

• 如果锁很快被释放(低竞争),自旋锁更快

• 如果锁被长时间持有(高竞争),自旋锁会浪费 CPU 资源

• 适用于短时间锁定的场景

容易造成优先级反转,iOS 10 之后,Apple 已废弃 OSSpinLock,使用 os_unfair_lock 代替 OSSpinlock

🙋 什么是"优先级反转"?

🔹 假设有两个线程

T1(低优先级) 持有 OSSpinLock,但被 T2 抢占 CPU,无法执行

T2(高优先级) 需要获取 OSSpinLock,但 T1 迟迟不释放

结果:T2 只能一直等待,导致"高优先级线程被低优先级线程饿死"

objc 复制代码
#import <Foundation/Foundation.h>
#import <libkern/OSAtomic.h>

OSSpinLock spinlock = OS_SPINLOCK_INIT;

void lowPriorityTask() {
    NSLog(@"低优先级线程尝试加锁...");
    OSSpinLockLock(&spinlock);  // 加锁
    NSLog(@"低优先级线程已获取锁,执行任务...");
    
    sleep(5);  // 模拟任务执行时间(期间不释放锁)

    NSLog(@"低优先级线程即将释放锁...");
    OSSpinLockUnlock(&spinlock);  // 解锁
}

void highPriorityTask() {
    sleep(1); // 确保低优先级线程先运行

    NSLog(@"高优先级线程尝试加锁...");
    OSSpinLockLock(&spinlock);  // 这里会被阻塞

    NSLog(@"高优先级线程已获取锁,执行任务...");
    OSSpinLockUnlock(&spinlock);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 低优先级线程
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
            lowPriorityTask();
        });

        // 高优先级线程
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            highPriorityTask();
        });

        [[NSRunLoop currentRunLoop] run]; // 让主线程保持运行
    }
    return 0;
}

🤔 为什么会出现优先级反转?

  1. OSSpinLock 是"自旋锁"

    T2(高优先级线程)不会进入睡眠,而是不断自旋,占用 CPU 资源,反而导致 T1(低优先级线程)更难执行完成。

  2. 低优先级线程可能被"抢占"

    如果系统有更高优先级的任务,T1(低优先级线程)可能会被"饿死" ,导致它迟迟不能释放锁。

  3. 高优先级线程无法主动"让步"

    T2 不会让出CPU资源,它的调度优先级更高,反而让 T1 更难执行完,形成死循环。

🙋 如何解决"优先级反转"

✅ 使用 os_unfair_lock 代替 OSSpinLock(推荐)

objc 复制代码
#import <os/lock.h>

os_unfair_lock unfairLock = OS_UNFAIR_LOCK_INIT;

void lowPriorityTask() {
    NSLog(@"低优先级线程尝试加锁...");
    os_unfair_lock_lock(&unfairLock);  // ✅ 替换 `OSSpinLockLock`

    NSLog(@"低优先级线程已获取锁,执行任务...");
    sleep(5);
    
    NSLog(@"低优先级线程即将释放锁...");
    os_unfair_lock_unlock(&unfairLock);  // ✅ 替换 `OSSpinLockUnlock`
}

• 如果 T2(高优先级线程)发现锁被占用,它会进入 syscall 休眠,让 T1 继续执行。

• 避免 T2 占用 CPU,导致 T1 不能释放锁,防止死锁。

✅ 使用 [pthread_mutex](#✅ 使用 pthread_mutex 代替 OSSpinLock "#3") 代替 [OSSpinLock](#✅ 使用 pthread_mutex 代替 OSSpinLock "#1")

objc 复制代码
#import <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void lowPriorityTask() {
    NSLog(@"低优先级线程尝试加锁...");
    pthread_mutex_lock(&mutex);  // ✅ 替换 `OSSpinLockLock`

    NSLog(@"低优先级线程已获取锁,执行任务...");
    sleep(5);

    NSLog(@"低优先级线程即将释放锁...");
    pthread_mutex_unlock(&mutex);  // ✅ 替换 `OSSpinLockUnlock`
}

pthread_mutex 是互斥锁,不会自旋,而是让线程进入睡眠等待,提高系统性能

• 当高优先级线程等待锁时,低优先级线程会继承高优先级,加快任务执行,减少等待时间。

相关推荐
EasyCVR5 分钟前
EasyRTC嵌入式音视频通话SDK:如何解决跨平台(Linix、Windows、ARM、物联网)、跨设备(Android、ios等)的兼容性难题?
android·ios·音视频
coooliang2 小时前
【iOS】SwiftUI 路由管理(NavigationStack)
ios·swiftui·swift
small_fox_dtt4 小时前
ios端使用TCplayer直播播放三秒直接卡顿bug
ios·bug·tcplayer
书弋江山16 小时前
ios分析app卡顿问题方案
ios·职场和发展·蓝桥杯
二流小码农1 天前
鸿蒙开发:远场通信服务rcp拦截器问题
android·ios·harmonyos
小画家~1 天前
第八:在Go语言项目中使用Zap日志库
ios·golang·xcode
Sitin涛哥1 天前
从0到1用cursor开发iOS应用(二)
ios
龙之吻1 天前
IOS接入微信方法
ios·微信
二流小码农1 天前
鸿蒙开发:填充剩余空间
android·ios·harmonyos