ios - 在 iOS 中同步对实例变量的读/写访问以实现高性能?

标签 ios iphone multithreading synchronization

在 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.

OSSpinLockis 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/

相关文章:

java - java静态同步方法

java - 在 Java 中,在多线程程序中保留前 100 项的最佳方法是什么?

ios - 无法从第二个数据源动态添加自定义单元格

ios - 类型不符合未定义的协议(protocol)

ios - 在 iOS 上不使用 UIWebview 打开 word、excel 和 PDF 文件

iphone - 如何为 iPhone 分发证书创建私钥?

ios - 强制 App 导航到 App DidBecomeActive 中的特定 View

iphone - iOS 的温度

IOS如何获取Facebook扩展权限对话框

c++ - std::thread() 和 std::ref() 在类内部使用时导致构建错误