Property中好玩的修饰符 - atomic

1.引

这是atomic的源码,源码地址点 这里

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

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 (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}

源码里说明了 在gettersetter里添加了一把叫SpinLock的锁,保证了settergetter的线程安全

2.那么啥是SpinLock呢?

了解一下SpinLock

中文叫自旋锁,当一个线程获得锁之后,其他线程将会一直循环在哪里查看是否该锁被释放。所以,此锁比较适用于锁的持有者保存时间较短的情况下。

在OC里它是OSSpinLock,OS为前缀说明它是MacOS的API


OSSpinLock

这里有一个问题,为什么苹果使用自旋锁,而不使用其它的锁?
赋值操作是一个非常快速的操作,如果用互斥锁的话,让等待外面的线程休眠,再醒来,是一件多么痛苦的事情啊,好不容易睡着了,睡没几毫秒就得起来,会有起床气的!这里的起床气意思是消耗CPU资源~

3.atomic安全吗?

这是一道经典的面试题!-_-

看如下代码

@property (atomic, assign) NSInteger plus;//有一个atomic的Int数,用来自增

有一个自增的方法,循环10次,为了模拟线程抢夺的场景,我让当前线程睡1秒后执行自增然后打印当前线程以及自增后的值

- (void)plusMethod
{
    for (NSInteger i = 0; i < 10; i++) {
        sleep(1);
        
        self.plus++;
        NSLog(@"%@ plus:%ld", NSThread.currentThread, self.plus);

    }
}

最后创建两条线程去操作这个自增方法

- (void)gcd
{
    queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        [self plusMethod];
    });
    
    dispatch_async(queue, ^{
        [self plusMethod];
    });
}

想一下打印的结果,是不是0,1,2...19

打印的结果

<NSThread: 0x604000276080>{number = 3, name = (null)} plus:2
<NSThread: 0x60000047cc40>{number = 4, name = (null)} plus:2
<NSThread: 0x60000047cc40>{number = 4, name = (null)} plus:4
<NSThread: 0x604000276080>{number = 3, name = (null)} plus:3
<NSThread: 0x60000047cc40>{number = 4, name = (null)} plus:5
<NSThread: 0x604000276080>{number = 3, name = (null)} plus:6
<NSThread: 0x604000276080>{number = 3, name = (null)} plus:8
<NSThread: 0x60000047cc40>{number = 4, name = (null)} plus:8
<NSThread: 0x604000276080>{number = 3, name = (null)} plus:9
<NSThread: 0x60000047cc40>{number = 4, name = (null)} plus:10
<NSThread: 0x60000047cc40>{number = 4, name = (null)} plus:11
<NSThread: 0x604000276080>{number = 3, name = (null)} plus:12
<NSThread: 0x604000276080>{number = 3, name = (null)} plus:13
<NSThread: 0x60000047cc40>{number = 4, name = (null)} plus:14
<NSThread: 0x60000047cc40>{number = 4, name = (null)} plus:15
<NSThread: 0x604000276080>{number = 3, name = (null)} plus:15
<NSThread: 0x60000047cc40>{number = 4, name = (null)} plus:16
<NSThread: 0x604000276080>{number = 3, name = (null)} plus:17
<NSThread: 0x604000276080>{number = 3, name = (null)} plus:18
<NSThread: 0x60000047cc40>{number = 4, name = (null)} plus:18

结果的确是打印了20次,但是打印的结果并不是我们想象中的1~20,也就是说 这段 代码由线程安全的问题。
atomic并不是线程安全的,它只保证修饰的属性的set和get在多线程调用下是安全的,但是并不能保证数据的线程安全,
解决方法:添加一把锁

- (void)plusMethod
{
    for (NSInteger i = 0; i < 10; i++) {
        sleep(1);
        OSSpinLockLock(&_spinLock);// <----
        self.plus++;
        NSLog(@"%@ plus:%ld", NSThread.currentThread, self.plus);
        OSSpinLockUnlock(&_spinLock);// <-----
    }
}

4.题外话

在Swift里,SpinLock是一个宏,它的值一把递归锁~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 前言 iOS开发中由于各种第三方库的高度封装,对锁的使用很少,刚好之前面试中被问到的关于并发编程锁的问题,都是一知...
    喵渣渣阅读 3,783评论 0 33
  • 前言 生活中的锁随处可见,锁的作用也不言而喻,本文小结一下iOS的锁。 技能表 atomic (酱油君) @syn...
    01_Jack阅读 2,815评论 4 25
  • 锁是一种同步机制,用于多线程环境中对资源访问的限制iOS中常见锁的性能对比图(摘自:ibireme): iOS锁的...
    LiLS阅读 1,573评论 0 6
  • 本文不介绍各种锁的高级用法,只是整理锁相关的知识点,帮助理解。 锁的作用 防止在多线程(多任务)的情况下对共享资源...
    HelloiWorld阅读 2,964评论 0 8
  • 闹闹的出生给张先生一家都带了欢乐,可是就在闹闹出生1个月的时候,父母发现闹闹皮肤出现瘀点、瘀斑,经过检查,被诊断为...
    健康小贴士阅读 736评论 0 0