iOS 锁&线程同步

作者: 木木等你 | 来源:发表于2018-07-29 19:12 被阅读55次
为什么要用锁?

为了保证多线程访问一块公共资源时,对资源的保护。或者说是多线程安全 or 线程同步
但是线程同步的实现并不是只有加锁才能解决,串行队列也是一种解决方式。

锁通用使用步骤
//带❀的是一定要有的步骤。 
❀初始化锁 | 赋予一定参数
❀加锁 | 通过一定条件加锁
等待 | 线程进入 wait 等待条件  
❀处理公共资源代码 { } 
❀解锁 | 给锁赋予条件
销毁锁 & 锁的属性

❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀正片❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀

1.OSSpinLock (Deprecated)

介绍: 是一种'自旋锁'
使用: 
#import <libkern/OSAtomic.h>
    OSSpinLock lock = OS_SPINLOCK_INIT;
    //加锁
    OSSpinLockLock(&lock);
    //尝试加锁
    BOOL lockStatus = OSSpinLockTry(&lock);
    //你需要保护的操作
    {}
    //解锁
    OSSpinLockUnlock(&lock);
#define OS_SPINLOCK_INIT    0   (就是把lock赋值为 0)
不过这个锁已经被废弃掉了。可查看.h文件中的介绍。
'OSSpinLock' is deprecated: first deprecated in iOS 10.0 - Use os_unfair_lock() from <os/lock.h> instead

2.os_unfair_lock

介绍: 是一种低级锁('Low-level'),'互斥锁' ,看了好多博客说是自旋锁,其实都是错的。
os_unfair_lock虽然是  'OSSpinLock'  的替代品,但是它确实是互斥锁。
👇有对os_unfair_lock是互斥锁的考证。
.h中的官方解释 
Does not spin on contention but waits in the kernel to be woken up
by an unlock

使用方法

    #import <os/lock.h>
    //静态初始化
    os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
    //加锁
    os_unfair_lock_lock(&lock);
    bool isCanLock = os_unfair_lock_trylock(&lock);
    //解锁
    os_unfair_lock_unlock(&lock);

3.pthread_mutex_t

介绍: 是一种跨平台的锁(Linux,Unix,OS,iOS),本质上是一种 互斥锁,可以动态初始化。
根据传入的参数生成对应的锁.(e.g. 递归锁)

使用介绍

#import <pthread.h>
    //静态初始化锁
    pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
    //动态初始化
    pthread_mutex_t mutex;
    //初始化属性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
     //传入  PTHREAD_MUTEX_RECURSIVE  (递归锁属性。)
     //PTHREAD_MUTEX_ERRORCHECK(错误检查)
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
    pthread_mutex_init(&mutex, NULL); (初始化属性可为null) 
    //注: #  define NULL ((void*)0)
    //动态初始化锁
    pthread_mutex_init(&mutex, &attr);
     //销毁,一定销毁对应的属性。
    pthread_mutexattr_destroy(&attr);
     pthread_mutex_destroy(&mutex);
    //加锁解锁
    pthread_mutex_lock(&mutex);
    pthread_mutex_unlock(&mutex);
// 关于另一种属性的解释 PTHREAD_MUTEX_ERRORCHECK
This type of mutex provides error checking. A thread attempting to relock 
this mutex without first unlocking it shall return with an error. A thread 
attempting to unlock a mutex which another thread has locked shall return
 with an error. A thread attempting to unlock an unlocked mutex shall 
return with an error.

4.pthread_cond_t

介绍:条件锁,是pthread_mutex_t引申出来的锁。
配合pthread_mutex_t来一起使用,可以用于线程的同步。亦或者是解决线程间的依赖关系。 
当当前线程进入 wait 之后, 当前线程 mutex 会放开,保证其他线程可以拿到锁 mutex 执行,
直到收到 signal 信号或者broadcast之后才会唤醒 当前线程,并且 唤醒后再次对 mutex 进行加锁。
    //条件锁
    pthread_cond_t cond;
    //静态初始化
    pthread_cond_t cond2 = PTHREAD_COND_INITIALIZER;
    pthread_condattr_t condAttr;
    //初始化attr参数
    pthread_condattr_init(&condAttr);
    //动态初始化,也可不传attr参数
    pthread_cond_init(&cond, &condAttr);
    pthread_cond_init(&cond, NULL);
    //1.放开当前锁 2.使当前线程进入休眠(wait) 3.唤醒后会再次mutex程加锁
    pthread_cond_wait(&cond, &mutex);
    //在time之前等待,之后放开锁。
    pthread_cond_timedwait(&cond, &mutex, const struct timespec *restrict _Nullable);
    //唤醒一个被wait的线程
    pthread_cond_signal(&cond);
    //唤醒所有被wait的线程
    pthread_cond_broadcast(&cond);
    //销毁attr 和cond
    pthread_condattr_destroy(&condAttr);
    pthread_cond_destroy(&cond);
