【iOS】锁的原理

文章目录

分类

  • 自旋锁

载自旋锁中线程会反复检查变量状态,线程一直保持执行,所以处于忙等待(CPU空转)。一旦获取了自旋锁,线程就会一直保持该锁直到显示释放。自旋锁避免了进程上下文的调度开销,所以适用于线程阻塞很短时间的场合,atomic自带一把自旋锁

​ OSSpinLock

​ atomic

  • 互斥锁

用于多线程编程中,防止两条线程同时对一公共资源进行读写的机制(将代码切成一个个临界区而达成),获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒

​ pthread_mutex: C语言层面的互斥锁,较为底层,性能较好

​ NSLock:OC封装的互斥锁,使用简单

​ @synchronized:OC语法级别的锁,最方便但是性能较低

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

@interface LockViewController ()
@property (nonatomic, assign)int tickets;
@property (nonatomic, strong)NSLock* lock;
@end

@implementation LockViewController {
  pthread_mutex_t _mutex;
}

- (void)viewDidLoad {
  [super viewDidLoad];
  pthread_mutex_init(&_mutex, NULL);
  //self.lock = [[NSLock alloc] init];
  [self text];
}

- (void)dealloc {
  pthread_mutex_destroy(&_mutex);
}

- (void)text {
  int tickets = 20;
  self.tickets = tickets;
  dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  dispatch_async(queue, ^{
    for (int i = 0; i < 10; i++) {
      [self sellTicket];
    }
  });
  dispatch_async(queue, ^{
    for (NSInteger i = 0; i < 10; i++) {
      [self sellTicket];
    }
  });

  dispatch_async(queue, ^{
    for (NSInteger i = 0; i < 10; i++) {
      [self sellTicket];
    }
  });

}

- (void)sellTicket {
  //[self.lock lock];
  pthread_mutex_lock(&_mutex);
  int oldTicket = self.tickets;
  [NSThread sleepForTimeInterval:0.2];
  oldTicket -= 1;
  self.tickets = oldTicket;
  pthread_mutex_unlock(&_mutex);
  NSLog(@"还剩%ld张票 ---- %@", (long)self.tickets, [NSThread currentThread]);
  //[self.lpck unlock];
}

/*
- (void)sellTicket {
    @synchronized (self) {
        NSInteger oldTicket = self.tickets;
        [NSThread sleepForTimeInterval:0.2];
        oldTicket--;
        self.tickets = oldTicket;

        NSLog(@"还剩%ld张票 ---- %@", (long)self.tickets, [NSThread currentThread]);
    }
}
*/

@end

显示递归锁:NSRecursiveLock:

objc 复制代码
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];

[lock lock];

// 临界区

[lock unlock];
  • 条件锁

即条件变量,当线程的某一些资源要求不满足时就会进入休眠状态,即锁住了,当资源被分配到了,条件锁就打开,线程继续运行

​ NSCondition

​ NSConditionLock

objc 复制代码
@interface TestConditionLock : NSObject

@property (nonatomic, strong) NSConditionLock *conditionLock;

@end

@implementation TestConditionLock

- (instancetype)init {
    if (self = [super init]) {
        _conditionLock = [[NSConditionLock alloc] initWithCondition:1];
    }
    return self;
}

- (void)task1 {
    [self.conditionLock lockWhenCondition:1];

    NSLog(@"执行任务 1");

    [self.conditionLock unlockWithCondition:2];
}

- (void)task2 {
    [self.conditionLock lockWhenCondition:2];

    NSLog(@"执行任务 2");

    [self.conditionLock unlock];
}

@end
//调用
TestConditionLock *test = [[TestConditionLock alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [test task2];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    sleep(2);
    [test task1];
});//初始状态condition为1,task2卡住,只有当task1执行结束task2才能执行
 
 //_________________

#import "TestCondition.h"

@interface TestCondition()
@property (nonatomic, strong)NSCondition* condition;
@property (nonatomic, assign)bool hasData;
@end

@implementation TestCondition

- (instancetype)init {
  if (self = [super init]) {
    _condition = [[NSCondition alloc] init];
    _hasData = NO;
  }
  return self;
}

