欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

iOS开发常用线程安全锁,

来源: 开发者 投稿于  被查看 7057 次 评论:71

iOS开发常用线程安全锁,


目录
  • 正文
    • 原子属性
    • OSSpinLock - 自旋锁
    • os_unfair_lock - 互斥锁
    • NSLock - 互斥锁
    • NSCondition - 互斥锁
    • NSConditionLock - 互斥锁
    • NSRecursiveLock
    • @synchronized
    • Semaphore信号量
    • pthread_mutex
    • 读写锁

正文

多线程开发,就会有资源抢占的情况,导致出现我们意想不到的数据问题,我们就需要对数据进行加锁,已保证线程安全.

锁主要分为两大类自旋锁和互斥锁。

  • 自旋锁:自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,因此是一种忙等待。自旋锁避免了线程上下文切换的调度开销,因此对于线程只会阻塞很短的时间是很高效的,但是对于比较长时间的阻塞也是比较消耗CPU的。(线程忙等)
  • 互斥锁:如果资源已经被占用,资源申请者只能进入睡眠状态。有上下文的切换(主动出让时间片, 线程休眠, 等待下一次唤醒)、CPU的抢占、信号的发送等开销。(线程闲等)

原子属性

我们创建属性一般都会设置属性为非原子属性noatomic, 因为原子属性atomic会有额外的加锁开销,那如果我们创建属性使用原子属性atomic,它能保证property是线程安全的吗?

#import "ViewController.h"
@interface ViewController ()
@property (atomic ,assign) int count;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.count = 0;
    [self test_atomic];
}
- (void)test_atomic {
    // self.count初始值是10
    for (int i = 0; i < 10; i ++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            self.count ++;
            NSLog(@"%d",self.count);
        });
    }
}
@end

从上面我们可以看到原子属性atomic不能保证数据的线程安全.下面我们从源码进行分析:在属性的getter/setter方法调用的底层atomic和nonatomic有什么区别。先看看setter方法:objc_setProperty

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);
}
void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset{
    reallySetProperty(self, _cmd, newValue, offset, true, false, false);
}
void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset) {
    reallySetProperty(self, _cmd, newValue, offset, false, false, false);
}
void objc_setProperty_atomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset) {
    reallySetProperty(self, _cmd, newValue, offset, true, true, false);
}
void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset) {
    reallySetProperty(self, _cmd, newValue, offset, false, true, false);
}

我们可以看到都是调用的reallySetProperty方法,atomic第五个参数为true,nonatomic为false, copy第六个参数为true, mutableCopy第七个参数为true.

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);
}

copy和mutableCopy使用copyWithZone进行新值的copy,其他使用objc_retain增加引用计数.nonatomic直接进行赋值;atomic会使用spinlock_t在赋值之前加锁,赋值之后解锁. 我们再来看看getter方法:objc_getProperty

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();
    // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
    return objc_autoreleaseReturnValue(value);
}

我们可以看到nonatomic直接返回,atomic在取值前加锁,取值后解锁,再返回值.

那么原子属性atomic在getter/setter底层有加锁解锁操作,为什么不能保证线程安全的呢?
因为原子属性atomic锁住资源的范围不够大。在self.count --;的时候,既有getter也有setter,可能就出现当getter的时候还没有return出去就被其它线程setter。

OSSpinLock - 自旋锁

OSSpinLock 在iOS10之后被移除了。  被移除的原因是它有一个bug:优先级反转。

优先级反转:当多个线程有优先级的时候,有一个优先级较低的线程先去访问了资源,并是有了OSSpinLock对资源加锁,又来一个优先级较高的线程去访问了这个资源,这个时候优先级较高的线程就会一直占用cpu的资源,导致优先级较低的线程没办法与较高的线程争夺cpu的时间,最后导致最先被优先级较低的线程锁住的资源迟迟不能被释放,从而造成优先级反转的bug。

所以 OSSpinLock使用限制:必须保证所有访问同一资源的线程处于优先级平等的时候,才可以使用。

OSSpinLock已被苹果放弃了,大家也可以放弃它,苹果设计了os_unfair_lock来代替OSSpinLock。

os_unfair_lock - 互斥锁

iOS10之后开始支持,os_unfair_lock 在os库中,使用之前需要导入头文件<os/lock.h>。

#import "ViewController.h"
#import <os/lock.h>
@interface ViewController ()
@property (nonatomic ,assign) int count;
@property (nonatomic ,assign) os_unfair_lock unfairLock;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.count = 0;
    self.unfairLock = OS_UNFAIR_LOCK_INIT; // 初始化锁
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    for (int i = 0; i<10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            os_unfair_lock_lock(&_unfairLock); // 加锁
            self.count ++;
            NSLog(@"%d",self.count);
            os_unfair_lock_unlock(&_unfairLock); // 解锁
        });
    }
}
@end

NSLock - 互斥锁

NSLock - Foundation框架内部的

用户评论