iOS底层原理探索 ----- 锁

974 阅读28分钟

资源准备

锁的介绍

锁的类型

  • 自旋锁:线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显示释放自旋锁。自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的:

    • OSSpinLock
    • os_unfair_lock
  • 互斥锁:是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。该目的通过将代码切片成一个一个的临界区而达成:

    • NSLock
    • pthread_mutex
    • @synchronized
  • 条件锁:就是条件变量,当进程的某些资源要求不满足时就进入休眠,也就是锁住了。当资源被分配到了,条件锁打开,进程继续运行:

    • NSCondition
    • NSConditionLock
  • 递归锁:同一个线程可以加锁N次而不会引发死锁

    • NSRecursiveLock
    • pthread_mutex(recursive)
  • 信号量(semaphore):一种更高级的同步机制,互斥锁可以说是semaphore在仅取值0/1时的特例。信号量可以有更多的取值空间,用来实现更加复杂的同步,而不单单是线程间互斥:

    • dispatch_semaphore
  • 读写锁:一种特殊的自旋锁。它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作:

    • 相比自旋锁而言,能提高并发性。因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。但写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数先关),但不能同时既有读者又有写者。在读写锁保持期间也是抢占失效的;

    • 如果读写锁当前没有读者,也没有写者,那么写者可以立刻获得读写锁,否则它必须自旋在那里,直到没有任何写者或读者;

    • 如果读写锁没有写者,那么读者可以立即获得该读写锁,否则读者必须自旋在那里,直到写者释放该读写锁。

其实基本的锁就包括三类:⾃旋锁互斥锁读写锁,其他的⽐如条件锁递归锁信号量都是上层的封装和实现:

  • 互斥锁 = 互斥 + 同步,互斥保证线程安全,当一条线程执行时,其他线程休眠。同步保证执行顺序,多线程串行执行

    • NSLockpthread_mutex@synchronizedNSConditionNSConditionLockNSRecursiveLockpthread_mutex(recursive)dispatch_semaphore
  • 自旋锁 = 互斥 + 忙等,例如do...while循环。它的优点在于不会引起调用者睡眠,所以不会进行线程调度,CPU时间片轮转等耗时操作。而缺点是当等待时会消耗大量CPU资源,所以自旋锁不适用较长时间的任务:

    • OSSpinLockos_unfair_lock

锁的性能

2021iPhone 12真机测试,锁的性能对比图:

67921E81-50A9-469E-8323-BB52B5EC758F.png

  • 性能从高到低依次排列:OSSpinLock(自旋锁)>os_unfair_lock(自旋锁)>NSCondition(条件锁)>pthread_mutex(互斥锁)>NSLock(互斥锁)>dispatch_semaphore(信号量)>pthread_mutex(recursive)(递归锁)>NSRecursiveLock(递归锁)>@synchronized(互斥锁)>NSConditionLock(条件锁)

测试方案

循环十万次,进行加锁和解锁操作。通过开始、结束时间,计算各自锁的耗时:

    int kc_runTimes = 100000;
  • OSSpinLock
{
    OSSpinLock kc_spinlock = OS_SPINLOCK_INIT;
    
    double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
    for (int i=0 ; i < kc_runTimes; i++) {
        OSSpinLockLock(&kc_spinlock);          //解锁
        OSSpinLockUnlock(&kc_spinlock);
    }
    double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
    KCLog(@"OSSpinLock: %f ms",(kc_endTime - kc_beginTime)*1000);
}
  • dispatch_semaphore_t
{
    dispatch_semaphore_t kc_sem = dispatch_semaphore_create(1);

    double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
    for (int i=0 ; i < kc_runTimes; i++) {
        dispatch_semaphore_wait(kc_sem, DISPATCH_TIME_FOREVER);
        dispatch_semaphore_signal(kc_sem);
    }
    double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
    KCLog(@"dispatch_semaphore_t: %f ms",(kc_endTime - kc_beginTime)*1000);
}
  • os_unfair_lock_lock
{
    os_unfair_lock kc_unfairlock = OS_UNFAIR_LOCK_INIT;

    double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
    for (int i=0 ; i < kc_runTimes; i++) {
        os_unfair_lock_lock(&kc_unfairlock);
        os_unfair_lock_unlock(&kc_unfairlock);
    }
    double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
    KCLog(@"os_unfair_lock_lock: %f ms",(kc_endTime - kc_beginTime)*1000);
}
  • pthread_mutex_t
{
    pthread_mutex_t kc_metext = PTHREAD_MUTEX_INITIALIZER;

    double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
    for (int i=0 ; i < kc_runTimes; i++) {
        pthread_mutex_lock(&kc_metext);
        pthread_mutex_unlock(&kc_metext);
    }
    double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
    KCLog(@"pthread_mutex_t: %f ms",(kc_endTime - kc_beginTime)*1000);
}
  • NSLock
{
    NSLock *kc_lock = [NSLock new];

    double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
    for (int i=0 ; i < kc_runTimes; i++) {
        [kc_lock lock];
        [kc_lock unlock];
    }
    double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
    KCLog(@"NSlock: %f ms",(kc_endTime - kc_beginTime)*1000);
}
  • NSCondition
{
    NSCondition *kc_condition = [NSCondition new];

    double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
    for (int i=0 ; i < kc_runTimes; i++) {
        [kc_condition lock];
        [kc_condition unlock];
    }
    double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
    KCLog(@"NSCondition: %f ms",(kc_endTime - kc_beginTime)*1000);
}
  • PTHREAD_MUTEX_RECURSIVE
{
    pthread_mutex_t kc_metext_recurive;
    pthread_mutexattr_t attr;
    pthread_mutexattr_init (&attr);
    pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init (&kc_metext_recurive, &attr);

    double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
    for (int i=0 ; i < kc_runTimes; i++) {
        pthread_mutex_lock(&kc_metext_recurive);
        pthread_mutex_unlock(&kc_metext_recurive);
    }
    double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
    KCLog(@"PTHREAD_MUTEX_RECURSIVE: %f ms",(kc_endTime - kc_beginTime)*1000);
}
  • NSRecursiveLock
{
    NSRecursiveLock *kc_recursiveLock = [NSRecursiveLock new];

    double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
    for (int i=0 ; i < kc_runTimes; i++) {
        [kc_recursiveLock lock];
        [kc_recursiveLock unlock];
    }
    double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
    KCLog(@"NSRecursiveLock: %f ms",(kc_endTime - kc_beginTime)*1000);
}
  • NSConditionLock
{
    NSConditionLock *kc_conditionLock = [NSConditionLock new];

    double_t kc_beginTime = CFAbsoluteTimeGetCurrent();
    for (int i=0 ; i < kc_runTimes; i++) {
        [kc_conditionLock lock];
        [kc_conditionLock unlock];
    }
    double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
    KCLog(@"NSConditionLock: %f ms",(kc_endTime - kc_beginTime)*1000);
}
  • @synchronized
{
    double_t kc_beginTime = CFAbsoluteTimeGetCurrent();

    for (int i=0 ; i < kc_runTimes; i++) {
        @synchronized(self) {}

    }
    double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
    KCLog(@"@synchronized: %f ms",(kc_endTime - kc_beginTime)*1000);
}