- (void)consumer {
  [self.condition lock];
  while (!self.hasData) {
    NSLog(@"没有数据,线程等待");
    [self.condition wait];//当前线程进入等待状态,临时释放condition内部的锁,被唤醒之后再重新尝试加锁。如果wait一致不释放锁,生产者就拿不到锁,也就无法修改条件唤醒消费者
  }
  NSLog(@"有数据了,开始消费");
  [self.condition unlock];
}

- (void)producer {
    [self.condition lock];
    self.hasData = YES;
    NSLog(@"生产了数据,通知消费者");
    [self.condition signal];
    [self.condition unlock];
}

@end

 	TestCondition* condition = [[TestCondition alloc] init];
  dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [condition consumer];
  });
  dispatch_async(dispatch_get_global_queue(0, 0), ^{
    sleep(2);
    [condition producer];
  });
  • 递归锁

同一个线程加锁N次而不会发生死锁,递归锁是一种特殊的互斥锁,即带有递归性质的互斥锁

​ pthread_mutex(recursive):默认是普通的互斥锁,需要设置相关属性才能变成递归锁

objc 复制代码
pthread_mutex_t mutex;
pthread_mutexattr_t attr;

pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);

pthread_mutex_init(&mutex, &attr);
pthread_mutexattr_destroy(&attr);
//使用:
pthread_mutex_t mutex;
pthread_mutexattr_t attr;

pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);

pthread_mutex_init(&mutex, &attr);
pthread_mutexattr_destroy(&attr);

​ NSRecursiveLock:加几次锁就需要解几次锁

objc 复制代码
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];

[lock lock];
[lock lock];

NSLog(@"不会死锁");

[lock unlock];
[lock unlock];
  • 信号量

就是为互斥锁提供了更多的取值空间,可以实现更加复杂的同步。

​ dispatch_semphore

objc 复制代码
dispatch_semaphore_create(NSInteger value);//创建信号量,设置初始值
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);//等待信号量,大于0时才能继续
dispatch_semaphore_signal(dispatch_semaphore_t dsema);//发送信号,释放一个信号量
  1. wait 的时候,如果信号量 > 0,可以继续执行,并且信号量 -1
  2. 如果信号量 == 0,当前线程等待
  3. signal 的时候,信号量 +1
  4. 如果有线程正在等,就唤醒一个线程
  • 读写锁

是一种特殊的自旋锁,将对共享资源的访问分成读者和写者,读者只能对资源进行读访问,写者则需要对共享资源进行写操作,相对于自旋锁而言,可以提高并发性。

  1. 一个读写锁同时只能有一个写者或者多个读者,但不能既有读者又有写者,在读写锁保持期间也是抢占失效的

  2. 如果读写锁当前没有读者,也没有写者,那么写者可以立刻获得读写锁,否则它必须自旋在那里, 直到没有任何写者或读者。如果读写锁没有写者,那么读者可以立即执行读操作

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

@interface SafeCache ()

@property (nonatomic, strong) NSMutableDictionary *cache;

@end

@implementation SafeCache {
    pthread_rwlock_t _rwlock;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        _cache = [NSMutableDictionary dictionary];
        pthread_rwlock_init(&_rwlock, NULL);
    }
    return self;
}

- (id)objectForKey:(NSString *)key {
    pthread_rwlock_rdlock(&_rwlock); // 加读锁
    
    id value = [self.cache objectForKey:key];
    
    pthread_rwlock_unlock(&_rwlock); // 解锁
    return value;
}

- (void)setObject:(id)obj forKey:(NSString *)key {
    pthread_rwlock_wrlock(&_rwlock); // 加写锁
    
    [self.cache setObject:obj forKey:key];
    
    pthread_rwlock_unlock(&_rwlock); // 解锁
}

- (void)dealloc {
    pthread_rwlock_destroy(&_rwlock);
}

@end

详解

自旋锁

OSSpinLock:

不安全,线程获取锁之后会一直处于忙等待状态,可能造成任务的优先级反转,看一个示例如下:

  1. 低优先级线程 A 拿到了锁
  2. A 还没执行完,时间片到了,被系统暂停
  3. 高优先级线程 B 开始执行
  4. B 想拿锁,但是锁在 A 手里
  5. B 开始自旋等待
  6. 因为 B 优先级高,系统一直优先调度 B
  7. A 优先级低,迟迟得不到 CPU 执行
  8. A 得不到执行,就无法释放锁
  9. B 拿不到锁,就继续自旋

