在 iOS 的 objective-c 中同步对实例变量的读/写访问的最佳方式/最少等待的方式是什么?
变量被频繁读写(假设每秒读写 1000 次)。更改是否立即生效并不重要。读取获得彼此一致的数据甚至都不重要,但写入迟早必须反射(reflect)在读取获取的数据中。是否有一些数据结构允许这样做?
我想到了这个:
- 创建两个变量而不是一个变量;我们称它们为
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]
。可能会遗漏一些值,但这对我的应用程序无关紧要。
你们怎么看,这行得通吗?有更好的解决方案吗?
更新:
一些性能benchmarking(一次一个benchmark的读写尽可能快地并发完成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
测试的。
我预计并发队列的方法在性能上与原子属性相似,因为它是读写同步机制的标准方法。
当然,runMissingSyncBenchmark
有时会产生读取,表明 SInt64
的写入已完成一半。
最佳答案
也许,自旋锁
将是最佳选择(参见 man 3 自旋锁)。
由于可以测试自旋锁当前是否被锁定(这是一个快速操作),如果自旋锁被写入器任务持有,读取器任务可以只返回之前的值。
也就是说,读取器任务使用OSSpinLockTry()
并仅在可以获得锁时才检索实际值。否则,读取任务将使用之前的值。
writer 任务将分别使用OSSpinLockLock()
和OSSpinLockUnlock()
来自动更新值。
来自手册页:
NAME OSSpinLockTry, OSSpinLockLock, OSSpinLockUnlock -- atomic spin lock synchronization primitives
SYNOPSIS
#include <libkern/OSAtomic.h> bool OSSpinLockTry(OSSpinLock *lock); void OSSpinLockLock(OSSpinLock *lock); void OSSpinLockUnlock(OSSpinLock *lock);
DESCRIPTION
Spin locks are a simple, fast, thread-safe synchronization primitive that is suitable in situations where contention is expected to be low. The spinlock operations use memory barriers to synchronize access to shared memory protected by the lock. Preemption is possible while the lock is held.
OSSpinLock
is an integer type. The convention is that unlocked is zero, and locked is nonzero. Locks must be naturally aligned and cannot be in cache-inhibited memory.
OSSpinLockLock()
will spin if the lock is already held, but employs various strategies to back off, making it immune to most priority-inversion livelocks. But because it can spin, it may be inefficient in some situations.
OSSpinLockTry()
immediately returns false if the lock was held, true if it took the lock. It does not spin.
OSSpinLockUnlock()
unconditionally unlocks the lock by zeroing it.RETURN VALUES
OSSpinLockTry()
returns true if it took the lock, false if the lock was already held.
关于ios - 在 iOS 中同步对实例变量的读/写访问以实现高性能?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20851332/