iphoneX真机执行结果:

OSSpinLock: 0.485063 ms
dispatch_semaphore_t: 0.717044 ms
os_unfair_lock_lock: 0.768065 ms
pthread_mutex_t: 0.838041 ms
NSlock: 1.092076 ms
NSCondition: 1.066923 ms
PTHREAD_MUTEX_RECURSIVE: 1.288056 ms
NSRecursiveLock: 1.695037 ms
NSConditionLock: 5.481005 ms
@synchronized: 3.665924 ms
  • 可以看到@synchronized的执行速度并不是最慢的,说明系统进行了优化。

iphoneX模拟器执行结果:

OSSpinLock: 0.509977 ms
dispatch_semaphore_t: 0.768065 ms
os_unfair_lock_lock: 0.856042 ms
pthread_mutex_t: 1.099944 ms
NSlock: 1.405954 ms
NSCondition: 1.512051 ms
PTHREAD_MUTEX_RECURSIVE: 1.899004 ms
NSRecursiveLock: 3.090978 ms
NSConditionLock: 4.673004 ms
@synchronized: 5.235076 ms
  • 可以看到在模拟器中@synchronized的效率低了不少,为什么呢,我们最下面会进行分析。

其中@synchronized比之前在老版本中的测试结果快了很多,说明官方对其进行了优化

使用模拟器运行,运行结果和真机略有不同,因为系统底层在真机和模拟器上的处理有一些差异。

@synchronized探索

底层实现

我们想对@synchronized进行分析,先要知道它调用了什么方法,我们可以有两种方式进行探索:1. clang编译出C++代码,2. 汇编跟源码。

clang编译出C++代码

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        @synchronized (appDelegateClassName) {
        }
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

使用xcrun生成cpp文件:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp

打开main.cpp文件,找到main函数的实现:

image.png

  • 调用_sync_exit传入_sync_obj,相当于调用结构体的构造函数和析构函数。构造函数中没有代码,而析构函数中调用objc_sync_exit函数,传入的sync_exit等同于_sync_obj

    • 使用objc_sync_enter(_sync_obj)函数,进行加锁;

    • 使用objc_sync_exit(_sync_obj)函数,进行解锁

  • 使用try...catch,说明锁的使用有可能出现异常

通过汇编追踪

main 函数设置断点,使用模拟器运行,查看汇编代码:

940CC99D-3949-4819-9C1F-08EEAA47DD15.png

  • 根据汇编追踪,同样的效果,objc_sync_enterobjc_sync_exit函数成对出现,分别进行加锁和解锁操作。

objc_sync_enter设置符号断点:

BCBE304F-0216-4DAA-8B31-40BA5008DBF8.png

  • 可以发现,这个函数来自于libobjc.A.dylib

底层源码分析

objc_sync_enter

打开objc4-818.2源码,进入objc_sync_enter函数:

5ACFBCD4-C554-4B87-82BA-A6198696EF01.png

  • 如果obj存在,执行id2data函数获取相应的SyncData,对threadCountlockCount进行递增操作。;
  • 否则,执行objc_sync_nil函数;

进入objc_sync_nil函数:

485813F3-5F7B-486C-9417-7BC1039C97A8.png

找到BREAKPOINT_FUNCTION的宏定义:

59FA07D5-35F9-46D8-826A-E627F324D241.png

实际上objc_sync_nil中的代码,相当于传入将void objc_sync_nil(void)传入宏,等同于以下代码:

void objc_sync_nil(void) { asm(""); }
  • 相当于无效代码,不会进行加锁操作

查看objc_sync_nil的汇编代码:

libobjc.A.dylib`objc_sync_nil: -> 0x7fff2018a7a3 <+0>: ret
  • 什么都不处理,直接返回

所以,使用@synchronized时,传入nil,相当于无效代码,不会进行加锁操作。

objc_sync_exit

F90BAE72-8386-4FC4-84D4-58898401A0D5.png

  • 如果obj存在,则调用id2data方法获取对应的SyncData,对threadCountlockCount进行递减操作。
  • 如果objnil,什么也不做。

由此可见,objc_sync_enterobjc_sync_exit中的核心代码都是id2data函数,参数传入的ACQUIRERELEASE有所区别,但最终都获取到一个SyncData对象。

  • objc_sync_enter中,对SyncData对象中的mutex,调用lock进行加锁;

  • objc_sync_exit中,对SyncData对象中的mutex,调用tryUnlock进行解锁。

SyncData结构

找到SyncData的结构定义

5E5F2C7D-3E7E-43A3-96FD-1908A74BD8FD.png

SyncData结构,属于单向链表

  • nextData指向下一条数据
  • DisguisedPtr<objc_object>用于封装类型
  • threadCount记录多线程操作数
  • recursive_mutex_t递归锁,可以递归使用,但不支持多线程递归

所以,通过SyncData的结构不难看出,@synchronized为递归互斥锁,支持多线程递归使用,比recursive_mutex_t更加强大。

id2data

进入id2data函数:

static SyncData* id2data(id object, enum usage why)
{
    //1、传入object,从哈希表中获取数据 
    //传入object,从哈希表中获取lock,用于保证分配SyncData代码的线程安全
    spinlock_t *lockp = &LOCK_FOR_OBJ(object);

    //传入object,从哈希表中获取SyncData的地址,等同于SyncList
    SyncData **listp = &LIST_FOR_OBJ(object);

    SyncData* result = NULL;

#if SUPPORT_DIRECT_THREAD_KEYS

    // Check per-thread single-entry fast cache for matching object

    bool fastCacheOccupied = NO;

    //2、在当前线程的tls(线程局部存储)中寻找
    SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);

    if (data) {

        fastCacheOccupied = YES;
        
        //SyncData中的对象和传入的对象相同
        if (data->object == object) {

            // Found a match in fast cache.
            //可以进入到这里,应该是同一线程中对同一对象,进行嵌套@synchronized
            uintptr_t lockCount;

            result = data;

            lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);

            if (result->threadCount <= 0  ||  lockCount <= 0) {

                _objc_fatal("id2data fastcache is buggy");

            }

            switch(why) {

            case ACQUIRE: {
                //锁的次数+1
                lockCount++;
                //存储到tls中
                tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);

                break;

            }

            case RELEASE:
                //锁的次数-1
                lockCount--;
                //存储到tls中
                tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);

                if (lockCount == 0) {

                    // remove from fast cache
                    //删除tls线程局部存储
                    tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);

                    // atomic because may collide with concurrent ACQUIRE
                    //对SyncData对象的threadCount进行-1,因为当前线程中的对象已经解锁
                    OSAtomicDecrement32Barrier(&result->threadCount);

                }

                break;

            case CHECK:

                // do nothing

                break;

            }

            return result;

        }

    }

#endif


    // Check per-thread cache of already-owned locks for matching object
    //3、tls中未找到,在各自线程的缓存中查找
    SyncCache *cache = fetch_cache(NO);

    if (cache) {

        unsigned int i;
        //遍历缓存
        for (i = 0; i < cache->used; i++) {

            SyncCacheItem *item = &cache->list[i];
            //item中的对象和传入的对象不一致,跳过
            if (item->data->object != object) continue;

            // Found a match.

            result = item->data;

            if (result->threadCount <= 0  ||  item->lockCount <= 0) {

                _objc_fatal("id2data cache is buggy");

            }                

            switch(why) {

            case ACQUIRE:
                //锁的次数+1
                item->lockCount++;

                break;

            case RELEASE:
                //锁的次数-1
                item->lockCount--;

                if (item->lockCount == 0) {

                    // remove from per-thread cache
                    //从缓存中删除
                    cache->list[i] = cache->list[--cache->used];

                    // atomic because may collide with concurrent ACQUIRE
                    //对SyncData对象的threadCount进行-1,因为当前线程中的对象已经解锁
                    OSAtomicDecrement32Barrier(&result->threadCount);

                }

                break;

            case CHECK:

                // do nothing

                break;

            }

            return result;

        }

    }

    // Thread cache didn't find anything.

    // Walk in-use list looking for matching object

    // Spinlock prevents multiple threads from creating multiple 

    // locks for the same new object.

    // We could keep the nodes in some hash table if we find that there are

    // more than 20 or so distinct locks active, but we don't do that now.
    //加锁,保证下面分配SyncData代码的线程安全
    lockp->lock();

    {

        SyncData* p;

        SyncData* firstUnused = NULL;
        //4、遍历SyncList,如果无法遍历,证明当前object第一次进入,需要分配新的SyncData
        for (p = *listp; p != NULL; p = p->nextData) {
            //遍历如果链表中存在SyncData的object和传入的object相等
            if ( p->object == object ) {
                //将p赋值给result
                result = p;
                //对threadCount进行+1
                // atomic because may collide with concurrent RELEASE
                OSAtomicIncrement32Barrier(&result->threadCount);
                //跳转至done
                goto done;
            }
            //找到一个未使用的SyncData
            if ( (firstUnused == NULL) && (p->threadCount == 0) )

                firstUnused = p;

        }
    
        // no SyncData currently associated with object
        //未找到与对象关联的SyncData,如果当前非ACQUIRE逻辑,直接进入done
        if ( (why == RELEASE) || (why == CHECK) )

            goto done;

        // an unused one was found, use it
        //从SyncList中找到未使用的SyncData,进行覆盖
        if ( firstUnused != NULL ) {
            //赋值给result
            result = firstUnused;

            result->object = (objc_object *)object;

            result->threadCount = 1;
            //跳转至done
            goto done;
        }
    }

    // Allocate a new SyncData and add to list.

    // XXX allocating memory with a global lock held is bad practice,

    // might be worth releasing the lock, allocating, and searching again.

    // But since we never free these guys we won't be stuck in allocation very often.
    //5、分配一个新的SyncData并添加到SyncList中
    posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));

    result->object = (objc_object *)object;

    result->threadCount = 1;

    new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
    //使用单链表头插法,新增节点总是插在头部
    result->nextData = *listp;

    *listp = result;

 done:
    //解锁,保证上面分配SyncData代码的线程安全
    lockp->unlock();

    if (result) {
         
        // Only new ACQUIRE should get here.
        // All RELEASE and CHECK and recursive ACQUIRE are 
        // handled by the per-thread caches above.

         //一些错误处理,应该只有ACQUIRE时,产生新SyncData时进入这里 
         //所有的RELEASE和CHECK和递归ACQUIRE,都应该由上面的线程缓存处理
        if (why == RELEASE) {

            // Probably some thread is incorrectly exiting 

            // while the object is held by another thread.

            return nil;

        }

        if (why != ACQUIRE) _objc_fatal("id2data is buggy");

        if (result->object != object) _objc_fatal("id2data is buggy");
//6、保存到tls线程或者缓存中
#if SUPPORT_DIRECT_THREAD_KEYS

        if (!fastCacheOccupied) {
            //保存到当前线程的tls中
            // Save in fast thread cache

            tls_set_direct(SYNC_DATA_DIRECT_KEY, result);

            tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);

        } else 

#endif
        {
            // Save in thread cache
            //tls还在占用,保存到缓存
            if (!cache) cache = fetch_cache(YES);

            cache->list[cache->used].data = result;

            cache->list[cache->used].lockCount = 1;

            cache->used++;
        }
    }
    return result;
}
  • 【第一步】传入object,从哈希表中获取数据

    • 传入object,从哈希表中获取lock,用于保证分配SyncData代码的线程安全

    • 传入object,从哈希表中获取SyncData的地址,等同于SyncList

  • 【第二步】在当前线程的tls(线程局部存储)中寻找

    • tls中获取SyncData

    • 如果存在,对比SyncData中的对象和传入的对象是否相同

      • 相同,根据传入的类型,对lockCount进行++--,更新tls。如果是RELEASE操作,对SyncData对象的threadCount进行-1,因为当前线程中的对象已经解锁。完成以上逻辑,直接返回result

      • 不同,进入【第三步】

    • 不存在,进入【第三步】

  • 【第三步】tls中未找到,在各自线程的缓存中查找

    • 调用fetch_cache函数,获取SyncCache

    • 如果存在,遍历缓存,对比item中的对象和传入的对象是否相同

      • 相同,根据传入的类型,对lockCount进行++--,更新缓存。如果是RELEASE操作,对SyncData对象的threadCount进行-1,因为当前线程中的对象已经解锁。完成以上逻辑,直接返回result

      • 遍历结束,未找到相同对象,进入【第四步】

    • 不存在缓存,进入【第四步】

  • 【第四步】遍历SyncList找到SyncData,相当于在所有线程中寻址

    • 对比SyncData中的对象和传入的对象是否相同,同时在遍历过程中,找到未使用的SyncData

      • 相同,对对threadCount进行+1,进入【第六步】

      • 遍历结束,未找到相同对象,查看未使用的SyncData是否存在

        • 存在,将未使用的SyncData中的object覆盖,threadCount重置为1,进入【第六步】

        • 不存在,需要分配新的SyncData,进入【第五步】

    • 如果无法遍历,证明当前object第一次进入,需要分配新的SyncData,进入【第五步】

  • 【第五步】分配一个新的SyncData并添加到SyncList

    • object赋值,threadCount初始化为1

    • 使用单链表头插法,新增节点总是插在头部

    • 进入【第七步】

  • 【第六步】将SyncData保存到tls线程或者缓存中

    • 判断fastCacheOccupied,如果tls中存在SyncDatafastCacheOccupied为真

      • 如果为真,说明tls还在占用,保存到缓存

      • 如果为假,保存到当前线程的tls

通过源码分析,同线程中的lockCount,表示@synchronized可递归使用。而SyncData中的threadCount,表示@synchronized可在多线程中使用。

哈希表的结构

9FF1B626-35DA-4B30-867C-665E99EC0181.png

  • lockdata都取自sDataLists,类型为StripedMap,使用static修饰,系统中只存在一份。

来到StripedMap的定义

61759D23-BC4D-43D3-AD3A-9FADBFB9EB8E.png

  • StripedMap是哈希表结构,真机预留8个空间,而模拟器上预留64个空间。所以之前测试锁的性能,使用真机和模拟器运行,结果略有不同。

存储到链表的情况,当不同对象生成相同哈希,也就是出现哈希冲突的情况,会直接存储在链表中

另一种情况,以模拟器为例,当StripedMap64个位置都插满后,并没有扩容操作。此时出现新对象,一定会出现哈希冲突,这时会将SyncData插入到链表中。

从代码逻辑上看,会进入【第四步】遍历SyncList,但因为SyncData中的对象和传入的对象不同,会在链表中找未使用的SyncData覆盖。如果找不到未使用的SyncData,会分配一个新的SyncData并添加到SyncList中:

image-1.png

TLS线程相关解释

线程局部存储(Thread Local Storage,TLS):是操作系统为线程单独提供的私有空间,通常只有有限的容量

Linux系统下通过pthread库中的API使用:

  • pthread_key_create()

  • pthread_getspecific()

  • pthread_setspecific()

  • pthread_key_delete()

注意事项

  • @synchronized为递归互斥锁,lockCount表示可递归使用,threadCount表示可在多线程中使用;

  • 使用@synchronized时,不能传入nil,使用nil锁的功能无法生效;

  • 在日常开发中,经常会传入self,它的好处可以保证生命周期同步,对象不会提前释放;

  • 不能使用非OC对象作为加锁对象,因为其object的参数为id类型;

  • 底层的缓存和链表都使用循环遍历查找,所以性能偏低。但开发中使用方便简单,并且不用解锁,所以使用频率较高。

pthread_mutex

Posix Thread中定义有⼀套专⻔⽤于线程同步的mutex函数。

mutex:⽤于保证在任何时刻,都只能有⼀个线程访问该对象。当获取锁操作失败时,线程会进⼊睡眠,等待锁释放时被唤醒。

我们看下 api

//导入头文件 
#import <pthread/pthread.h> 

//锁的声明 
pthread_mutex_t _lock; 

//锁的初始化 
pthread_mutex_init(&_lock, NULL); 

//加锁 
pthread_mutex_lock(&_lock); 

//解锁 
pthread_mutex_unlock(&_lock); 

//锁的释放 
pthread_mutex_destroy(&_lock);

NSLock

NSLock的底层对pthread_mutex进行封装,同样是一把互斥锁。但它属于非递归互斥锁,所有不能进行递归使用。

我们看下 api

//锁的初始化
NSLock *lock = [[NSLock alloc] init];

//加锁 
[lock lock]; 

//解锁 
[lock unlock];

源码分析

NSLock的源码在Foundation框架中:

image.png

由于OCFoundation框架未能开源,我们通过SwiftFoundation源码代替

打开swift-corelibs-foundation-master项目,找到NSLock的实现

NSLock遵循NSLocking协议

038533C4-829A-45DA-8CD4-7CE9A83B8773.png

NSLock的初始化、销毁、加锁、解锁功能,都是对pthread_mutex进行二次封装:

image.png

image-1.png

  • NSLock的初始化方法中,封装了pthread_mutex的初始化;

  • 使用NSLock必须调用它的init方法;

  • 通过pthread_cond_broadcast广播,唤醒在锁中等待的所有线程。

NSLock 的不足之处

NSLock属于非递归互斥锁,所有不能进行递归使用。

举个例子:

- (void)lg_testRecursive{ 
    NSLock *lock = [[NSLock alloc] init]; 
    for (int i=0; i<10; i++) { 
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            static void (^testMethod)(int); 
            testMethod = ^(int value){ 
                if (value > 0) { 
                    NSLog(@"current value = %d",value); 
                    testMethod(value - 1); 
                } 
            }; 
            [lock lock]; 
            testMethod(10); 
            [lock unlock]; 
        }); 
    } 
}
  • 在异步函数的代码中,使用NSLock进行加锁和解锁,可以保证线程安全,从10~1顺序输出,循环打印10遍。

block方法中进行加锁,同样可以保证线程安全。但NSLock属于非递归锁,遇到递归场景,在没有unlock的情况下,再次执行lock,造成死锁:

image-1.png

  • 只输出了 10 , 后面的结果无法继续打印。

NSRecursiveLock

NSRecursiveLock也是对pthread_mutex进行封装,属于递归锁。它可以允许同一线程多次加锁,而不会造成死锁。

  • NSLock相比,它可以在递归场景中使用;
  • 但它没有@synchronized强大,因为它不支持在多线程中递归加锁。

看下 api 的调用:

//锁的初始化 
NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init]; 

//加锁 
[recursiveLock lock]; 

//解锁 
[recursiveLock unlock];

源码分析

找到NSRecursiveLock的实现,同样遵循了NSLocking协议。NSRecursiveLock的初始化、销毁、加锁、解锁功能,都是对pthread_mutex进行二次封装:

image.png image-1.png

NSRecursiveLock的实现和NSLock非常相似,它们最大的区别在于初始化,NSRecursiveLockpthread_mutex设置PTHREAD_MUTEX_RECURSIVE标示,故此NSRecursiveLock是一把递归互斥锁。

pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))

NSRecursiveLock的不足之处

NSRecursiveLock会跟踪它被lock的次数。每次成功的lock都必须平衡调用unlock操作。只有所有达到这种平衡,锁最后才能被释放,以供其它线程使用。用个案例演示下:

1D2C0828-FF92-49E0-9230-02560D177E42.png

  • 从打印结果上看,在单一线程中正常打印出10~1后,多线程递归执行报错。

NSCondition

NSCondition属于条件锁,使用的方式和信号量相似。当线程满足需求后才会继续执行,否则会阻塞线程,使其进入休眠状态。

NSCondition的对象实际上作为⼀个锁和⼀个线程检查器:

  • 锁主要为了当检测条件时保护数据源,执⾏条件引发的任务
  • 线程检查器主要是根据条件决定是否继续运⾏线程,即线程是否被阻塞

查看下 api

//⽤于多线程同时访问、修改同⼀个数据源 
//保证在同⼀时间内数据源只被访问、修改⼀次 
//其他线程的命令需要在lock外等待,直到unlock,才可访问 
[condition lock];

//与lock同时使⽤ 
[condition unlock]; 

//使当前线程处于等待状态 
[condition wait];

//CPU发信号告诉线程不⽤等待,可以继续执⾏ 
[condition signal];

使用

生产消费者的模型案例:

#import "ViewController.h"

@interface ViewController () 

@property (nonatomic, assign) NSUInteger ticketCount; 
@property (nonatomic, strong) NSCondition *testCondition;

@end

@implementation ViewController 
- (void)viewDidLoad { 
    [super viewDidLoad]; 
    
    [self lg_testConditon]; 
}

#pragma mark -- NSCondition 
- (void)lg_testConditon{ 
    _testCondition = [[NSCondition alloc] init]; 
    
    //创建生产-消费者 
    for (int i = 0; i < 50; i++) { 
       dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ 
           [self lg_consumer]; 
       }); 
       dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ 
           [self lg_consumer]; 
       }); 
       
       dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ 
           [self lg_producer];
       }); 
       
       dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ 
           [self lg_producer]; 
       }); 
    } 
 } 
 
 - (void)lg_producer{ 
     //操作的多线程影响
     [_testCondition lock]; 
     
     self.ticketCount = self.ticketCount + 1; 
     
     NSLog(@"生产一个 现有 count %zd",self.ticketCount); 
     
     //信号 
     [_testCondition signal]; 
     
     [_testCondition unlock]; 
} 

- (void)lg_consumer{ 

    //操作的多线程影响 
    [_testCondition lock]; 

    while (self.ticketCount == 0) {

        NSLog(@"等待 count %zd",self.ticketCount); 
        [_testCondition wait]; 
    }

    //注意消费行为,要在等待条件判断之后 
    self.ticketCount -= 1;
    NSLog(@"消费一个 还剩 count %zd",self.ticketCount); 
    
    [_testCondition unlock]; 
}

@end

生产者和消费者都进行了加锁处理,保证线程安全

当消费者发现库存为0的时候,线程等待

当生产者增加库存后,发送信号。消费者收到信号,之前等待的线程继续执行

消费者判断库存为0,使用while循环,而不是if判断:

  • 使用if判断,唤醒线程后会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码;

  • 使用while循环,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait

源码分析

找到NSCondition的实现:

image.png image.png image.png

  • 底层采用pthread_mutex + pthread_cond实现:

    • pthread_mutex:对互斥锁进行封装;

    • pthread_cond:用来控制条件变量的执行情况。

  • wait:操作会阻塞线程,使其进入休眠状态;

  • signal:操作唤醒一个正在休眠等待的线程,使其继续执行wait之后的代码块;

  • broadcast:唤醒在锁中等待的所有线程。

NSConditionLock

NSConditionLock是一把条件锁,⼀个线程获得锁,其他线程⼀定等待。它的内部对NSCondition又做了一层封装,自带条件探测,能够更简单灵活的使用。

查看下 api

⽆条件锁

[conditionLock lock];

[conditionLock unlock];
  • 表示condition期待获得锁,如果没有其他线程获得锁(不需要判断内部的condition) ,那它能执⾏此⾏以下代码;

  • 如果已经有其他线程获得锁(可能是条件锁,或者⽆条件锁),则等待,直⾄其他线程解锁。

条件锁

[conditionLock lockWhenCondition:A条件];
  • 表示如果没有其他线程获得该锁,但是该锁内部的condition不等于A条件,它依然不能获得锁,仍然等待;

  • 如果内部的condition等于A条件,并且没有其他线程获得该锁,则进⼊代码区,同时设置它获得该锁,其他任何线程都将等待它代码的完成,直⾄它解锁;

  • 所谓的A条件就是整数,内部通过整数⽐较条件。

释放条件锁

[conditionLock unlockWithCondition:A条件];
  • 表示释放锁,同时把内部的condition设置为A条件

条件锁 + 超时时间

return = [conditionLock lockWhenCondition:A条件 beforeDate:A时间];
  • 表示如果被锁定(没获得锁),并超过该时间则不再阻塞线程。但是注意:返回的值是NO,它没有改变锁的状态,这个函数的⽬的在于可以实现两种状态下的处理。

NSConditionLockNSCondition 相比:

  • 相同点:

    • 都是互斥锁;

    • 通过条件变量来控制加锁、释放锁,从而达到阻塞线程、唤醒线程的目的。

  • 不同点:

    • NSCondition是基于pthread_mutex的封装,而NSConditionLock是基于NSCondition又做了一层封装;

    • NSCondition需要手动让线程进入等待状态阻塞线程、释放信号唤醒线程,而NSConditionLock只需要外部传入一个值,就会依据这个值进行自动判断,决定阻塞线程还是唤醒线程。

使用

案例演示:

- (void)lg_testConditonLock {
    NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ 
        [conditionLock lockWhenCondition:1];
        NSLog(@"线程1"); 
        [conditionLock unlockWithCondition:0]; 
    });

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ 
        [conditionLock lockWhenCondition:2];
        NSLog(@"线程2"); 
        [conditionLock unlockWithCondition:1];
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{ 
        [conditionLock lock]; 
        NSLog(@"线程3"); 
        [conditionLock unlock]; 
    });
}

打印结果: 4361652E-6351-4FFF-96EE-BDF58B5E033F.png

  • 案例的执行结果,大概率为线程3、线程2、线程1

NSConditionLock初始化,设置的条件为2。按照线程优先级顺序:

  • 线程1优先级最高,但不符合条件,代码块无法执行,进入等待状态;

  • 线程3默认优先级,同时属于无条件锁,可以执行代码块;

  • 线程2优先级最低,但符合条件,也可以执行代码块;

所以,大概率线程3会在线程2之前打印,而线程1必须等待线程2执行完,释放条件锁之后才能执行。

源码分析

初始化

image-1.png

  • NSCondition和条件value,作为NSConditionLock的成员变量;
  • 初始化时将条件赋值value

无条件锁

open func lock() { 
    let _ = lock(before: Date.distantFuture) 
} 

open func lock(before limit: Date) -> Bool { 
    _cond.lock() 
    while _thread != nil { 
        if !_cond.wait(until: limit) { 
            _cond.unlock() 
            return false 
        } 
    } 

#if os(Windows) 
    _thread = GetCurrentThread() 
#else 
    _thread = pthread_self() 
#endif 
    _cond.unlock()
    return true 
}
  • 调用lock方法,底层调用的是lock(before limit: Date)方法,无条件判断,可直接执行。

释放无条件锁

open func unlock() { 
    _cond.lock() 
#if os(Windows) 
    _thread = INVALID_HANDLE_VALUE 
#else
    _thread = nil 
#endif 
    _cond.broadcast() 
    _cond.unlock() 
}
  • 调用unlock方法,实际上处理的是NSCondition

条件锁

open func lock(whenCondition condition: Int) {
    let _ = lock(whenCondition: condition, before: Date.distantFuture)
}

open func lock(whenCondition condition: Int, before limit: Date) -> Bool {
    _cond.lock() 
    while _thread != nil || _value != condition { 
        if !_cond.wait(until: limit) { 
            _cond.unlock() 
            return false 
        } 
    }
    
#if os(Windows) 
    _thread = GetCurrentThread() 
#else 
    _thread = pthread_self() 
#endif 
    _cond.unlock()
    return true 
}
  • 内部进行lockunlock处理,保证线程安全;

  • 如果条件不一致,调用wait方法进入等待状态;

  • 否则,调用pthread_self方法。

释放条件锁

open func unlock(withCondition condition: Int) {
    _cond.lock() 
#if os(Windows) 
    _thread = INVALID_HANDLE_VALUE
#else 
    _thread = nil
#endif 
    _value = condition 
    _cond.broadcast() 
    _cond.unlock() 
}
  • 内部进行lockunlock处理,保证线程安全;

  • 更新条件value

  • 调用broadcast方法,进行广播,唤醒在锁中等待的所有线程。

汇编分析

initWithCondition

设置-[NSConditionLock initWithCondition:]符号断点,真机运行项目:

image.png

  • 面对复杂的汇编代码,我们可以对所有的跳转代码设置断点,例如:blb等指令,然后读取x0x1寄存器查看消息接收者和方法编号,遇到ret指令,通过x0查看返回值。

执行bl 0x188fa9e34指令:

(lldb) register read x0 
        x0 = 0x000000016f7b99c8 
        
(lldb) register read x1 
        x1 = 0x00000001d0748898 

(lldb) po 0x000000016f7b99c8 
6165338568 
        
(lldb) po (SEL)0x00000001d0748898
"init"
  • 调用一个未知对象的init方法。

执行bl 0x1893db4fc指令:

(lldb) register read x0 
        x0 = 0x0000000283e72670 
        
(lldb) register read x1 
        x1 = 0x00000001d0748898 
        
(lldb) po 0x0000000283e72670 
<NSConditionLock: 0x283e72670>{condition = 0, name = nil} 

(lldb) po (SEL)0x00000001d0748898 
"init"
  • 调用NSConditionLock对象的init方法。

执行bl 0x188fa9e28指令:

(lldb) register read x0 
        x0 = 0x0000000283e72670 
        
(lldb) register read x1
        x1 = 0x00000001cffedc93 
     
(lldb) po 0x0000000283e72670 
<NSConditionLock: 0x283e72670>{condition = 0, name = nil} 

(lldb) po (SEL)0x00000001cffedc93 
"zone"
  • 调用NSConditionLock对象的zone方法,开辟内存空间;

执行bl 0x188fa9e28指令:

(lldb) register read x0 
        x0 = 0x00000001de1d0e20 (void *)0x00000001de1d0e48: NSCondition 
        
(lldb) register read x1
        x1 = 0x00000001d0c3d418
      
(lldb) po (SEL)0x00000001d0c3d418 
"allocWithZone:" 

(lldb) register read x2 
        x2 = 0x00000001ded98000 libsystem_malloc.dylib`virtual_default_zone
  • 调用NSCondition类对象的allocWithZone:方法;
  • 传入参数:virtual_default_zone