在OSSpinLock被弃用之后,替代方案是内部封装os_unfair_lock,这个在加锁时会处于休眠状态。

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

@implementation TestSpinLock {
  OSSpinLock _lock;
}

- (instancetype)init {
  if (self = [super init]) {
    _lock = OS_SPINLOCK_INIT;
  }
  return self;
}

- (void)lowTask {
  OSSpinLockLock(&_lock);
  NSLog(@"低优先级任务执行开始");
  NSLog(@"低优先级任务执行结束");
  OSSpinLockUnlock(&_lock);
}

- (void)highTask {
  OSSpinLockLock(&_lock);
  NSLog(@"高优先级任务执行开始");
  NSLog(@"高优先级任务执行结束");
  OSSpinLockUnlock(&_lock);
}

@end

  TestSpinLock* spin = [[TestSpinLock alloc] init];
  NSThread* low = [[NSThread alloc] initWithTarget:spin selector:@selector(lowTask) object:nil];
  [low start];
  NSThread* high = [[NSThread alloc] initWithTarget:spin selector:@selector(highTask) object:nil];
 high.threadPriority = 1.0;
  [high start];

//A 持有锁,但没机会运行.B 等待锁,但一直占着 CPU

OS_unfair_lock:

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

@implementation TestSpinLock {
  os_unfair_lock _lock;
}

- (instancetype)init {
  if (self = [super init]) {
    _lock = OS_UNFAIR_LOCK_INIT;
  }
  return self;
}

- (void)lowTask {
  os_unfair_lock_lock(&_lock);
  NSLog(@"低优先级任务执行开始");
  NSLog(@"低优先级任务执行结束");
  os_unfair_lock_unlock(&_lock);
}

- (void)highTask {
  os_unfair_lock_lock(&_lock);
  NSLog(@"高优先级任务执行开始");
  NSLog(@"高优先级任务执行结束");
  os_unfair_lock_unlock(&_lock);
}

@end

可以发现解决了优先级反转的问题。

原子锁

atomic适用于OC中属性的修饰符,其自带一把自旋锁,但是这个一般基本不使用,都是使用的nonatomic.

首先就是一个setter方法,之前在介绍对象的底层探究有讲到过,是用了一个适配器设计模式,底层都是调用了一个reallySetProperty方法。具体实现这里就不赘述了。

现在的atomic原子锁底层是通过os_unfair_lock替代了OSSpinLock实现的加锁。同时为了防止哈希冲突,还是用了加盐操作。(就是添加了一个变量来区分锁,减少哈希冲突的概率)

互斥锁

synchronized(互斥递归锁)

底层是走objc_sync_enterobjc_sync_exit方法实现的。

下面我们详细分析一下这两个方法:

objc_sync_enter

objc 复制代码
int objc_sync_enter(id obj)
{
    int result = OBJC_SYNC_SUCCESS;

    if (obj) {//传入不为nil
        SyncData* data = id2data(obj, ACQUIRE);//重点
        ASSERT(data);
        data->mutex.lock();//加锁
    } else {//传入nil
        // @synchronized(nil) does nothing
        if (DebugNilSync) {
            _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
        }
        objc_sync_nil();
    }
    return result;
}

如果传入的obj存在,则通过id2data方法获取相应的syncData,然后对threadCount和lockcount执行递增操作

如果传入的obj不存在,调用objc_sync_nil,方法直接return

objc_exit_exit

objc 复制代码
int objc_sync_exit(id obj)
{
    int result = OBJC_SYNC_SUCCESS;
    
    if (obj) {//obj不为nil
        SyncData* data = id2data(obj, RELEASE); 
        if (!data) {
            result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
        } else {
            bool okay = data->mutex.tryUnlock();//解锁
            if (!okay) {
                result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
            }
        }
    } else {//obj为nil时,什么也不做
        // @synchronized(nil) does nothing
    }
    return result;
}

如果obj存在,调用id2Data方法获取对象对应的syncData,然后对threadCount和lockcount执行递减操作

如果obj为nil,啥也不干

那么什么是syncData呢,下面我们了解一下:

首先我们看一下其内部构造:

objc 复制代码
typedef struct alignas(CacheLineSize) SyncData {
    struct SyncData* nextData;//类似链表结构
    DisguisedPtr<objc_object> object;
    int32_t threadCount;  //有多少个线程使用了这个锁
    recursive_mutex_t mutex;//锁
} SyncData;

