lock - ShenYj/ShenYj.github.io GitHub Wiki

iOS 中的锁

.

简介

锁作为一种非强制的机制,被用来保证线程安全。每一个线程在访问数据或资源前,要先获取(Acquire)锁,并在访问结束之后释放(Release)锁。如果锁已经被占用,其他试图获取锁的线程会等待,知道锁重新可用

不应该将过多的操作代码放到锁里面,否则一个线程执行的时候另外一个线程一直在等待,将无法发挥多线程的作用,应该只将关键代码放入锁内

锁的分类

在 iOS 中锁的基本种类只有两种: 互斥锁自旋锁

其他的比如 条件锁递归锁信号量 都是上层封装和实现

互斥锁

互斥锁(Mutual exclusion,缩写 Mutex)防止两条线程同时对同一公共资源进行读写的机制。当获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒。

互斥锁又分为

  • 递归锁 可重入锁,同一个线程在锁释放前可再次获取锁,即可以递归调用
  • 非递归锁 不可重入,必须等锁释放后才能再次获取锁

自旋锁

线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种 忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显示释放自旋锁

自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的

互斥和自旋锁的区别

  • 互斥锁:
    在线程获取锁但没有获取到时,线程会进入休眠状态,等锁被释放时线程会被唤醒
  • 自旋锁:
    线程会一直处于等待状态(忙等待)不会进入休眠,因此效率高

iOS 中的线程同步方案

.

OSSpinLock

OSSpinLock 叫做自旋锁,等待锁的线程会处于忙等(busy-wait)状态,一直占用着 CPU资源

使用时需要导入头文件#import <libkern/OSAtomic.h>

  • 目前已经不再安全,可能出现优先级反转问题

    如果等待锁的线程优先级较高,它会一直占用着 CPU 资源,优先级低的线程就无法释放锁

    • 最早看到有关这方面描述的一篇文章: ibireme (郭曜源)的 不再安全的 OSSpinLock

      具体来说,如果一个低优先级的线程获得锁并访问共享资源,这时一个高优先级的线程也尝试获得这个锁,它会处于 spin lock 的忙等状态从而占用大量 CPU。此时低优先级线程无法与高优先级线程争夺 CPU 时间,从而导致任务迟迟完不成、无法释放 lock。这并不只是理论上的问题,libobjc 已经遇到了很多次这个问题了,于是苹果的工程师停用了 OSSpinLock。

  • api

    // 初始化
    OSSpinLock lock = OS_SPINLOCK_INIT;
    // 尝试加锁
    OSSpinLockTry(&lock);
    // 加锁
    OSSpinLockLock(&lock);
    // 解锁
    OSSpinLockUnlock(&lock);
    

os_unfair_lock

os_unfair_lock 用于取代不安全的 OSSpinLock,从iOS10开始才支持

需要导入头文件 #import <os/lock.h>

  • api

    // 初始化
    os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
    // 尝试加锁
    os_unfair_lock_trylock(&lock);
    // 加锁
    os_unfair_lock_lock(&lock);
    // 解锁
    os_unfair_lock_unlock(&lock);
    

从底层汇编调用看,等待 os_unfair_lock 锁的线程会处于休眠状态 (执行汇编 syscall 调用到内核api,与 pthread_mutex 流程一致,而不会像 OSSpinLock 进入到汇编的循环状态 ),并非忙等
苹果有关 os_unfair_lock_lock 的描述

This is a replacement for the deprecated OSSpinLock. This function doesn't spin on contention, but instead waits in the kernel to be awoken by an unlock. Like OSSpinLock, this function does not enforce fairness or lock ordering—for example, an unlocker could potentially reacquire the lock immediately, before an awoken waiter gets an opportunity to attempt to acquire the lock. This may be advantageous for performance reasons, but also makes starvation of waiters a possibility.

pthread_mutex

mutex 叫做 互斥锁,等待锁的线程会处于休眠状态。是跨平台的一个锁方案,其他很多锁都是基于 pthread_mutex 的封装

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

  • api

    
    /** 
    * 
    * Mutex type attributes
    * #define PTHREAD_MUTEX_NORMAL        0
    * #define PTHREAD_MUTEX_ERRORCHECK    1
    * #define PTHREAD_MUTEX_RECURSIVE     2  // 递归
    * #define PTHREAD_MUTEX_DEFAULT       PTHREAD_MUTEX_NORMAL
    */
    
    // 初始化属性
    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_lock(&_mutex);
    // 解锁
    pthread_mutex_unlock(&_mutex);
    
    // 销毁锁
    pthread_mutex_destroy(&_mutex);
    