执行bl 0x188fa9e28指令:

(lldb) register read x0 
        x0 = 0x0000000280220510 
        
(lldb) register read x1 
        x1 = 0x00000001d0748898 
        
(lldb) po 0x0000000280220510 
<NSCondition: 0x280220510>{name = nil} 

(lldb) po (SEL)0x00000001d0748898
"init"
  • 调用NSCondition对象的init方法。

image.png

  • 比较x8x9寄存器;
  • b.ne:比较结果是不等于(not equal to),执行标号处指令,否则继续执行。

输出x8x9寄存器:

(lldb) register read x9 
        x9 = 0x933ba4ad46660065 
        
(lldb) register read x8
        x8 = 0x933ba4ad46660065
  • 相等,继续执行。

执行ret指令,输出x0寄存器,查看返回值

(lldb) register read x0 
        x0 = 0x0000000283e72670 
        
(lldb) po 0x0000000283e72670 
<NSConditionLock: 0x283e72670>{condition = 2, name = nil
}
  • 返回NSConditionLock实例对象。

使用x/8g命令,查看实例对象的内存结构:

(lldb) x/8g 0x283e72670 
0x283e72670: 0x000021a1de1d0ec1 0x0000000000000000 
0x283e72680: 0x0000000280220510 0x0000000000000000 
0x283e72690: 0x0000000000000002 0x0000000000000000 
0x283e726a0: 0x0000a817e1de26a0 0x00000000006000cc