这是一个结构体,有next属性,是一个链表形式的结构,内部封装了recursive_mutex_t属性。存在于由runtime维护的一个全局结构中。

啥是syncCache呢

objc 复制代码
typedef struct {
    SyncData *data;
    unsigned int lockCount;  // number of times THIS THREAD locked this block
} SyncCacheItem;

typedef struct SyncCache {
    unsigned int allocated;
    unsigned int used;
    SyncCacheItem list[0];//柔性数组
} SyncCache;

同样是一个结构体,主要是用来存储线程的本地缓存数据

id2data分析

objc 复制代码
static SyncData* id2data(id object, enum usage why)
{
    spinlock_t *lockp = &LOCK_FOR_OBJ(object);
    SyncData **listp = &LIST_FOR_OBJ(object);
    SyncData* result = NULL;

#if SUPPORT_DIRECT_THREAD_KEYS
    // 1. 先查线程快速缓存
    bool fastCacheOccupied = NO;
    SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);

    if (data) {
        fastCacheOccupied = YES;

        if (data->object == object) {
            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:
                // 当前线程重复加锁,增加重入次数
                lockCount++;
                tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
                break;

            case RELEASE:
                // 当前线程释放锁,减少重入次数
                lockCount--;
                tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);

                if (lockCount == 0) {
                    // 重入次数为 0,移出快速缓存
                    tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
                    OSAtomicDecrement32Barrier(&result->threadCount);
                }
                break;

            case CHECK:
                break;
            }

            return result;
        }
    }
#endif

    // 2. 再查当前线程的 SyncCache
    SyncCache *cache = fetch_cache(NO);

    if (cache) {
        unsigned int i;

        for (i = 0; i < cache->used; i++) {
            SyncCacheItem *item = &cache->list[i];

            if (item->data->object != object) continue;

            result = item->data;

            if (result->threadCount <= 0 || item->lockCount <= 0) {
                _objc_fatal("id2data cache is buggy");
            }

            switch (why) {
            case ACQUIRE:
                // 同一线程再次进入,增加重入次数
                item->lockCount++;
                break;

            case RELEASE:
                // 退出同步块,减少重入次数
                item->lockCount--;

                if (item->lockCount == 0) {
                    // 当前线程完全释放该锁,从缓存中移除
                    cache->list[i] = cache->list[--cache->used];
                    OSAtomicDecrement32Barrier(&result->threadCount);
                }
                break;

            case CHECK:
                break;
            }

            return result;
        }
    }

    // 3. 线程缓存没找到,进入全局 SyncData 链表查找
    lockp->lock();

    {
        SyncData* p;
        SyncData* firstUnused = NULL;

        for (p = *listp; p != NULL; p = p->nextData) {
            if (p->object == object) {
                // 找到对象对应的 SyncData
                result = p;
                OSAtomicIncrement32Barrier(&result->threadCount);
                goto done;
            }

            if ((firstUnused == NULL) && (p->threadCount == 0)) {
                firstUnused = p;
            }
        }

        // RELEASE 或 CHECK 找不到时,直接结束
        if ((why == RELEASE) || (why == CHECK)) {
            goto done;
        }

        // 复用空闲的 SyncData
        if (firstUnused != NULL) {
            result = firstUnused;
            result->object = (objc_object *)object;
            result->threadCount = 1;
            goto done;
        }
    }

    // 4. 没有可复用的 SyncData,则新建一个
    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:
    lockp->unlock();

    if (result) {
        if (why == RELEASE) {
            return nil;
        }

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

        if (result->object != object) {
            _objc_fatal("id2data is buggy");
        }

#if SUPPORT_DIRECT_THREAD_KEYS
        if (!fastCacheOccupied) {
            // 保存到线程快速缓存
            tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
            tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
        } else
#endif
        {
            // 保存到当前线程的 SyncCache
            if (!cache) cache = fetch_cache(YES);

            cache->list[cache->used].data = result;
            cache->list[cache->used].lockCount = 1;
            cache->used++;
        }
    }

    return result;
}
对比项 TLS SyncCache
本质 线程本地快速缓存 线程本地普通缓存
存储数量 只存一组 SyncData + lockCount 可存多组 SyncData + lockCount
查找方式 直接通过 key 取 遍历 cache->list
速度 最快 较快
适合场景 同一线程反复锁同一个对象 同一线程持有多个对象锁
lockCount 位置 TLS 中单独保存 SyncCacheItem.lockCount
是否线程私有
是否访问全局表 不访问 不访问
源码位置 tls_get_direct(...) fetch_cache(...)
作用 快速判断当前线程是否已经锁过该对象 记录当前线程已经持有的多个锁

