拒绝八股文,用理论知识+刻意练习碾压面试
面试题:有哪些方法可以解决多读单写问题,并比较一下性能
所谓多读单写,就是符合如下条件:
- 可以多线程读
- 读和写要互斥
- 写和写要互斥
两个方案:
- 使用GCD中的并发队列+dispatch_barrier
- 使用系统API中的pthread_rwlock_t,即读写锁
pthread_rwlock_t
先看一下官方对关键的两个方法的描述:
pthread_rwlock_rdlock ()
: The pthread_rwlock_rdlock() function acquires a read lock on rwlock, provided that rwlock is not presently held for writing and no writer threads are presently blocked on the lock. If the read lock cannot be immediately acquired, the calling thread blocks until it can acquire the lock.
pthread_rwlock_wrlock()
: The pthread_rwlock_wrlock() function blocks until a write lock can be acquired against rwlock. The pthread_rwlock_trywrlock() function performs the same action, but does not block if the lock cannot be immediately ately obtained.
GCD
很多文章中都有提到,不再赘述,贴出代码直接看下即可
class QueueThreadSafeContainer: ThreadSafeContainer {
private var data: [String: Any] = [:]
private let queue: DispatchQueue
deinit {
print("释放")
}
init(qos: DispatchQoS = .default) {
queue = DispatchQueue(
label: "com.QueueThreadSafeContainer",
qos: qos,
attributes: .concurrent,
autoreleaseFrequency: .workItem,
target: nil)
}
func set(value: Any, for key: String, completion: @escaping () -> Void) {
queue.async(flags: .barrier) {
self.data[key] = value
completion()
}
}
func data(by key: String) -> Any? {
return queue.sync {
return self.data[key]
}
}
}
性能对比
测试设备为iPhone 11 128G,debug环境下
单线程下同步读和写
var dict: [String: String] = [:]
var begin = CACurrentMediaTime()
var end = CACurrentMediaTime()
dict["a"] = "1"
dict.removeAll()
let taskCount = 10
let isReadingMode = false
let queue = DispatchQueue(label: "serial")
begin = CACurrentMediaTime()
for _ in 0..<taskCount {
queue.sync {
if isReadingMode {
_ = dict["a"]
} else {
dict["a"] = "1"
}
}
}
end = CACurrentMediaTime()
print("单线程使用serialqueue\(isReadingMode ? "读" : "写") \(taskCount)次 耗时--\((end - begin) * 1000) ms")
// clear
dict.removeAll()
var lock = pthread_rwlock_t()
pthread_rwlock_init(&lock, nil)
begin = CACurrentMediaTime()
for _ in 0..<taskCount {
if isReadingMode {
pthread_rwlock_rdlock(&lock)
} else {
pthread_rwlock_wrlock(&lock)
}
if isReadingMode {
_ = dict["a"]
} else {
dict["a"] = "1"
}
pthread_rwlock_unlock(&lock)
}
end = CACurrentMediaTime()
print("单线程使用pthread_rwlock\(isReadingMode ? "读" : "写") \(taskCount)次 耗时--\((end - begin) * 1000) ms")
1 | 100 | 1000 | |
---|---|---|---|
gcd读 | 0.045 | 0.6 | 5.3 |
pthread_rwlock读 | 0.007 | 0.3 | 2.2 |
gcd写 | 0.04 | 0.6 | 4.7 |
pthread_rwlock写 | 0.007 | 0.3 | 2.5 |
结论
单线程下测试结果可以说明,queue.sync是明显比pthread_rwlock获取锁的方法要耗时一些
多线程下读与写
实际生产环境中主要是异步写+同步读的情况,且读线程数会多于写线程数
由于pthread_rwlock无法做到异步写,所以测试中pthread_rwlock的方式使用的仍然是同步写。GCD使用的是异步写
测试方案
- 使用gcd barrier和pthread_rwlock两种方式实现Reader和Writer,进行Dictionary的读和写操作。每个Reader和Writer对Dictionary进行1000次读或写
- 每个Reader或Writer都开一个线程来模拟
- 测试不同数量Reader和Writer下,所有任务完成的总耗时(ms)情况
Reader:Writer | 5:1 | 2:1 | 1:1 | 1:2 | 1:5 | 1:10 |
---|---|---|---|---|---|---|
pthread_rwlock | 65 | 38 | 31 | 37 | 68 | 104 |
gcd barrier(qos: .default) | 70 | 45 | 31 | 52 | 102 | >200 |
gcd barrier(qos: .userInteractive) | 70 | 40 | 28 | 40 | 100 | >200 |
- gcd表现随着reader或writer变多(即线程数的变多)越来越不稳定,比如1:10比例下,起初耗时只有200-210以内,后面会逐渐上升到300多
结论
- pthread_rwlock表现更稳定,性能略好
- writer变多时pthread_rwlock性能明显更优