(lldb) po 0x0000000280220510
<NSCondition: 0x280220510>{name = nil
}
  • NSCondition和初始化设置的默认条件,都作为成员变量保存在NSConditionLock的实例对象中;
  • 设置的默认条件:0x0000000000000002,即:初始化时传入的2

lockWhenCondition:

设置-[NSConditionLock lockWhenCondition:]符号断点:

image.png

输出x0x1x2寄存器:

(lldb) register read x0
    x0 = 0x0000000283fcbb40 

(lldb) register read x1
    x1 = 0x00000001d0543629 
    
(lldb) register read x2 
    x2 = 0x0000000000000001 
    
(lldb) po 0x0000000283fcbb40 
<NSConditionLock: 0x283fcbb40>{condition = 2, name = nil} 

(lldb) po (SEL)0x00000001d0543629 
"lockWhenCondition:"
  • 执行线程1的代码;
  • x2:传入的条件值为1

执行bl 0x188fa9e28指令:

(lldb) register read x0 
    x0 = 0x00000001de1c7030 (void *)0x00000001de1c7058: NSDate 
    
(lldb) register read x1 
    x1 = 0x00000001d0f8b6aa
     
(lldb) po (SEL)0x00000001d0f8b6aa 
"distantFuture"
  • 调用NSDatedistantFuture方法。