我们使用自然语言概述一下流程:

  • 首先在tls(线程局部存储)中查询(tls存储线程最近一次使用的锁对象以及嵌套锁计数)
    • 通过tls_get_direct获取线程绑定的syncData,检查data->object是否与传入的对象匹配。如果匹配执行相应的加锁或者解锁操作,若归零则移除 TLS 缓存并原子递减 threadCount。如果出现threadCount <= 0或者lockCount <= 0,触发崩溃。
    • 反之进入下一步查询
  • 查询syncCache线程存储(线程存储已经持有的多个锁对象,支持多锁的交替使用)
    • 通过 fetch_cache(NO) 获取线程缓存池,遍历 SyncCacheItem 数组,检查item->data->object是否匹配,然后执行对应的加锁解锁操作,若归零则从数组中移除并原子递减 threadCount。同样如果出现threadCount <= 0或者lockCount <= 0,触发崩溃。
    • 反之进入下一步查询
  • 查询全局链表与创建syncData节点(找到活着创建之后还要放入当前线程的缓存中去,tls和syncCache二选一)
    • 如果全局链表里找到了 object 对应的 SyncData,说明这个 object 以前已经被创建过锁数据了。可能是其他线程正在用,也可能是之前用过还没完全释放。这时不会重新创建 SyncData,而是直接复用已有的节点。
    • 如果全局链表中没有找到,但是有空闲的syncData节点,可以进行复用。
    • 如果局链表里完全没有可用的 SyncData,那就真正创建一个新的 SyncData
场景 操作
TLS 找到 当前线程重入,lockCount++
SyncCache 找到 当前线程重入,lockCount++
全局链表找到 复用已有 SyncDatathreadCount++
全局链表没找到,但有空闲 SyncData 复用空闲节点,threadCount = 1
全局链表完全没有 创建新的 SyncDatathreadCount = 1

@synchronized坑点

我们看一下下面这段代码:

objc 复制代码
 - (void)cjl_testSync{
    _testArray = [NSMutableArray array];
    for (int i = 0; i < 200000; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            @synchronized (self.testArray) {
                self.testArray = [NSMutableArray array];
            }
        });
    }
}

运行这段代码可以发现报错了,这是为啥嘞?

  • 首先就是因为我们锁对象在循环中一直被修改释放,不同线程可能使用的是不同的锁对象,无法实现真正的互斥
  • 其次就是@synchronized底层通过全局哈希表管理锁数据,如果锁对象频繁的变化,会触发大量的syncData节点的创建与销毁,导致内存管理异常或者链表断裂。

所以我们在选择锁对象的时候,使用一个独立且稳定的锁对象作为锁,可以有效的避免锁对象动态变化带来的负面效果。

同时我们也可以控制线程的并发量:改用串行队列或者限制并发数量(NSOperationQueue.maxConcurrentOperationCount),避免资源耗尽

NSLock

对于嵌套次数过多的情况,我们可以使用NSLock避免@synchronized带来的性能消耗问题。底层是对pthread_mutex的封装,是一个非递归互斥锁,如果发生递归调用,线程会死锁(表现为阻塞)

在同一线程上调用NSLock的两次lock方法将永久锁定线程

pthread_mutex

是NSLock的子类,底层也是对pthread_mutex的封装。当锁被占用时,其他线程申请锁时,不会一直忙于等待,而是阻塞线程并睡眠。具体使用方法前面有涉及过。

条件锁

NSCondition

objc 复制代码
NSCondition *condition = [[NSCondition alloc] init]
[condition lock];
[condition unlock];
//让当前线程处于等待状态
[condition wait];
//CPU发信号告诉线程不用在等待,可以继续执行
[condition signal];

NSCondition 是一个条件锁,在日常开发中使用较少,与信号量有点相似:线程1需要满足条件1才会往下走,否则会堵塞等待,知道条件满足。经典模型是生产消费者模型

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

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

我们看一下其Swift源码:

