美文网首页iOS Developer
iOS开发中的锁

iOS开发中的锁

作者: 赵梦楠 | 来源:发表于2017-12-07 16:39 被阅读126次

    前言

    在多线程开发中,常会遇到多个线程访问修改数据。为了防止数据不一致或数据污染,通常采用加锁机制来保证线程安全。


    概述

    锁是多线程开发中最基本的同步工具。开发中常用的锁通常分为以下几种类型:

    • Mutex(互斥锁): 互斥锁是一种信号量,一次只能访问一个线程如果一个互斥体正在使用,而另一个线程试图获取它,则该线程将阻塞,直到互斥体被其原始持有者释放。如果多个线程竞争同一个互斥体。则一次只允许一个线程访问它。

    • Recursive lock(递归锁):递归锁是互斥锁的变体。递归锁允许单个线程在释放之前多次获取锁。其他线程保持阻塞状态。直到锁的所有者释放锁的次数与获取它的次数相同,递归锁主要在递归中使用,但也可能在多个方法需要单独获取锁的情况下使用。

    • Spin lock(自旋锁): 自旋锁重复其锁定条件,直到该条件成立。自旋锁最常用于多处理器系统,其中锁的预期等待时间很短。在这些情况下,轮询通常比阻塞线程更高效,这涉及到上下文切换和线程数据结构的更新。

    • Read-write lock(读写锁):读写锁也被称为Shared-exclusive lock。通常用于较大规模的操作,适用于数据结构被频繁读取和偶尔修改,可以显着提高性能。在正常操作期间,多个线程可以同时访问数据。当一个线程想要写入数据时会阻塞,直到所有的读取线程释放锁。此时,写入线程才能获取锁,并修给数据。写入线程在锁定时,新的线程将被阻塞,直到写入线程完成。

    • Distributed lock(分布式锁): 分布式锁提供进程级别的互斥访问。与真正的互斥锁不同,分布式锁不会阻塞进程或阻止进程运行。它只是报告锁何时忙,让流程决定如何进行。

    • Double-checked lock(双重检查锁): 双重检查锁试图通过在锁定之前测试锁定标准来降低锁定的开销。由于双重检查的锁可能是不安全的,系统不提供对它们的明确的支持,并且它们的使用是不鼓励的。

    以上大致介绍了锁的分类,下面将介绍Objective-C中各种实现锁的方式。

    一、@synchronized指令

    简介

    @synchronized指令是Objective-C中易用性和可读性最好的创建互斥锁的方式。我们不用去直接创建锁和锁定对象,它会像其它互斥锁一样,防止其它线程获取同一个锁。传递给@synchronized的对象是区分保护块的唯一标识。它的简单用法是这样

    - (void)myMethod:(id)anObj
    {
        @synchronized(anObj)
        {
            //需要加锁的内容
        }
    }
    

    实现原理

    编译器将@synchronized转化成了一对objc_sync_enter()objc_sync_exit()的调用,通过查看源码我们可以分析得出:

    • @synchronized是通过recursive_mutex_t递归锁实现;
    • @synchronized(object)中传入的object的内存地址,被用作唯一的key,通过hash map对应到一个系统维护的递归锁;
    • objc_sync_enter()objc_sync_exit()并没有对传入的对象做retainsreleases;
    • objc_sync_enter(nil)objc_sync_exit(nil)不起任何作用;

    详细的分析可以参见这篇博客:正确使用多线程同步锁@synchronized()

    使用注意

    通过上面的分析, 我们可以得出@synchronized使用中应该注意的几个问题

    • 因为@synchronized使用递归锁实现的,所以如下代码不会产生死锁;
    @synchronized (obj) {
        NSLog(@"1st sync");
        @synchronized (obj) {
            NSLog(@"2nd sync");
        }
    }
    
    • 因为是利用传入的object的内存地址作为唯一标识,所以传入的object理论上可以是任意对象,但应避免不同的critical section使用相同的锁,应该是不同的数据使用不同的锁;
    @synchronized (objectA) {
        [arrA addObject:obj];
    }
    
    @synchronized (objectB) {
        [arrB addObject:obj];
    }
    
    • 应该注意传入对象的生命周期,因为@synchronized(object)并没有对object进行retains,因此当@synchronized(nil)时,将不起任何作用;

    • 注意@synchronized(object)内部的方法调用,将不需要同步操作的方法放在外面调用;

    • 另外官方文档中提到 :

    作为预防措施,@synchronized块隐式地向受保护的代码添加异常处理程序。如果引发异常,该处理程序会自动释放互斥锁。这意味着为了使用@synchronized指令,还必须在代码中启用Objective-C异常处理。如果您不想由隐式异常处理程序引起额外开销,则应考虑使用锁类。

    二、pthread_mutex

    简介

    pthread 表示 POSIX thread,定义了一组跨平台的线程相关的 API,pthread_mutex 表示互斥锁。相关函数:

    __API_AVAILABLE(macos(10.4), ios(2.0))
    int pthread_mutex_destroy(pthread_mutex_t *);
    
    __API_AVAILABLE(macos(10.4), ios(2.0))
    int pthread_mutex_getprioceiling(const pthread_mutex_t * __restrict,
            int * __restrict);
    
    __API_AVAILABLE(macos(10.4), ios(2.0))
    int pthread_mutex_init(pthread_mutex_t * __restrict,
            const pthread_mutexattr_t * _Nullable __restrict);
    
    __API_AVAILABLE(macos(10.4), ios(2.0))
    int pthread_mutex_lock(pthread_mutex_t *);
    
    __API_AVAILABLE(macos(10.4), ios(2.0))
    int pthread_mutex_setprioceiling(pthread_mutex_t * __restrict, int,
            int * __restrict);
    
    __API_AVAILABLE(macos(10.4), ios(2.0))
    int pthread_mutex_trylock(pthread_mutex_t *);
    
    __API_AVAILABLE(macos(10.4), ios(2.0))
    int pthread_mutex_unlock(pthread_mutex_t *);
    
    • pthread_mutex 可通过宏定义静态初始化pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
    • 动态方式是采用int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)函数来初始化互斥锁,mutexattr用于指定属性,如果为NULL则使用缺省属性。
    • int pthread_mutex_destroy(pthread_mutex_t *mutex)函数用于销毁一个互斥锁,这意味着释放它所占用的资源,且要求锁当前处于开放状态。
    • pthread_mutexPTHREAD_MUTEX_NORMAL(普通互斥锁)、PTHREAD_MUTEX_ERRORCHECK(检错锁)和PTHREAD_MUTEX_RECURSIVE(递归锁等属性;

    它的简单用法如下:

        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);// 定义锁的属性
    
        pthread_mutex_t mutex;
        pthread_mutex_init(&mutex, &attr); // 创建锁
    
        pthread_mutex_lock(&mutex); // 申请锁
        
        //需要加锁的代码
        
        pthread_mutex_unlock(&mutex); // 释放锁
    

    使用注意

    • 同一线程,多次获得同一锁时,会造成死锁,此时应使用PTHREAD_MUTEX_RECURSIVE属性。

    三、pthread_rwlock

    简介

    pthread_rwlockpthread中定义的读写锁,相关函数如下:

    __API_AVAILABLE(macos(10.4), ios(2.0))
    int pthread_rwlock_destroy(pthread_rwlock_t * ) __DARWIN_ALIAS(pthread_rwlock_destroy);
    
    __API_AVAILABLE(macos(10.4), ios(2.0))
    int pthread_rwlock_init(pthread_rwlock_t * __restrict,
            const pthread_rwlockattr_t * _Nullable __restrict)
            __DARWIN_ALIAS(pthread_rwlock_init);
    
    __API_AVAILABLE(macos(10.4), ios(2.0))
    int pthread_rwlock_rdlock(pthread_rwlock_t *) __DARWIN_ALIAS(pthread_rwlock_rdlock);
    
    __API_AVAILABLE(macos(10.4), ios(2.0))
    int pthread_rwlock_tryrdlock(pthread_rwlock_t *) __DARWIN_ALIAS(pthread_rwlock_tryrdlock);
    
    __API_AVAILABLE(macos(10.4), ios(2.0))
    int pthread_rwlock_trywrlock(pthread_rwlock_t *) __DARWIN_ALIAS(pthread_rwlock_trywrlock);
    
    __API_AVAILABLE(macos(10.4), ios(2.0))
    int pthread_rwlock_wrlock(pthread_rwlock_t *) __DARWIN_ALIAS(pthread_rwlock_wrlock);
    
    __API_AVAILABLE(macos(10.4), ios(2.0))
    int pthread_rwlock_unlock(pthread_rwlock_t *) __DARWIN_ALIAS(pthread_rwlock_unlock);
    
    __API_AVAILABLE(macos(10.4), ios(2.0))
    int pthread_rwlockattr_destroy(pthread_rwlockattr_t *);
    
    __API_AVAILABLE(macos(10.4), ios(2.0))
    int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t * __restrict,
            int * __restrict);
    
    __API_AVAILABLE(macos(10.4), ios(2.0))
    int pthread_rwlockattr_init(pthread_rwlockattr_t *);
    
    __API_AVAILABLE(macos(10.4), ios(2.0))
    int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *, int);
    
    • pthread_mutex 类似,pthread_rwlock也可以通过宏定义快速初始化:pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
    • 也可以通过int pthread_rwlock_init(pthread_rwlock_t * __restrict, const pthread_rwlockattr_t * _Nullable __restrict)初始化,其中pthread_rwlockattr_t是属性对象;
    • 属性对象可以通过int pthread_rwlockattr_init (pthread_rwlockattr_t *__attr);初始化。同时可以通过int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *, int)用来设置读写锁的作用范围,这里需要的int类型有两个宏定义PTHREAD_PROCESS_SHARED:该读写锁可以在多个进程中的线程之间共享。PTHREAD_PROCESS_PRIVATE:仅初始化本读写锁的线程所在的进程内的线程才能够使用该读写锁。

    使用注意

    • 由于读写锁的性质,必须要等到所有读锁都释放之后,才能成功申请写锁,这就很容易导致写线程饥饿。

    四、NSLock

    简介

    NSLock是典型的面向对象的锁,遵循Objective-CNSLocking协议接口,该协议定义了lockunlock。此外NSLock类还增加了tryLocklockBeforeDate:方法。tryLock方法尝试获取锁,如果锁不可用返回NOlockBeforeDate:尝试在指定时间内获取锁,如不成功返回NO

        NSLock *lock = [[NSLock alloc] init];
        //线程1
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"线程1 准备加锁ing...");
            [lock lock];
            NSLog(@"线程1 锁定成功");
            sleep(5);//睡眠5秒
            NSLog(@"线程1 准备解锁");
            [lock unlock];
            NSLog(@"线程1 解锁成功");
        });
        
        //线程2
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"线程2 尝试加锁ing...");
            BOOL x =  [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:4]];
            if (x) {
                NSLog(@"线程2 锁定成功");
                [lock unlock];
                NSLog(@"线程2 解锁成功");
            }else{
                NSLog(@"线程2 加锁失败");
            }
        });
    

    实现原理

    • NSLock是在内部封装了一个 pthread_mutex,属性为PTHREAD_MUTEX_ERRORCHECK,它会损失一定性能换来错误提示。

    使用注意

    • NSLock对象发送解锁消息时,必须确保该消息是从发送初始锁定消息的同一线程发送的。解锁来自不同线程的锁可能会导致未定义的行为。
    • 在同一线程上两次调用lock方法,会造成死锁,因此在递归中不能使用NSLock。应使用NSRecursiveLock

    五、NSRecursiveLock

    简介

    NSRecursiveLock是面向对象的递归锁,同样遵循Objective-CNSLocking协议接口。该锁可被同一线程多次获取,而不会造成死锁。只是记录获取锁成功的次数,只有调用解锁的次数与锁定次数相同时,锁才会被真正释放,此时才能被其它线程获取。

    NSRecursiveLock *rLock = [NSRecursiveLock new];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void (^RecursiveBlock)(int);
        RecursiveBlock = ^(int value) {
            [rLock lock];
            if (value > 0) {
                NSLog(@"线程%d", value);
                RecursiveBlock(value - 1);
            }
            [rLock unlock];
        };
        RecursiveBlock(4);
    });
    

    实现原理

    • NSRecursiveLock递归锁也是通过 pthread_mutex 来实现,与 NSLock 的区别在于内部封装的 pthread_mutex_t 对象的类型为 PTHREAD_MUTEX_RECURSIVE

    使用注意

    • 因为在所有锁定调用与解锁调用保持平衡之前,递归锁定不会被释放,所以我们应该仔细衡量使用递归锁潜在性能影响。长时间保持锁可能导致其他线程阻塞,直到递归完成。如果可以,我们应该从代码设计上尽量避免递归锁。

    六、NSCondition

    简介

    NSCondition同样遵循NSLocking协议,NSCondition提供了单独的信号量管理接口。

    - (void)wait;//阻塞当前线程直到条件锁发出信号为止,在调用此方法之前必须锁定接收器。
    - (BOOL)waitUntilDate:(NSDate *)limit;//阻塞当前线程直到条件锁发出信号或达到指定的时间限制为止。
    - (void)signal;//条件锁的信号,唤醒一个等待的线程,可多次调用唤醒多个线程,如没有被锁定的线程,则不起任何作用。为了避免竞争条件锁,应该仅在接收器锁定时调用此方法。
    - (void)broadcast;//唤醒全部等待的线程。
    
    

    实现原理

    • NSCondition是通过条件变量(condition variable) pthread_cond_t 来实现的,同时封装了一个互斥锁和条件变量。提供了线程阻塞与信号机制。

    使用注意

    • NSCondition是通过条件变量实现,而条件变量必须和一个互斥锁配合, 以防止多个线程同时请求pthread_cond_wait()的竞争条件。所以在调用waitwaitUntilDate方法前,必须在本线程调用lock方法,确保当前线程为锁定状态;
    • wait函数并不是完全可信的,可能存在虚假唤醒。也就是说wait返回后,并不代表对应的事件一定被触发了,因此,为了保证线程之间的同步关系,使用NSCondtion时往往需要加入一个额外的变量来对非正常的wait返回进行规避。
    //等待事件触发的线程  
    [cocoaCondition lock];  
    while (timeToDoWork <= 0)  
        [cocoaCondition wait];  
       
    timeToDoWork--;  
       
    // Do real work here.     
    [cocoaCondition unlock];  
    
      
    //出发事件的线程  
    [cocoaCondition lock];  
    timeToDoWork++;  
    [cocoaCondition signal];  
    [cocoaCondition unlock]; 
    
    • 经笔者测试,当多个线程被阻塞时,通过调用signal进行唤醒操作,线程被唤醒的顺序与wait的调用顺序相关,而与线程的优先级无关。
            NSLog(@"thread1:等待发送1");
            [cocoaCondition lock];
            [cocoaCondition wait];
            
            NSLog(@"thread1:发送1");
            [self.condition unlock];
            
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            sleep(1);
            
            NSLog(@"thread2:等待发送2");
            [cocoaCondition lock];
            [cocoaCondition wait];
            
            NSLog(@"thread2:发送2");
            [cocoaCondition unlock];
            
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            sleep(2);
            [cocoaCondition lock];
            NSLog(@"thread3:收到数据");
            [cocoaCondition signal];
            [cocoaCondition unlock];
            
        });
        
    
        打印结果:
         thread1:等待发送1
         thread2:等待发送2
         thread3:收到数据
         thread1:发送1
    

    七、NSConditionLock

    简介

    NSConditionLock遵循NSLocking协议,可以与用户自定义的条件相关联的互条件锁,是互斥锁的变种。提供了更加直观、方便的条件管理接口,可以更方便的实现生产者-消费者模式。

    - (instancetype)initWithCondition:(NSInteger)condition;//初始化一个条件锁,并设置条件;
    - (void)lockWhenCondition:(NSInteger)condition;//当条件满足时,获取锁。
    - (BOOL)tryLock;//不考虑条件,直接尝试获取锁,成功返回YES,反之为NO。
    - (BOOL)tryLockWhenCondition:(NSInteger)condition;//条件满足时尝试锁定,成功返回YES,反之为NO。
    - (void)unlockWithCondition:(NSInteger)condition;//解锁并重新设置条件。
    - (BOOL)lockBeforeDate:(NSDate *)limit;//在限制期限内获取锁,成功返回YES,反之为NO。
    - (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;//当条件满足时,在指定期限内获取锁,此方法会阻塞线程,直达获取锁(返回YES)或超时(返回NO)。
    

    NSConditionLock可以很方便的实现线程间的依赖关系:

        id condLock = [[NSConditionLock alloc] initWithCondition:0];
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [condLock lockWhenCondition:2];
            NSLog(@"线程1");
            [condLock unlockWithCondition:0];
            
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [condLock lockWhenCondition:1];
            NSLog(@"线程2");
            [condLock unlockWithCondition:2];
            
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [condLock lockWhenCondition:0];
            NSLog(@"线程3");
            [condLock unlockWithCondition:1];
            
        });
        
        打印结果:
        线程3
        线程2
        线程1
    

    实现原理

    NSConditionLock 借助 NSCondition 来实现 内部持有一个NSCondition对象,和一个_condition_value属性,调用lockWhenCondition:时,只有_condition_value条件值相等时,才能获得锁。

    八、dispatch_semaphore

    简介

    dispatch_semaphore信号量,GCD中基于信号控制访问资源的线程数量。当限定的线程数量为一时,就起到了和同步锁相同的效果;信号量主要的函数如下:

    dispatch_semaphore_create(long value);//用初始值创建新的计数信号量。注意:value值必须>=0,否则返回NULL。
    dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);//等待(递减)信号量。
    dispatch_semaphore_signal(dispatch_semaphore_t dsema);//信号(增加)信号量。
    

    信号量的简单用法如下:

        //value表示最多几个资源可访问
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
        dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
        //线程1
        dispatch_async(quene, ^{
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"线程1:启动");
            sleep(1);
            NSLog(@"线程1:完成");
            dispatch_semaphore_signal(semaphore);
        });
        //线程2
        dispatch_async(quene, ^{
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"线程2:启动");
            sleep(1);
            NSLog(@"线程2:完成");
            dispatch_semaphore_signal(semaphore);
        });
        //线程3
        dispatch_async(quene, ^{
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"线程3:启动");
            sleep(1);
            NSLog(@"线程3:完成");
            dispatch_semaphore_signal(semaphore);
        });
        
        当value = 3时,打印结果:
        线程1:启动
        线程2:启动
        线程3:启动
        
        线程1:完成
        线程2:完成
        线程3:完成
        
        当value = 2时,打印结果:
        线程1:启动 
        线程2:启动
     
        线程1:完成
        线程2:完成
        线程3:启动
        
        线程3:完成
        
        当value = 1时,打印结果:
        线程1:启动
        
        线程1:完成
        线程2:启动
        
        线程2:完成
        线程3:启动
        
        线程3:完成
    

    当信号计数大于0时,每条进来的线程使调用dispatch_semaphore_wait函数使计数减1;直到信号技术为0,阻塞其他线程,直到执行的线程调用dispatch_semaphore_signal函数使级数加1,信号技术大于0,允许阻塞的线程启动;可见,当信号计数控制为1时,可实现同步锁的作用。

    实现原理

    想要深入的理解dispatch_semaphore,可以参考这篇博客源码

    使用注意

    • 创建信号量时,传入的value参数必须>=0,否则返回NULL;
    • timeout参数为dispatch_time_t类型。比较有用的两个宏是DISPATCH_TIME_NOW(表示当前,立即返回超时)和DISPATCH_TIME_FOREVER (表示遥远的未来,一直等待下去)。一般可以直接设置timeout为这两个宏其中的一个,或者自己创建一个dispatch_time_t类型的变量。创建dispatch_time_t类型的变量有两种方法,dispatch_timedispatch_walltime
    • 通过dispatch_semaphore特征我们不难发现, 它不支持递归。

    九、OSSpinLock

    简介

    OSSpinLock是iOS/MacOS自有的自旋锁,其特点是线程在等待取锁时,不会被挂起,而是保持空转,这避免了上下文切换等锁的操作。适用于临界区耗时短的操作,如果等待取锁的时间过长,轮训操作会消耗大量CPU资源。其主要函数和简单适用如下:

    typedef int32_t OSSpinLock;
    
    bool    OSSpinLockTry( volatile OSSpinLock *__lock );
    
    void    OSSpinLockLock( volatile OSSpinLock *__lock );
    
    void    OSSpinLockUnlock( volatile OSSpinLock *__lock );
    
    
    __block OSSpinLock spinLock = OS_SPINLOCK_INIT;
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            OSSpinLockLock(& spinLock);
            NSLog(@"线程1");
            sleep(10);
            OSSpinLockUnlock(& spinLock);
            NSLog(@"线程1解锁成功");
        });
        
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            sleep(1);
            OSSpinLockLock(& spinLock);
            NSLog(@"线程2");
            OSSpinLockUnlock(& spinLock);
        });
    
    

    因优先级反转的问题,在不同优先级的线程中OSSpinLock已不在安全,详细的介绍可参见这篇博客不再安全的 OSSpinLock,这里不在做过多介绍。

    性能对比

    这里贴出一张ibireme测试的结果图:


    性能对比

    总结

    以上介绍的几种加锁方式,在原理、用法和性能等方面上各有不同。我们不能单从性能方面评论孰好孰坏。应该根据不同的需求和场景,选取合适的加锁方式。

    • 对性能要求苛刻,且临界区耗时很短,可以考虑自旋锁;
    • 同样对性能要求高,dispatch_semaphorepthread_mutex也是不错的选择;
    • 如果要临界区有大量的读写操作,且读多写少,那么pthread_rwlock或许是不错的选择;
    • NSLockNSRecursiveLock更加面向对象,用起来或许跟顺手;
    • NSConditionLock虽然性能不强,但是提供了方便的面向对象的条件管理接口;
    • 在递归中使用pthread_mutex(recursive)NSRecurisiveLcok都不错;
    • @synchronized虽然性能最差,但是操作简单快捷,如果是低频操作,或者紧急修复,它也是很好的选择;

    参考资料

    Synchronization

    More than you want to know about @synchronized

    pthread_mutex_lock.c 源码

    深入理解 iOS 开发中的锁

    深入理解GCD

    pthread的各种同步机制

    不再安全的 OSSpinLock

    相关文章

      网友评论

        本文标题: iOS开发中的锁

        本文链接:https://www.haomeiwen.com/subject/diezmxtx.html