可以不初始化属性,在传属性的时候直接传 NULL,表示使用默认属性 PTHREAD_MUTEX_NORMALpthread_mutex_init(mutex, NULL);

pthread_mutex,从设置属性枚举值得知,即能实现非递归锁,也能实现递归锁,同时还支持条件锁

  • 设置条件的 api

    // 初始化条件
    pthread_cond_t condition
    pthread_cond_init(&_cond, NULL);
    
    // 等待条件
    pthread_cond_wait(&_cond, &_mutex);
    
    //激活一个等待该条件的线程
    pthread_cond_signal(&_cond);
    
    //激活所有等待该条件的线程
    pthread_cond_broadcast(&_cond);
    
    // 销毁条件
    pthread_cond_destroy(&_cond);
    

NSLock

NSLock 是对 pthread_mutex 普通锁的封装。pthread_mutex_init(mutex, NULL);

NSLock 遵循 NSLocking 协议。

  • Lock 方法是加锁
  • unlock 是解锁
  • tryLock 是尝试加锁,如果失败的话返回 NO
  • lockBeforeDate: 是在指定 Date 之前尝试加锁,如果在指定时间之前都不能加锁,则返回 NO
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end

@interface NSLock : NSObject <NSLocking> {
    @private
    void *_priv;
}

- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;

@property (nullable, copy) NSString *name

@end

NSLock 源码定义来看还是比较简单的,转换成 C++ 后也只能看到一些消息发送