objc 复制代码
open class NSCondition: NSObject, NSLocking {
    internal var mutex = _MutexPointer.allocate(capacity: 1)
    internal var cond = _ConditionVariablePointer.allocate(capacity: 1)
    //初始化
    public override init() {
        pthread_mutex_init(mutex, nil)
        pthread_cond_init(cond, nil)
    }
    //析构
    deinit {
        pthread_mutex_destroy(mutex)
        pthread_cond_destroy(cond)

        mutex.deinitialize(count: 1)
        cond.deinitialize(count: 1)
        mutex.deallocate()
        cond.deallocate()
    }
    //加锁
    open func lock() {
        pthread_mutex_lock(mutex)
    }
    //解锁
    open func unlock() {
        pthread_mutex_unlock(mutex)
    }
    //等待
    open func wait() {
        pthread_cond_wait(cond, mutex)
    }
    //等待
    open func wait(until limit: Date) -> Bool {
        guard var timeout = timeSpecFrom(date: limit) else {
            return false
        }
        return pthread_cond_timedwait(cond, mutex, &timeout) == 0
    }
    //信号,表示等待的可以执行了
    open func signal() {
        pthread_cond_signal(cond)
    }
    //广播
    open func broadcast() {
        // 汇编分析 - 猜 (多看多玩)
        pthread_cond_broadcast(cond) // wait  signal
    }
    open var name: String?
}

可以发现,NSCondition是对pthread_mutex的封装,其中:

  • cond就是用于访问和操作特定类型数据的指针
  • wait操作会阻塞线程,使其进入休眠状态,直至超时
  • signal操作是唤醒一个正在休眠等待的线程
  • broadcast会唤醒所有正在等待的线程

具体的使用示例前面有写过。

NSConditionLock

条件锁,一旦一个线程获得锁,其他线程一定等待。

objc 复制代码
// 初始化条件锁,设置内部 condition 初始值为 2
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
// 普通加锁:
// 只判断这把锁有没有被其他线程占用
// 不判断内部 condition 的值
[conditionLock lock];
// 条件加锁:
// 只有同时满足下面两个条件,才能继续执行:
// 1. 当前锁没有被其他线程占用
// 2. 内部 condition == A条件
[conditionLock lockWhenCondition:A条件];
// 普通解锁:
// 只释放锁
// 不修改内部 condition 的值
[conditionLock unlock];
// 条件解锁:
// 释放锁
// 同时把内部 condition 修改为 A条件
[conditionLock unlockWithCondition:A条件];
// 带超时的条件加锁:
// 在 A时间 之前等待 condition == A条件
// 如果条件满足并成功拿到锁,返回 YES
// 如果超时还没拿到锁,返回 NO
BOOL result = [conditionLock lockWhenCondition:A条件 beforeDate:A时间];
if (result) {
    // 成功拿到锁,可以执行临界区代码
    [conditionLock unlock];
} else {
    // 没有拿到锁,不能调用 unlock
}

使用场景

如果只是简单的使用,比如设计一个线程安全问题,使用NSLock即可

如果是循环嵌套,建议使用递归锁,性能好

如果在循环嵌套中,还有多线程影响时,(如等待、死锁问题)建议使用@synchronized

相关推荐
秋雨梧桐叶落莳2 小时前
iOS——MVC架构学习
学习·ui·ios·架构·mvc·objective-c
代码的小搬运工18 小时前
UITableView
开发语言·ui·ios·objective-c
互联网行业信息差19 小时前
iOS开发常见问题与最新工具使用心得
macos·ios·cocoa
MonkeyKing20 小时前
iOS Tagged Pointer 原理、判断方式、适用场景与避坑指南
ios
wuxianda103021 小时前
Object-C/Swift/UniApp项目苹果商店上架3天极速解决方案汇报总结
ios·uni-app·objective-c·cocoa·苹果上架
鹤卿12321 小时前
UI----多界面传值
ui·ios
UnicornDev1 天前
从零开始学iOS开发(第四十七篇):Core Haptics 触感反馈 —— 让应用拥有真实的触觉体验
ios
EasyControl移动设备管理1 天前
iOS设备“零接触部署”指南
物联网·ios·设备管理·mdm·移动设备管理·abm·ade
Digitally1 天前
如何在 Mac/MacBook 上删除 iPhone 照片?
macos·ios·iphone