iOS底层|iOS底层 -- 多线程之atomic、读写安全

一、atomic atomic用于保证属性setter、getter的原子性操作,相当于在gettersetter内部加了线程同步的锁
可以参考源码objc4的objc-accessors.mm

  • setter方法实现
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 = https://www.it610.com/article/[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); }

  • get方法实现
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 = https://www.it610.com/article/objc_retain(*slot); slotlock.unlock(); // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock. return objc_autoreleaseReturnValue(value); }

分析 nonatomicatomic
  • atom:原子,不可再分割的单位
  • atomic:原子性
  • 给属性加上atomic修饰,可以保证属性settergetter都是原子性操作,也就是保证settergette内部是线程同步
@property (copy, atomic) NSString *name; - (void)setName:(NSString *)name { // 加锁 _name = name; // 解锁 }- (NSString *)name { // 加锁 return _name; // 解锁 }

  • 它并不能保证使用属性的过程是线程安全的
@property (strong, atomic) NSMutableArray *data; Person *p = [[Person alloc] init]; p.data = https://www.it610.com/article/[NSMutableArray array]; // 以下操作就不能保证线程安全了 for (int i = 0; i < 10; i++) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ [p.data addObject:@"1"]; }); }

虽然data属性是声明为atomic,但是也只是在p.data(实际上调用了get方法)和p.data = https://www.it610.com/article/[NSMutableArray array]; (实际上调用了set方法)是安全的。但是多条线程同时添加对象时,即[p.data addObject:@"1"]; 并不能保证线程安全。
二 读与写的方案 思考如何实现以下场景
  • 同一时间,只能有1个线程进行写的操作
  • 同一时间,允许有多个线程进行读的操作
  • 同一时间,不允许既有写的操作,又有读的操作
【iOS底层|iOS底层 -- 多线程之atomic、读写安全】上面的场景就是典型的“多读单写”,经常用于文件等数据的读写操作,iOS中的实现方案有
  • pthread_rwlock:读写锁
  • dispatch_barrier_async:异步栅栏调用
2.1 pthread_rwlock 等待锁的线程会进入休眠
iOS底层|iOS底层 -- 多线程之atomic、读写安全
文章图片
代码例子如下
#import @property (assign, nonatomic) pthread_rwlock_t lock; - (void)viewDidLoad { [super viewDidLoad]; // 初始化锁 pthread_rwlock_init(&_lock, NULL); dispatch_queue_t queue = dispatch_get_global_queue(0, 0); for (int i = 0; i < 10; i++) { dispatch_async(queue, ^{ [self read]; }); dispatch_async(queue, ^{ [self write]; }); } }- (void)read { pthread_rwlock_rdlock(&_lock); sleep(1); NSLog(@"%s", __func__); pthread_rwlock_unlock(&_lock); }- (void)write { pthread_rwlock_wrlock(&_lock); sleep(1); NSLog(@"%s", __func__); pthread_rwlock_unlock(&_lock); }- (void)dealloc { pthread_rwlock_destroy(&_lock); }

执行结果
2.2 dispatch_barrier_async
  • 这个函数传入的并发队列必须是自己通过dispatch_queue_cretate创建的
  • 如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果
重要方法
// 初始化队列 dispatch_queue_t queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT); // 读 dispatch_async(self.queue, ^{ [self read]; }); // 写 dispatch_barrier_async(self.queue, ^{ [self write]; });

原理如下
iOS底层|iOS底层 -- 多线程之atomic、读写安全
文章图片
代码例子如下
@property (strong, nonatomic) dispatch_queue_t queue; // 栅栏 - (void)barrier_async_test { self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT); for (int i = 0; i < 5; i++) { dispatch_async(self.queue, ^{ [self read]; }); dispatch_async(self.queue, ^{ [self read]; }); dispatch_barrier_async(self.queue, ^{ [self write]; }); } }- (void)read { sleep(1); NSLog(@"read"); }- (void)write { sleep(1); NSLog(@"write"); }

执行结果如下
iOS底层|iOS底层 -- 多线程之atomic、读写安全
文章图片

    推荐阅读