我正在尝试使用pthread API在Swift中实现读/写锁定,我遇到了一个奇怪的问题。
我的实现主要基于以下内容,增加了尝试读锁的超时。
http://swiftweb.johnholdsworth.com/Deferred/html/ReadWriteLock.html
这是我的实施:
public final class ReadWriteLock {
private var lock = pthread_rwlock_t()
public init() {
let status = pthread_rwlock_init(&lock, nil)
assert(status == 0)
}
deinit {
let status = pthread_rwlock_destroy(&lock)
assert(status == 0)
}
@discardableResult
public func withReadLock<Result>(_ body: () throws -> Result) rethrows -> Result {
pthread_rwlock_rdlock(&lock)
defer { pthread_rwlock_unlock(&lock) }
return try body()
}
@discardableResult
public func withAttemptedReadLock<Result>(_ body: () throws -> Result) rethrows -> Result? {
guard pthread_rwlock_tryrdlock(&lock) == 0 else { return nil }
defer { pthread_rwlock_unlock(&lock) }
return try body()
}
@discardableResult
public func withAttemptedReadLock<Result>(_ timeout: Timeout = .now, body: () throws -> Result) rethrows -> Result? {
guard timeout != .now else { return try withAttemptedReadLock(body) }
let expiry = DispatchTime.now().uptimeNanoseconds + timeout.rawValue.uptimeNanoseconds
var ts = Timeout.interval(1).timespec
var result: Int32
repeat {
result = pthread_rwlock_tryrdlock(&lock)
guard result != 0 else { break }
nanosleep(&ts, nil)
} while DispatchTime.now().uptimeNanoseconds < expiry
// If the lock was not acquired
if result != 0 {
// Try to grab the lock once more
result = pthread_rwlock_tryrdlock(&lock)
}
guard result == 0 else { return nil }
defer { pthread_rwlock_unlock(&lock) }
return try body()
}
@discardableResult
public func withWriteLock<Return>(_ body: () throws -> Return) rethrows -> Return {
pthread_rwlock_wrlock(&lock)
defer { pthread_rwlock_unlock(&lock) }
return try body()
}
}
/// An amount of time to wait for an event.
public enum Timeout {
/// Do not wait at all.
case now
/// Wait indefinitely.
case forever
/// Wait for a given number of seconds.
case interval(UInt64)
}
public extension Timeout {
public var timespec: timespec {
let nano = rawValue.uptimeNanoseconds
return Darwin.timespec(tv_sec: Int(nano / NSEC_PER_SEC), tv_nsec: Int(nano % NSEC_PER_SEC))
}
public var rawValue: DispatchTime {
switch self {
case .now:
return DispatchTime.now()
case .forever:
return DispatchTime.distantFuture
case .interval(let milliseconds):
return DispatchTime(uptimeNanoseconds: milliseconds * NSEC_PER_MSEC)
}
}
}
extension Timeout : Equatable { }
public func ==(lhs: Timeout, rhs: Timeout) -> Bool {
switch (lhs, rhs) {
case (.now, .now):
return true
case (.forever, .forever):
return true
case (let .interval(ms1), let .interval(ms2)):
return ms1 == ms2
default:
return false
}
}
这是我的单元测试:
func testReadWrite() {
let rwLock = PThreadReadWriteLock()
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2
queue.qualityOfService = .userInteractive
queue.isSuspended = true
var enterWrite: Double = 0
var exitWrite: Double = 0
let writeWait: UInt64 = 500
// Get write lock
queue.addOperation {
enterWrite = Double(Timeout.now.rawValue.uptimeNanoseconds) / Double(NSEC_PER_MSEC)
rwLock.withWriteLock {
// Sleep for 1 second
var ts = Timeout.interval(writeWait).timespec
var result: Int32
repeat { result = nanosleep(&ts, &ts) } while result == -1
}
exitWrite = Double(Timeout.now.rawValue.uptimeNanoseconds) / Double(NSEC_PER_MSEC)
}
var entered = false
var enterRead: Double = 0
var exitRead: Double = 0
let readWait = writeWait + 50
// Get read lock
queue.addOperation {
enterRead = Double(Timeout.now.rawValue.uptimeNanoseconds) / Double(NSEC_PER_MSEC)
rwLock.withAttemptedReadLock(.interval(readWait)) {
print("**** Entered! ****")
entered = true
}
exitRead = Double(Timeout.now.rawValue.uptimeNanoseconds) / Double(NSEC_PER_MSEC)
}
queue.isSuspended = false
queue.waitUntilAllOperationsAreFinished()
let startDifference = abs(enterWrite - enterRead)
let totalWriteTime = abs(exitWrite - enterWrite)
let totalReadTime = abs(exitRead - enterRead)
print("Start Difference: \(startDifference)")
print("Total Write Time: \(totalWriteTime)")
print("Total Read Time: \(totalReadTime)")
XCTAssert(totalWriteTime >= Double(writeWait))
XCTAssert(totalReadTime >= Double(readWait))
XCTAssert(totalReadTime >= totalWriteTime)
XCTAssert(entered)
}
最后,我的单元测试的输出如下:
Start Difference: 0.00136399269104004
Total Write Time: 571.76081609726
Total Read Time: 554.105705976486
当然,测试失败是因为写锁定没有及时发布。鉴于我的等待时间只有半秒(500毫秒),为什么执行和释放写锁需要大约570毫秒?
我尝试使用优化开启和关闭都无济于事。
我的印象是nanosleep
是高分辨率睡眠定时器,我希望锁定超时的分辨率至少 5-10毫秒。
有人能在这里说清楚吗?
答案 0 :(得分:2)
由于我的单元测试中的长时间睡眠,基础正在使用OperationQueue
执行某种优化。
用usleep
替换睡眠功能并以1ms的睡眠时间迭代直到总时间超过似乎已经解决了问题。
// Get write lock
queue.addOperation {
enterWrite = Double(Timeout.now.rawValue.uptimeNanoseconds) / Double(NSEC_PER_MSEC)
rwLock.withWriteLock {
let expiry = DispatchTime.now().uptimeNanoseconds + Timeout.interval(writeWait).rawValue.uptimeNanoseconds
let interval = Timeout.interval(1)
repeat {
interval.sleep()
} while DispatchTime.now().uptimeNanoseconds < expiry
}
exitWrite = Double(Timeout.now.rawValue.uptimeNanoseconds) / Double(NSEC_PER_MSEC)
}