同步读写访问实例variables在iOS中的高性能?

什么是最好的方式/最less等待的方式来同步读取/写入访问的Objective-C中的实例variables为iOS?

variables读取和写入的频率很高(假设每秒读写1000次)。 更改立即生效并不重要。 读取获得一致的数据并不重要,但写入迟早会反映在读取数据中。 有一些数据结构允许这个吗?

我想到了这个:

  • 创build两个variables而不是一个variables; 我们称它们为v[0]v[1]
  • 对于每个v[i] ,创build一个并发调度队列来构build一个读写器locking机制。 我们称他们为q[i]
  • 现在对于写操作,只有v[0]被写入,遵循q[0]的locking机制。
  • 在读取操作中,首先读取v[1] ,并且仅在某个机会(例如1%)下读取操作查看v[0]并在必要时更新v[1]

下面的伪代码说明了这一点:

 typedef int VType; // the type of the variable VType* v; // array of first and second variable dispatch_queue_t* q; // queues for synchronizing access to v[i] - (void) setV:(VType)newV { [self setV:newV at:0]; } - (void) setV:(VType)newV at:(int)i { dispatch_barrier_async(q[i], ^{ v[i] = newV; }); } - (VType) getV:(int)i { __block VType result; dispatch_sync(q[i], ^{ result = v[i]; }); return result; } - (VType) getV { VType result = [self getV:1]; if ([self random] < 0.01) { VType v0_result = [self getV:0]; if (v0_result != result) { [self setV:v0_result at:1]; result = v0_result; } } return result; } - (float) random { // some random number generator - fast, but not necessarily good } 

这有以下好处:

  • v[0]通常不会被读操作占用。 因此,写入操作通常不会被阻止。

  • 在大多数情况下, v[1]不会被写入,因此读取操作通常不会被阻塞。

  • 但是,如果发生许多读操作,最终写入的值将从v[0]传播到v[1] 。 有些值可能会丢失,但这对我的应用程序无关紧要。

你们觉得呢,这个工作吗? 有更好的解决scheme吗?

更新:

一些性能基准testing(一次读取和写入一个基准testing可以同时尽快完成1秒,一个读取队列,一个写入队列):

在iOS 7的iPhone 4S上:

 runMissingSyncBenchmark: 484759 w/s runMissingSyncBenchmark: 489558 r/s runConcurrentQueueRWSyncBenchmark: 2303 w/s runConcurrentQueueRWSyncBenchmark: 2303 r/s runAtomicPropertyBenchmark: 460479 w/s runAtomicPropertyBenchmark: 462145 r/s 

在模拟器与iOS 7:

 runMissingSyncBenchmark: 16303208 w/s runMissingSyncBenchmark: 12239070 r/s runConcurrentQueueRWSyncBenchmark: 2616 w/s runConcurrentQueueRWSyncBenchmark: 2615 r/s runAtomicPropertyBenchmark: 4212703 w/s runAtomicPropertyBenchmark: 4300656 r/s 

到目前为止,primefaces属性赢了。 极大。 这是用一个SInt64testing的。

我期望与并发队列的方法在性能上与primefaces属性类似,因为它是r / w同步机制的标准方法。

当然, runMissingSyncBenchmark有时会产生显示写入SInt64已经完成。

也许, spinlock将是最优的(见man 3 spinlock)。

由于旋转locking可以被testing,如果它是当前被locking的(这是一个快速的操作),如果旋转locking由作者任务保持,读取器任务可以只返回以前的值。

也就是说,读者任务使用OSSpinLockTry() ,只有获得锁才能获取实际值。 否则,读取任务将使用以前的值。

OSSpinLockLock()任务将分别使用OSSpinLockLock()OSSpinLockUnlock()来自动更新值。

从手册页:

名称OSSpinLockTry,OSSpinLockLock,OSSpinLockUnlock – primefaces旋转locking同步原语

概要

  #include <libkern/OSAtomic.h> bool OSSpinLockTry(OSSpinLock *lock); void OSSpinLockLock(OSSpinLock *lock); void OSSpinLockUnlock(OSSpinLock *lock); 

描述

自旋锁是一种简单,快速,线程安全的同步原语,适用于争用预期较低的情况。 自旋锁操作使用内存屏障来同步对由锁保护的共享内存的访问。 抢锁时可以抢占。

OSSpinLock是一个整数types。 约定是解锁为零,locking非零。 锁必须自然alignment,不能在caching禁止的内存中。

OSSpinLockLock()将在已locking的情况下旋转,但采用各种策略退避,使其免受大多数优先级反转活锁的影响。 但是因为它可以旋转,所以在某些情况下可能效率低下。

OSSpinLockTry()在locking时立即返回false,如果locking为true,则返回true。 它不旋转。

OSSpinLockUnlock()通过置零来无条件解锁。

返回值

OSSpinLockTry()在locking时返回true,如果locking已经被locking,则返回false。

我认为CouchDeveloper在同步锁中使用try -checks的build议是一个有趣的可能性。 在我的特定实验中,它对自旋锁的影响可以忽略不计,pthread读写锁的适度增益,以及简单互斥锁的最显着影响)。 我敢打赌,不同的configuration也会在自旋锁上获得一些收益,但是我必须得到自旋锁的争用,才能使用可观察的影响。

如果您正在处理不可变或基本的数据types,则还可以使用“ 线程编程指南”中的“ 同步工具”部分中所述的atomic属性

primefaces操作是一种简单的同步forms,适用于简单的数据types。 primefaces操作的优点是它们不会阻塞竞争的线程。 对于简单的操作,比如增加一个计数器variables,这可能会导致比locking更好的性能。

不知道你已经做了你自己的基准testing,我testing了一些在这个文档中讨论过的技术(使用或不使用“try”algorithm进行互斥锁和pthread读/写锁)以及GCD读写器模式。 在我的testing中,我做了500万的读取,同时做了500k随机值的写入。 这产生了以下基准(以秒为单位测量,较小则更好)。

 | 表格| 模拟器| 设备| 
 + --------------------------- + ----------- --------- + -  +
 | primefaces|  1.9 |  7.2 |
 |  Spinlock w / o尝试|  2.8 |  8.0 |
 |  Pthread RWlockingw / try |  2.9 |  9.1 |
 | 互斥锁w / try |  2.9 |  9.4 |
 |  GCD读写器模式|  3.2 |  9.1 |
 |  Pthread RW锁没有尝试|  7.2 |  22.2 |
 |  NSLock |  23.1 |  89.7 |
 | 互斥锁无w / o尝试|  24.2 |  80.2 |
 |  @synchronized |  25.2 |  92.0 |

底线,在这个特定的testing中,primefaces性能performance最好。 显然,primefaces属性有很大的局限性,但在你的场景中,这听起来像是可以接受的。 这些结果显然会受到你场景的细节的影响,听起来好像你的testing已经证实primefaces属性为你带来了最好的性能。