5.pthread_rwlock_t
介绍: 读写锁,(互斥锁的进化)分为读锁(rlock)和写锁(wlock),可以有多个线程共同持有读锁,但是写锁只能有一个线程持有,如果读锁被持有是,写锁是不能持有的。
需要等待读锁unlock 才能持有写锁,同样需要写锁unlock才能持有读锁。

具体使用

//静态初始化
        pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER;
        _rwlock = lock;
//动态初始化
        pthread_rwlockattr_init(&_rwlock_attr);
        pthread_rwlock_init(&_rwlock, &_rwlock_attr);
- (void)__add {
//写锁上锁
    pthread_rwlock_wrlock(&_rwlock);
    [super __add];
    pthread_rwlock_unlock(&_rwlock);
}
- (void)__readArr {
//读锁上锁
    pthread_rwlock_rdlock(&_rwlock);
    NSLog(@"self.lockArr=%@",self.lockArray);
    pthread_rwlock_unlock(&_rwlock);
}
- (void)dealloc {
//销毁 锁 & 锁的属性
    pthread_rwlockattr_destroy(&_rwlock_attr);
    pthread_rwlock_destroy(&_rwlock);
}
/*
 * 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
6.NSLock 、NSCondition 、NSConditionLock和NSRecursiveLock
简介: 都属于互斥锁。
NSLock 底层是对 pthread_mutex_t 的封装.对应的参数是 PTHREAD_MUTEX_NORMAL
NSCondition 底层则是对 pthread_cond_t 的封装. 
NSConditionLock 的底层则是使 NSCondition 实现的.
NSRecursiveLock 则是对 pthread_mutex_t 的 PTHREAD_MUTEX_RECURSIVE 参数的封装。
实现原理可以通过 GNUstep 查看 
以上都是苹果对pthread_mutex的封装,让锁的使用更面向对象了。

具体使用

    NSLock *lock = [[NSLock alloc] init];
    //尝试加锁
    BOOL isLocked = [lock tryLock];
    [lock lock];
    [lock unlock];

    //由于 NSCondition 是对 pthread_cond_t 的封装,所以使用方法与 pthread_cond_t 基本一致。
    //不同的是不需要我们去手动销毁锁。
    NSCondition *conLock = [[NSCondition alloc] init];
    [conLock lock];
    [conLock wait];
    [conLock waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
    [conLock unlock];
    [conLock signal];
    [conLock broadcast];

//NSConditionLock  设置condition 保证多线程中的同步,按自己想要的顺序执行。
//先add 然后 remove。
self.conditionLock = [[NSConditionLock alloc] init]; //默认condition 是0。
self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
- (void)demoTest {
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    sleep(3);
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}
- (void)__remove {
    [self.conditionLock lockWhenCondition:2];
    [super __remove];
    [self.conditionLock unlock];
}
- (void)__add {
    [self.conditionLock lockWhenCondition:1];
    [super __add];
    [self.conditionLock unlockWithCondition:2];
}

// NSRecursiveLock 用法类似于 NSLock 但是可以递归加锁。
    NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
    [recursiveLock lock];
    [recursiveLock unlock];
7.dispatch_semaphore
简单来说并不是锁,而是通过信号的方式,可以实现锁的一种机制。

简单使用

//create 的value 代表最多有几个信号量
     dispatch_semaphore_t sema = dispatch_semaphore_create(0);
    dispatch_after(dispatch_time( DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC),              dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"走到了块里");
        dispatch_semaphore_signal(sema);//发送1个信号量
    });
    NSLog(@"等待-----");
//如果信号量的值 >0,就让信号量的值减1,然后继续往下执行代码
//如果信号量的值 <= 0,就让线程 `sleep` (休眠).直到信号量 >0.
    dispatch_wait(sema, DISPATCH_TIME_FOREVER);
//  dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);  //两个方法都可
    NSLog(@"完成”);
2018-07-13 16:34:17.524572 AddressBook[6830:473890] 等待-----
2018-07-13 16:34:20.823293 AddressBook[6830:473911] 走到了块里
2018-07-13 16:34:20.823515 AddressBook[6830:473890] 完成
8.@synchronized(id obj) { }
简介: 互斥锁
关于 更深的synchronized的实现。实际上也是对  pthread_mutex 的递归锁的一个封装。

简单使用和底层实现:

 @synchronized(id obj) {
      //公共资源操作
        NSLog(@"加锁");
    }

实现原理:  调用堆栈
0x107d25111 <+2193>: callq  0x107d27b68 ; symbol stub for: objc_sync_enter
0x107d25139 <+2233>: callq  0x107d27ab4 ; symbol stub for: NSLog
0x107d2514a <+2250>: callq  0x107d27b6e ; symbol stub for: objc_sync_exit
0x107d2515c <+2268>: callq  0x107d27b44 ; symbol stub for: objc_release
通过查看 objc4-723 中 objc-sync.mm 源码,可以知道:
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;
}
SyncData结构体如下
typedef struct SyncData {
    struct SyncData* nextData;
    DisguisedPtr<objc_object> object;
    int32_t threadCount;  // number of THREADS using this block
    recursive_mutex_t mutex;
} SyncData;

objc_sync_enter 中 通过 synchronized 传入的对象obj 生成 data 结构体指针
然后data 在 LIST_FOR_OBJ(obj)  (static StripedMap<SyncList> sDataLists)中
取出对应的 mutex.lock. 
这个obj 就作为这个锁的key,从对应的hash表中找到对应的锁。
只要传入的obj相同,对应的锁就相同。
如果传入nil 则  // @synchronized(nil) does nothing (什么也做)

mutex 对应的就是 recursive_mutex_t。 
通过源码再往里面查看就知道 synchronized 实质就是 一把  RECURSIVE 的pthread_mutex_t (递归锁)。

lockdebug_recursive_mutex_lock(recursive_mutex_t *lock)
{
    auto& locks = ownedLocks();
    setLock(locks, lock, RECURSIVE);
}

补充 atomic (原子性) 很好的参考博客

改变setter,getter方法的实现,对方法进行加锁和解锁的操作(原子性操作)。
保证 setter和getter方法内部线程同步。底层实现是 os_unfair_lock 。
但是: 并不能保证 使用atomic修饰的属性 的线程安全。
而且性能消耗太大, 因为 setter和getter 方法调用频率太高!! 

源码实现:

objc4-723 中全局搜索atomic 发现在 objc-abi.h 文件中的 
objc_setProperty(id _Nullable self, SEL _Nonnull _cmd, ptrdiff_t offset,
                 id _Nullable newValue, BOOL atomic, signed char shouldCopy)
方法中。通过调用栈查看具体实现:
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);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) {
 if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }
} //省略部分代码
由 reallySetProperty 方法可知,如果是atomic 则会在 set 前生成 PropertyLocks 锁。
set 值之后 解锁
对应的 getter 方法中 的实现 

id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
    if (offset == 0) {
        return object_getClass(self);
    }

    // Retain release world
    id *slot = (id*) ((char*)self + offset);
    if (!atomic) return *slot;
        
    // Atomic retain release world
    spinlock_t& slotlock = PropertyLocks[slot];
    slotlock.lock();
    id value = objc_retain(*slot);
    slotlock.unlock();
    
//再通过源码查看
StripedMap<spinlock_t> PropertyLocks;
slotlock是PropertyLocks通过 slot 从StripedMap 获取。
在查看 slotlock定义
using spinlock_t = mutex_tt<LOCKDEBUG>;
class mutex_tt : nocopy_t {
    os_unfair_lock mLock;
}
可见 底层是通过  os_unfair_lock 实现。
    // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
    return objc_autoreleaseReturnValue(value);
}

几种主要锁的类别

互斥锁 sleep
是一种 low-level 的锁,相对于自旋锁来说比较低级。如果发现没有持有锁,则使线程进入sleep 状态。
自旋锁 busy-wait
相当于是一个外部死循环。当其他线程访问被锁的资源后,会一直进行循环,进入 busy-wait的状态,
直到其他线程锁放开,因为线程一直在进行执行,所以会一直占用cpu资源。
递归锁
可以让当前线程递归的去给当前线程加锁,然后解锁。
比如: 
    //动态初始化
    pthread_mutex_t mutex;
    //初始化属性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&mutex, &attr);
- (void)pthread_mutex_recursive {
    //加锁
    pthread_mutex_lock(&mutex);
    for (int i=0; i<5; i++) {
//递归调用
        [self pthread_mutex_recursive];
    }
    pthread_mutex_unlock(&mutex);
}
注意点
使用任何锁都需要消耗系统资源(内存资源和CPU时间),这种资源消耗可以分为两类:
 1.建立锁所需要的资源 
 2.当线程被阻塞时所需要的资源 

同步方案的性能排序(待考证) 从高到低

  • os_unfair_lock
  • OSSpinLock (Deprecated)
  • dispatch_semaphore
  • pthread_mutex_t
  • dispatch_queue_create("串行队列", DISPATCH_QUEUE_SERIAL); (本文未做详细介绍)
  • NSLock
  • NSCondition
  • pthread_mutex( recursive)
  • NSRecursiveLock
  • NSConditionLock
  • @synchronized

关于 os_unfair_lock 是互斥锁的考证

os_unfair_lock的使用.png

thread_9中对资源加锁,在thread_10中对os_unfair_lock_lock()的实现进行disassembly 查看。


image.png

下面是调用栈,省略了其他步骤

->  0x10d7628c4 <+20>:  movq   0x43bd(%rip), %rdi ; OSUnfairLock._unfair_lock
->  0x10d762fb6 <+0>: jmpq   *0x217c(%rip); os_unfair_lock_lock
->  0x1128d334b <+19>: jmp    0x1128d3350; _os_unfair_lock_lock_slow
->  0x1128d33cd <+125>: callq  0x1128d3ae6 ; _os_ulock_wait
->  0x1128d3afa <+20>:  callq  0x1128d5318 ; symbol stub for: __ulock_wait
->  0x1128d5318 <+0>: jmpq   *0x1d5a(%rip);  __ulock_wait
->  0x1128ae31c <+8>:  syscall 

当调用玩 syscall的时候线程进入休眠而不是进行自旋。所以 os_unfair_lock是互斥锁
#0  0x00000001128ae31e in __ulock_wait ()

用到的资源

写在最后: 关于技术的运用,总结一句话:知识决定你的下限,但是想象力决定你的上限。熟练的运用在项目中才是我们最需要的。

可以关注 我的掘金 也可以 关注 我的简书

如果本文帮助了你,也可以赞助我一哈,O(∩_∩)O哈哈~

相关文章

  • 起底多线程同步锁(iOS)

    起底多线程同步锁(iOS) 起底多线程同步锁(iOS)

  • iOS 锁&线程同步

    为什么要用锁? 为了保证多线程访问一块公共资源时,对资源的保护。或者说是多线程安全 or 线程同步但是线程同步的实...

  • OC--各种线程锁

    参考:正确使用多线程同步锁@synchronized()iOS中的锁iOS多线程安全详解iOS 常见知识点(三):...

  • 线程锁

    探讨iOS开发中各种锁使用NSCondition实现多线程同步 NSCondition是线程同步, 阻塞线程。 取...

  • iOS中的锁

    起底多线程同步锁(iOS) OSSpinLock NSLock NSRecursiveLock 同步 NSCond...

  • iOS线程同步

    iOS线程同步 iOS线程同步

  • 底层21:多线程-锁

    iOS中的线程同步方案: 1.OSSpinLock: 叫做“自旋锁”,等待锁的线程会处于忙等(busy-wait)...

  • iOS 并发,锁,线程同步【二】Operation

    在之前的一篇文章中【iOS 并发,锁,线程同步【一】GCD】,我们讨论了一下 GCD 的并发,锁和线程同步的问题,...

  • iOS开发中常用的锁

    锁是线程编程同步工具的基础,在iOS开发中常用的锁有以下几种: @synchronized NSLock 对象锁 ...

  • IOS线程同步-锁

    为什么要线程同步 我们在使用多线程的时候,可能会遇到多个线程同时访问同一个数据导致数据错乱和数据不安全的问题,所以...

网友评论

    本文标题:iOS 锁&线程同步

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