NSLock *lock = ((NSLock *(*)(id, SEL))(void *)objc_msgSend)((id)((NSLock *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSLock"), sel_registerName("alloc")), sel_registerName("init"));

((void (*)(id, SEL))(void *)objc_msgSend)((id)lock, sel_registerName("lock"));
((BOOL (*)(id, SEL))(void *)objc_msgSend)((id)lock, sel_registerName("tryLock"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)lock, sel_registerName("unlock"));

翻阅了下 GNUStep 代码作为参考, 里面找到了一些关于 NSLock 的实现 (GNU 并不是苹果的,这里只是将一些苹果未开源的部分模拟实现了)

NSLock (GNU)
@implementation NSLock

+ (id) allocWithZone: (NSZone*)z
{
if (self == baseLockClass && YES == traceLocks)
    {
    return class_createInstance(tracedLockClass, 0);
    }
return class_createInstance(self, 0);
}

+ (void) initialize
{
static BOOL	beenHere = NO;

if (beenHere == NO)
    {
    beenHere = YES;

    /* Initialise attributes for the different types of mutex.
    * We do it once, since attributes can be shared between multiple
    * mutexes.
    * If we had a pthread_mutexattr_t instance for each mutex, we would
    * either have to store it as an ivar of our NSLock (or similar), or
    * we would potentially leak instances as we couldn't destroy them
    * when destroying the NSLock.  I don't know if any implementation
    * of pthreads actually allocates memory when you call the
    * pthread_mutexattr_init function, but they are allowed to do so
    * (and deallocate the memory in pthread_mutexattr_destroy).
    */
    pthread_mutexattr_init(&attr_normal);
    pthread_mutexattr_settype(&attr_normal, PTHREAD_MUTEX_NORMAL);
    pthread_mutexattr_init(&attr_reporting);
    pthread_mutexattr_settype(&attr_reporting, PTHREAD_MUTEX_ERRORCHECK);
    pthread_mutexattr_init(&attr_recursive);
    pthread_mutexattr_settype(&attr_recursive, PTHREAD_MUTEX_RECURSIVE);

    /* To emulate OSX behavior, we need to be able both to detect deadlocks
    * (so we can log them), and also hang the thread when one occurs.
    * the simple way to do that is to set up a locked mutex we can
    * force a deadlock on.
    */
    pthread_mutex_init(&deadlock, &attr_normal);
    pthread_mutex_lock(&deadlock);

    baseConditionClass = [NSCondition class];
    baseConditionLockClass = [NSConditionLock class];
    baseLockClass = [NSLock class];
    baseRecursiveLockClass = [NSRecursiveLock class];

    tracedConditionClass = [GSTracedCondition class];
    tracedConditionLockClass = [GSTracedConditionLock class];
    tracedLockClass = [GSTracedLock class];
    tracedRecursiveLockClass = [GSTracedRecursiveLock class];

    untracedConditionClass = [GSUntracedCondition class];
    untracedConditionLockClass = [GSUntracedConditionLock class];
    untracedLockClass = [GSUntracedLock class];
    untracedRecursiveLockClass = [GSUntracedRecursiveLock class];
    }
}

MDEALLOC
MDESCRIPTION
MFINALIZE

/* Use an error-checking lock.  This is marginally slower, but lets us throw
* exceptions when incorrect locking occurs.
*/
- (id) init
{
if (nil != (self = [super init]))
    {
    if (0 != pthread_mutex_init(&_mutex, &attr_reporting))
    {
    DESTROY(self);
    }
    }
return self;
}

MISLOCKED
MLOCK

- (BOOL) lockBeforeDate: (NSDate*)limit
{
do
    {
    int err = pthread_mutex_trylock(&_mutex);
    if (0 == err)
    {
        CHK(Hold)
    return YES;
    }
    if (EDEADLK == err)
    {
    (*_NSLock_error_handler)(self, _cmd, NO, @"deadlock");
    }
    sched_yield();
    } while ([limit timeIntervalSinceNow] > 0);
return NO;
}

MNAME
MSTACK
MTRYLOCK
MUNLOCK

@end

NSRecursiveLock

NSRecursiveLock 是对 pthread_mutex 递归锁的封装,API 跟 NSLock 基本一致

NSRecursiveLock (GNU)
@implementation NSRecursiveLock

+ (id) allocWithZone: (NSZone*)z
{
  if (self == baseRecursiveLockClass && YES == traceLocks)
    {
      return class_createInstance(tracedRecursiveLockClass, 0);
    }
  return class_createInstance(self, 0);
}

+ (void) initialize
{
  [NSLock class];	// Ensure mutex attributes are set up.
}

MDEALLOC
MDESCRIPTION
MFINALIZE

- (id) init
{
  if (nil != (self = [super init]))
    {
      if (0 != pthread_mutex_init(&_mutex, &attr_recursive))
	{
	  DESTROY(self);
	}
    }
  return self;
}

MISLOCKED
MLOCK
MLOCKBEFOREDATE
MNAME
MSTACK
MTRYLOCK
MUNLOCK
@end

NSCondition

NSCondition 是对 pthread_mutexcond 的封装

NSCondition (GNU)
@implementation NSCondition

+ (id) allocWithZone: (NSZone*)z
{
  if (self == baseConditionClass && YES == traceLocks)
    {
      return class_createInstance(tracedConditionClass, 0);
    }
  return class_createInstance(self, 0);
}

+ (void) initialize
{
  [NSLock class];	// Ensure mutex attributes are set up.
}

- (void) broadcast
{
  pthread_cond_broadcast(&_condition);
}

MDEALLOC
MDESCRIPTION

- (void) finalize
{
  pthread_cond_destroy(&_condition);
  pthread_mutex_destroy(&_mutex);
}

- (id) init
{
  if (nil != (self = [super init]))
    {
      if (0 != pthread_cond_init(&_condition, NULL))
	{
	  DESTROY(self);
	}
      else if (0 != pthread_mutex_init(&_mutex, &attr_reporting))
	{
	  pthread_cond_destroy(&_condition);
	  DESTROY(self);
	}
    }
  return self;
}

MISLOCKED
MLOCK
MLOCKBEFOREDATE
MNAME

- (void) signal
{
  pthread_cond_signal(&_condition);
}

MSTACK
MTRYLOCK
MUNLOCK

- (void) wait
{
  pthread_cond_wait(&_condition, &_mutex);
}

- (BOOL) waitUntilDate: (NSDate*)limit
{
  NSTimeInterval ti = [limit timeIntervalSince1970];
  double secs, subsecs;
  struct timespec timeout;
  int retVal = 0;

  // Split the float into seconds and fractions of a second
  subsecs = modf(ti, &secs);
  timeout.tv_sec = secs;
  // Convert fractions of a second to nanoseconds
  timeout.tv_nsec = subsecs * 1e9;

  /* NB. On timeout the lock is still held even through condition is not met
   */

  retVal = pthread_cond_timedwait(&_condition, &_mutex, &timeout);
  if (retVal == 0)
    {
      return YES;
    }
  if (retVal == ETIMEDOUT)
    {
      return NO;
    }
  if (retVal == EINVAL)
    {
      NSLog(@"Invalid arguments to pthread_cond_timedwait");
    }
  return NO;
}

@end

NSConditionLock

NSConditionLock 是对 NSCondition 的进一步封装,可以设置具体的条件值

  • NSConditionLock 定义

    @interface NSConditionLock : NSObject <NSLocking> {
    
    - (instancetype)initWithCondition:(NSInteger)condition;
    
    @property (readonly) NSInteger condition;
    - (void)lockWhenCondition:(NSInteger)condition;
    - (BOOL)tryLock;
    - (BOOL)tryLockWhenCondition:(NSInteger)condition;
    - (void)unlockWithCondition:(NSInteger)condition;
    - (BOOL)lockBeforeDate:(NSDate *)limit;
    - (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
    @property (nullable, copy) NSString *name;
    @end
    
  • NSConditionLock 提供的关键 api

    • initWithCondition:(NSInteger)condition 初始化 condition,并且设置状态值
    • lockWhenCondition:(NSInteger)condition 当状态值为 condition 的时候加锁
    • unlockWithCondition:(NSInteger)condition 当状态值为 condition 的时候解锁

对比简单的 waitsignal 方法,允许给条件设置值

NSConditionLock (GNU)
@implementation NSConditionLock

+ (id) allocWithZone: (NSZone*)z
{
  if (self == baseConditionLockClass && YES == traceLocks)
    {
      return class_createInstance(tracedConditionLockClass, 0);
    }
  return class_createInstance(self, 0);
}

+ (void) initialize
{
  [NSLock class];	// Ensure mutex attributes are set up.
}

- (NSInteger) condition
{
  return _condition_value;
}

- (void) dealloc
{
  [_name release];
  [_condition release];
  [super dealloc];
}

- (id) init
{
  return [self initWithCondition: 0];
}

- (id) initWithCondition: (NSInteger)value
{
  if (nil != (self = [super init]))
    {
      if (nil == (_condition = [NSCondition new]))
	{
	  DESTROY(self);
	}
      else
	{
          _condition_value = value;
          [_condition setName:
            [NSString stringWithFormat: @"condition-for-lock-%p", self]];
	}
    }
  return self;
}

- (BOOL) isLockedByCurrentThread
{
  return [_condition isLockedByCurrentThread];
}

- (void) lock
{
  [_condition lock];
}

- (BOOL) lockBeforeDate: (NSDate*)limit
{
  return [_condition lockBeforeDate: limit];
}

- (void) lockWhenCondition: (NSInteger)value
{
  [_condition lock];
  while (value != _condition_value)
    {
      [_condition wait];
    }
}

- (BOOL) lockWhenCondition: (NSInteger)condition_to_meet
                beforeDate: (NSDate*)limitDate
{
  if (NO == [_condition lockBeforeDate: limitDate])
    {
      return NO;        // Not locked
    }
  if (condition_to_meet == _condition_value)
    {
      return YES;       // Keeping the lock
    }
  while ([_condition waitUntilDate: limitDate])
    {
      if (condition_to_meet == _condition_value)
	{
	  return YES;   // Keeping the lock
	}
    }
  [_condition unlock];
  return NO;            // Not locked
}

MNAME
MSTACK

- (BOOL) tryLock
{
  return [_condition tryLock];
}

- (BOOL) tryLockWhenCondition: (NSInteger)condition_to_meet
{
  if ([_condition tryLock])
    {
      if (condition_to_meet == _condition_value)
	{
	  return YES; // KEEP THE LOCK
	}
      else
	{
	  [_condition unlock];
	}
    }
  return NO;
}

- (void) unlock
{
  [_condition unlock];
}

- (void) unlockWithCondition: (NSInteger)value
{
  _condition_value = value;
  [_condition broadcast];
  [_condition unlock];
}

@end

dispatch_semaphore

semaphore 叫做 信号量

信号量的初始值,可以用来控制线程并发访问的最大数量

e.g. 信号量的初始值为 1,代表同时只允许1条线程访问资源,保证线程同步

  • e.g.

    // 最多开启 5个 线程
    dispatch_semaphore_create(5);
    // 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
    // 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成 >0,就让信号量的值 减1,然后继续往下执行代码
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    // 让信号量的值 +1
    dispatch_semaphore_signal(self.semaphore);
    

@synchronized

@synchronized 也是对 pthread_mutex 递归锁的封装,@synchronized(obj) 内部会生成 obj 对应的递归锁,然后进行加锁、解锁操作

是一种最简单又十分安全的锁方案,但是性能比较差,所在 Xcode 中连智能提示都没有,并不推荐使用

作为锁对象的两个条件:

  • 继承自NSObject
  • 必须是全局的

如果不满足全局,就不能保证锁的唯一性,相当于入口不唯一:
当一条线程访问公共资源前进行了加锁, 另外一条线程访问公共资源前也加了锁, 两个线程从不同的入口进入, 获取到了公共资源, 这样也就失去了意义

之所以经常配合 self 使用, 是因为 self 是最容易获取到的2个条件都满足的全局锁对象

@synchronized的原理

  1. 默认继承自 NSObject 的对象内部(SyncData)都有一把互斥锁, 默认开启状态

    早期版本 recursive_mutex_tt 中包装的 mLock 是基于 pthread_mutex 递归锁的封装,目前源码 objc-781 上改为了 os_unfair_recursive_lock

  2. 当执行到 @synchronized 关键字时,会先检查对象的锁的状态是开启还是关闭, 通过锁对象内部的锁, 锁住大括号内的代码,再去执行大括号内的代码, 这样其他线程就被挡在锁外等候, 当进入互斥锁内部的线程执行完锁内代码后, 会重新打开互斥锁, 这样在锁外等候的线程就可以进来了, 重复加锁-->执行内部代码-->解锁的操作
  3. 最终会通过调用 objc_sync_enterobjc_sync_exit 两个方法进行加锁、解锁操作

@synchronized 的实现原理比这里描述的要复杂的多,@synchronized 维护了一个 hash表hash表 中保存了每条线程使用 @synchronized 的情况,用 threadCount 记录线程数,用 lockCount 记录每个线程下的加锁数量。所以 @synchronized 才能支持多线程和嵌套使用。

@synchronized - objc-781中的源码

using recursive_mutex_t = recursive_mutex_tt<LOCKDEBUG>;

class recursive_mutex_tt : nocopy_t {
  os_unfair_recursive_lock mLock;
省略 .....
}

typedef struct os_unfair_recursive_lock_s {
    os_unfair_lock ourl_lock;
    uint32_t ourl_count;
} os_unfair_recursive_lock, *os_unfair_recursive_lock_t;

typedef struct alignas(CacheLineSize) SyncData {
    struct SyncData* nextData;
    DisguisedPtr<objc_object> object;
    int32_t threadCount;  // number of THREADS using this block
    recursive_mutex_t mutex;
} SyncData;

// Begin synchronizing on 'obj'. 
// Allocates recursive mutex associated with 'obj' if needed.
// Returns OBJC_SYNC_SUCCESS once lock is acquired.  
int objc_sync_enter(id obj)
{
    int result = OBJC_SYNC_SUCCESS;

    if (obj) {
        SyncData* data = id2data(obj, ACQUIRE);
        ASSERT(data);
        data->mutex.lock();
    } else {
        // @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;
}

BOOL objc_sync_try_enter(id obj)
{
    BOOL result = YES;

    if (obj) {
        SyncData* data = id2data(obj, ACQUIRE);
        ASSERT(data);
        result = data->mutex.tryLock();
    } else {
        // @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;
}

atomic

  • 原子性

    原子性是指一个操作是不可中断的,要么全部执行成功要么全部执行失败,有着“同生共死”的感觉。及时在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程所干扰。

atomic 用于保证属性 settergetter 的原子性操作,通过在 gettersetter 内部加了线程同步的锁来实现原子性, 它并不能保证使用属性的过程是线程安全的

比如一个可变数组 NSMutableArray 实例,通过 addObject: 方法添加元素时并不是线程安全的

  • 参考objc4-781objc4-818objc-accessors.mm 内属性 set 方法的具体实现

    static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
    {
        if (offset == 0) {
            object_setClass(self, newValue);
            return;
        }
    
        id oldValue;
        id *slot = (id*) ((char*)self + offset);
    
        if (copy) {
            newValue = [newValue copyWithZone:nil];
        } else if (mutableCopy) {
            newValue = [newValue mutableCopyWithZone:nil];
        } else {
            if (*slot == newValue) return;
            newValue = objc_retain(newValue);
        }
    
        if (!atomic) {
            oldValue = *slot;
            *slot = newValue;
        } else {
            spinlock_t& slotlock = PropertyLocks[slot];
            slotlock.lock();
            oldValue = *slot;
            *slot = newValue;        
            slotlock.unlock();
        }
    
        objc_release(oldValue);
    }
    
    void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
    {
        bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
        bool mutableCopy = (shouldCopy == MUTABLE_COPY);
        reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
    }
    

    根据设置的原子性,取决于是否加锁

    using spinlock_t = mutex_tt<LOCKDEBUG>;
    
    class mutex_tt : nocopy_t {
        os_unfair_lock mLock;
    ...
    }
    

    从结构、成员来看,在 781 下也是个互斥锁

pthread_spin_lock

pthread_mutex 一样同属于 pthread,由 POSIX 标准提供的接口,但是苹果似乎并未开源提供给开发者使用, 在 GNU 源码中能搜到部分接口信息

pthread_rwlock

pthread_rwlock 读写锁,经常用于文件等数据的读写操作,需要导入头文件 #import <pthread.h>

读写锁 实际是一种 特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的CPU数

写者是排他性的,⼀个读写锁同时只能有⼀个写者或多个读者(与CPU数相关),但不能同时既有读者⼜有写者。在读写锁保持期间也是抢占失效的 如果读写锁当前没有读者,也没有写者,那么写者可以⽴刻获得读写锁,否则它必须⾃旋在那⾥,直到没有任何写者或读者。如果读写锁没有写者,那么读者可以⽴即获得该读写锁,否则读者必须⾃旋在那⾥,直到写者释放该读写锁

  • 关键 api

    // 导入头文件
    #import <pthread.h>
    // 全局声明读写锁
    pthread_rwlock_t lock;
    // 初始化读写锁
    pthread_rwlock_init(&lock, NULL);
    // 读操作-加锁
    pthread_rwlock_rdlock(&lock);
    // 读操作-尝试加锁
    pthread_rwlock_tryrdlock(&lock);
    // 写操作-加锁
    pthread_rwlock_wrlock(&lock);
    // 写操作-尝试加锁
    pthread_rwlock_trywrlock(&lock);
    // 解锁
    pthread_rwlock_unlock(&lock);
    // 释放锁
    pthread_rwlock_destroy(&lock);
    

dispatch_barrier_async

栅栏函数,只有配合自己创建的并发队列才有意义。

如果传入的是串行队列或全局队列,效果等同于 dispatch_async 函数

当前面的任务都执行完后,当前任务才会开始执行,当前任务执行完后,后面的任务才会开始执行。可以达到读写锁的效果。

dispatch_queue

利用 GCD 串行队列,直接可以解决访问公共资源的安全问题,因为串行本就是按顺序执行

线程同步技术的性能排序

  • 由高到低排序

    • OSSpinLock (已经被废弃)
    • os_unfair_lock (iOS 10.0)
    • dispatch_semaphore
    • pthread_mutex
    • dispatch_queue(DISPATCH_QUEUE_SERIAL)
    • NSLock
    • NSCondition
    • pthread_mutex(recursive)
    • NSRecursiveLock
    • NSConditionLock
    • @synchronized
  • 结论

    • 尽量不要使用递归锁(取决于业务)
    • 互斥锁用于临界区持锁时间比较长的操作,自旋锁就主要用在临界区持锁时间非常短且CPU资源不紧张的情况下,自旋锁一般用于多核的服务器

iOS 这么多的同步方案,我使用最多的就是 GCD 和 信号量了,其他的在开源库中有接触,但基本都没有首选使用,在了解其内部实现原理后,可以结合今后业务的需求进行选择

⚠️ **GitHub.com Fallback** ⚠️