iOS DeveloperiOS技术收藏

iOS 锁&线程同步

2018-07-29  本文已影响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 是互斥锁的考证

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哈哈~

上一篇下一篇

猜你喜欢

热点阅读