【发布时间】:2013-12-31 03:37:02
【问题描述】:
在 iOS 的 Objective-C 中同步对实例变量的读/写访问的最佳方式/最少等待导致的方式是什么?
该变量被非常频繁地读取和写入(假设每秒读取和写入 1000 次)。更改立即生效并不重要。读取是否获得彼此一致的数据甚至都不重要,但写入必须迟早反映在读取获取的数据中。是否有一些数据结构允许这样做?
我想到了这个:
- 创建两个变量而不是一个变量;我们称他们为
v[0]和v[1]。 - 对于每个
v[i],创建一个并发调度队列,用于围绕它构建readers-writer-locking 机制。我们就叫他们q[i]吧。 - 现在对于写入操作,只有
v[0]被写入,遵守q[0]的锁定机制。 - 在读取操作中,首先读取
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]。可能会遗漏一些值,但这对我的应用程序来说并不重要。
你们怎么看,这行得通吗?有没有更好的解决方案?
更新:
一些性能基准测试(一次读取和写入一个基准测试以尽可能快的速度同时进行,持续 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
到目前为止,原子属性胜出。非常好。这是用SInt64 测试的。
我希望并发队列的方法在性能上与原子属性相似,因为它是 r/w-sync 机制的标准方法。
当然,runMissingSyncBenchmark 有时会产生读取,表明对 SInt64 的写入已完成一半。
【问题讨论】:
-
您是否将其与简单队列读写器模式进行了基准测试(如 WWDC 2012 视频Asynchronous Design Patterns with Blocks, GCD, and XPC 中所述,即相同的并发队列、同步读取、异步屏障写入,就像您在上面所做的那样,但是没有单独的 v[0] 和 v[1] 逻辑)?基于 GCD 的读写器模式非常高效(通常比锁更高效),感觉上面的代码示例效率低于简单的读写器模式。
-
@Rob,我还没有比较基准测试结果。我只用一个并发队列描述了标准的读写器模式。 Instruments 显示,由于等待,阅读和写作所花费的时间平均增加了一倍。我正在尝试找到一种方法来优化它。
-
好的。正如 CouchDeveloper 建议的那样,您的读者可以使用“尝试”锁定机制(使用自旋锁或读/写互斥锁)。但是,如果对象是不可变的(或者在您的示例中是基本数据类型),您不能只使用
atomic属性吗?在那些你可以摆脱atomic属性的简单情况下,它通常比锁和读写 GCD 模式快得多。 -
@Rob,我可能会按照你的建议使用原子属性,因为它非常简单,并且比 concurrent_queue-dispatch_sync-dispatch_barrier_sync 方法快 2000 倍 (LOL)。
-
您的 2000x 评论促使我进行了一些基准测试,我得到了一些完全不同的结果(原子操作仅比 GCD 读写器模式快 20-40%,而不是快三个数量级)。最重要的是,atomic 比 GCD、自旋锁或使用 try 算法的互斥锁快一点。并且这三种同步方式比 pthread 读写锁快 2-3 倍,而 pthread 本身比简单的 mutex/NSLock/
@synchronized锁快 3-4 倍。所以,虽然我的原子性能没有你描述的 2000 倍增益,但它绝对是最快的。
标签: ios iphone multithreading synchronization