执行b 0x188fa9e28指令:

(lldb) register read x0 
    x0 = 0x00000002813fc360 
    
(lldb) register read x1 
    x1 = 0x00000001d070122e 
    
(lldb) po 0x00000002813fc360
<NSConditionLock: 0x2813fc360>{condition = 2, name = nil} 

(lldb) po (SEL)0x00000001d070122e
"lockWhenCondition:beforeDate:" 

(lldb) register read x2 
    x2 = 0x0000000000000001 
    
(lldb) register read x3 
    x3 = 0x00000001d64d7168 CoreFoundation`_NSConstantDateDistantFuture
  • 调用NSConditionLock对象的lockWhenCondition:beforeDate:方法;

  • x2:传入的条件值1

  • x3:上一个bl,调用distantFuture方法得到的返回值。

lockWhenCondition: beforeDate:

设置-[NSConditionLock lockWhenCondition:beforeDate:]符号断点:

image.png

执行bl 0x188fa9e28指令:

(lldb) register read x0 
    x0 = 0x0000000280220510 
    
(lldb) register read x1 
    x1 = 0x00000001d0195618 
    
(lldb) po 0x0000000280220510 
<NSCondition: 0x280220510>{name = nil} 

(lldb) po (SEL)0x00000001d0195618
"lock"
  • 调用NSCondition对象的lock方法。

image.png

  • 比较x8x21寄存器;
  • b.eq:比较结果是等于(equal to),执行标号处指令,否则继续执行。

输出x8x21寄存器:

(lldb) register read x8 
    x8 = 0x0000000000000002 
    
(lldb) register read x21
    x21 = 0x0000000000000001
  • 不相等,继续执行。

执行bl 0x188fa9e28指令:

(lldb) register read x0 
    x0 = 0x0000000281ad5050
     
 (lldb) register read x1
     x1 = 0x00000001d099aac3
     
 (lldb) po 0x0000000281ad5050
 <NSCondition: 0x281ad5050>{name = nil}
 
 (lldb) po (SEL)0x00000001d099aac3 
 "waitUntilDate:"
  • 调用NSCondition对象的waitUntilDate:方法;
  • 线程进入等待状态。

程序继续执行,此时会切换线程,跳转到线程2lockWhenCondition:方法:

image.png

  • x2:传入的条件值为2

程序继续执行,进入线程2lockWhenCondition:beforeDate:方法:

(lldb) register read x0 
    x0 = 0x0000000283e72670
    
(lldb) register read x1 
    x1 = 0x00000001d070122e 
    
(lldb) po 0x0000000283e72670 
<NSConditionLock: 0x283e72670>{condition = 2, name = nil} 

(lldb) po (SEL)0x00000001d070122e 
"lockWhenCondition:beforeDate:" 

(lldb) register read x2
    x2 = 0x0000000000000002 
    
(lldb) register read x3
    x3 = 0x00000001d64d7168 CoreFoundation`_NSConstantDateDistantFuture
  • 和之前调用线程1的区别,传入的条件值为2

