Swift pthread读/写锁需要一段时间来释放锁

时间:2016-11-23 04:46:36

标签: swift multithreading locking pthreads readwritelock

我正在尝试使用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毫秒。

有人能在这里说清楚吗?

1 个答案:

答案 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)
    }