执行bl 0x188fa9e28指令:

(lldb) register read x0 
    x0 = 0x0000000280220510 
    
(lldb) register read x1 
    x1 = 0x00000001d0195618 
    
(lldb) po 0x0000000280220510 
<NSCondition: 0x280220510>{name = nil} 

(lldb) po (SEL)0x00000001d0195618
"lock"
  • 调用NSCondition对象的lock方法。

image.png

  • 比较x8x21寄存器;
  • b.eq:比较结果是等于(equal to),执行标号处指令,否则继续执行。

输出x8x21寄存器:

(lldb) register read x8 
    x8 = 0x0000000000000002 
    
(lldb) register read x21
    x21 = 0x0000000000000002
  • 相等,执行标号处指令:

执行bl 0x188faa140指令:

(lldb) register read x0 
    x1 = 0x0000000000000001
  • 只能看出x0寄存器的值为1,具体执行的代码未知。

执行bl 0x188fa9e28指令:

(lldb) register read x0 
    x0 = 0x0000000280220510 
    
(lldb) register read x1
    x1 = 0x00000001d03bca18 
    
(lldb) po 0x0000000280220510 
<NSCondition: 0x280220510>{name = nil}

(lldb) po (SEL)0x00000001d03bca18 
"unlock"
  • 调用NSCondition对象的unlock方法。

执行ret指令,输出x0寄存器,查看返回值:

(lldb) register read x0 
    x0 = 0x0000000000000001
  • 返回值为1,表示条件符合,代码块执行完成。

如果条件不符合,线程进入等待状态,因为超时导致代码块未执行,会进行解锁,然后返回0

image.png

unlockWithCondition:

设置-[NSConditionLock unlockWithCondition:]符号断点:

image.png

进入线程2unlockWithCondition:方法:

(lldb) register read x2 
    x2 = 0x0000000000000001
  • 传入的条件值为1

执行bl 0x1893db4fc指令:

(lldb) register read x0
    x0 = 0x0000000282688330 
    
(lldb) register read x1
    x1 = 0x00000001d025a602 
    
(lldb) po 0x0000000282688330 
<NSConditionLock: 0x282688330>{condition = 2, name = nil} 

(lldb) po (SEL)0x00000001d025a602
"unlockWithCondition:"
  • 调用NSConditionLock对象的unlockWithCondition:方法。

执行bl 0x188fa9e28指令:

(lldb) register read x0 
    x0 = 0x0000000281ad5050 
    
(lldb) register read x1 
    x1 = 0x00000001d0195618 
    
(lldb) po 0x0000000281ad5050
<NSCondition: 0x281ad5050>{name = nil} 

(lldb) po (SEL)0x00000001d0195618
"lock"
  • 调用NSCondition对象的lock方法。

执行bl 0x188fa9e28指令:

(lldb) register read x0 
    x0 = 0x0000000281ad5050 
    
(lldb) register read x1 
    x1 = 0x00000001d004280d 
    
(lldb) po 0x0000000281ad5050
<NSCondition: 0x281ad5050>{name = nil} 

(lldb) po (SEL)0x00000001d004280d 
"broadcast"
  • 调用NSCondition对象的broadcast方法,进行广播,唤醒在锁中等待的所有线程。

执行b 0x188fa9e28指令:

(lldb) register read x0 
     x0 = 0x0000000281ad5050 
     
(lldb) register read x1 
     x1 = 0x00000001d03bca18 
     
(lldb) po 0x0000000281ad5050 
<NSCondition: 0x281ad5050>{name = nil}

(lldb) po (SEL)0x00000001d03bca18
"unlock"
  • 调用NSCondition对象的unlock方法。

使用b指令,表示义无反顾的跳转,不再返回。此方法执行完,线程2的代码执行结束。

但是,在线程2中,调用NSCondition对象的broadcast方法,触发广播操作。此时,处于等待状态的线程1将被唤醒,继续线程1的代码执行。

唤醒线程1

程序继续执行,此时会切换线程,跳转到线程1lockWhenCondition:方法

此时线程1解除等待状态,继续执行代码:

image.png

  • 比较x8x21寄存器;
  • b.eq:比较结果是等于(equal to),执行标号处指令,否则继续执行。

输出x8x21寄存器:

(lldb) register read x8 
    x8 = 0x0000000000000001
    
(lldb) register read x21 
    x21 = 0x0000000000000001
  • 相等,执行标号处指令。

执行bl 0x188faa140指令:

(lldb) register read x0 
    x1 = 0x0000000000000001
  • 只能看出x0寄存器的值为1,具体执行的代码未知。

执行bl 0x188fa9e28指令:

(lldb) register read x0 
    x0 = 0x0000000280220510 
    
(lldb) register read x1 
    x1 = 0x00000001d03bca18
    
(lldb) po 0x0000000280220510
<NSCondition: 0x280220510>{name = nil} 

(lldb) po (SEL)0x00000001d03bca18 
"unlock"
  • 调用NSCondition对象的unlock方法。

执行ret指令,输出x0寄存器,查看返回值:

(lldb) register read x0 
    x0 = 0x0000000000000001
  • 返回值为1

之后会进入线程1unlockWithCondition:方法,释放条件锁。

自旋锁

atomic

atomic是属性的修饰,不是锁。

它在底层自带一把自旋锁,只是能保证数据的完整性,单一线程安全,不能保证多线程安全。效率低,不推荐使用。

案例演示:

2FCF6037-6468-40B0-99C9-EED31879E489.png

线程A中,循环遍历的索引为偶数,数组中的元素一定存在三个;

线程B中,如果数组中的元素>= 2,获取第一个元素;

如果这两个逻辑在同一线程中执行,一定不会报错。但由于多线程,atomic无法保证多线程安全,所以程序还是会崩溃。

image.png

OSSpinLock

OSSpinLock是一把自旋锁,由于安全性问题,iOS10之后被os_unfair_lock替代。使用OSSpinLock,会让线程处于忙等待状态。目前OSSpinLock还可以使用,但不推荐。

案例演示:

#import "ViewController.h"
#import <libkern/OSAtomic.h>
#import <os/lock.h>

@interface ViewController (){
    OSSpinLock _spinLock;
}

@implementation ViewController 
- (void)viewDidLoad {
    [super viewDidLoad]; 
    
    [self lg_testOSSPinLock]; 
}

#pragma mark -- OSSpinLock - (void)lg_testOSSPinLock { 

    _spinLock = OS_SPINLOCK_INIT; 
    
    // 线程1 
    dispatch_async(dispatch_get_global_queue(0, 0), ^{ 
        NSLog(@"线程1 准备上锁,currentThread:%@",[NSThread currentThread]);
        OSSpinLockLock(&_spinLock); 
        NSLog(@"线程1"); 
        sleep(3); 
        OSSpinLockUnlock(&_spinLock); 
        NSLog(@"线程1 解锁完成"); 
    });
    
    // 线程2 
    dispatch_async(dispatch_get_global_queue(0, 0), ^{ 
        NSLog(@"线程2 准备上锁,currentThread:%@",[NSThread currentThread]); 
        OSSpinLockLock(&_spinLock); 
        NSLog(@"线程2"); 
        sleep(3); 
        OSSpinLockUnlock(&_spinLock);
        NSLog(@"线程2 解锁完成"); 
    });
}

@end

os_unfair_lock

os_unfair_lock是一把自旋锁,使用os_unfair_lock,会让线程处于休眠状态.

查看 api

//导入头文件 
#import <libkern/OSAtomic.h> 
#import <os/lock.h> 

//初始化 
os_unfair_lock _unfairLock = OS_UNFAIR_LOCK_INIT; 

//加锁 
os_unfair_lock_lock(&_unfairLock); 

//解锁 
os_unfair_lock_unlock(&_unfairLock);

读写锁

通常,当读写锁处于读模式锁住状态时,如果有另外线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求。这样可以避免读模式锁⻓期占⽤,⽽等待的写模式锁请求⻓期阻塞。

读写锁适合于对数据结构的读次数⽐写次数多得多的情况,因为读模式锁定时可以共享,以写模式锁住时意味着独占,所以读写锁⼜叫共享/独占锁

读写锁要实现以下几点:

  • 多读单写

  • 读和写互斥

  • 写操作之间互斥

  • 不能阻塞正常的任务执行

读写锁的两种实现方式:

  • 使用pthread实现

  • 使用GCD实现

使用pthread实现

//导入头文件 
#include <pthread.h> 

//初始化 
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr) 

//释放 
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock)
  • 成功返回0, 出错返回错误编号;
  • 同互斥量以上,在释放读写锁占⽤的内存之前,需要先通过pthread_rwlock_destroy对读写锁进⾏清理⼯作,释放由init分配的资源。
//读锁 
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); 

//写锁 
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

//释放锁 
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
  • 成功返回0, 出错返回错误编号;

  • 三个函数分别实现获取读锁,获取写锁和释放锁的操作;

  • 获取锁的两个函数是阻塞操作。

⾮阻塞的获取锁操作:

//读锁 
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock) 

//写锁 
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock)
  • 成功返回0, 出错返回错误编号。

使用GCD实现

使用GCD创建自定义并发队列:

  • 通过同步函数实行读操作

  • 通过异步栅栏函数实现写操作

将同步函数和异步栅栏函数加入同一队列,可以满足读写互斥,写操作之间的互斥。并且异步栅栏函数只会阻塞自身队列中的任务,不会影响其他任务的执行。

声明

#import "ViewController.h" 

@interface ViewController () 

@property (nonatomic,strong) NSMutableDictionary *dic; 
@property (nonatomic,strong) dispatch_queue_t queue;

@end

初始化

- (void)viewDidLoad { 
    [super viewDidLoad]; 
    
    _dic = [NSMutableDictionary dictionary];
    _queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT); 
}

读操作

- (NSString *)safrGetter:(NSString *)strKey{
    __block NSString* strValue;
    dispatch_sync(self.queue, ^{ 
        strValue = [self.dic objectForKey:strKey]; 
        NSLog(@"safrGetter:%@,%@,%@",strKey,strValue,[NSThread currentThread]); 
    }); 
    
    return strValue; 
}

写操作

- (void)safeSetter:(NSString *)strKey strValue:(NSString *)strValue {
    dispatch_barrier_async(self.queue, ^{ 
        [self.dic setObject:strValue forKey:strKey]; 
        NSLog(@"safeSetter:%@,%@,%@",strKey,strValue,[NSThread currentThread]); 
    });
}

测试代码

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 
    [self test]; 
} 

-(void)test { 
    for (int intIndex=0; intIndex<50; intIndex++) { 
        dispatch_async(dispatch_get_global_queue(0, 0), ^{ 
        
            [self safeSetter:[NSString stringWithFormat:@"key_%i",intIndex] strValue:[NSString stringWithFormat:@"value_%i",intIndex]]; 
            
            [self safrGetter:[NSString stringWithFormat:@"key_%i",intIndex]];
        }); 
    } 
}