2016-11-23 67 views
1

我想用pthread API實現Swift中的讀/寫鎖,並且遇到了一個奇怪的問題。Swift pthread讀/寫鎖需要一段時間才能釋放鎖

我的實現在很大程度上基於以下內容,並增加了嘗試讀取鎖定的超時時間。

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

如果將'withAttemptedReadLock'睡眠中的超時時間從1ms增加到10ms,會發生什麼? – caf

+0

我試過了,它對結果沒有影響。 – Randy

+0

其實,我做了一些額外的分析,我不相信這個問題是與pthread。好像額外的時間花在執行塊的內部,但它並沒有執行我的任何代碼,所以我不知道這裏發生了什麼。 – Randy

回答

1

原因是我的單元測試中長時間的睡眠導致基礎部分正在執行一些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) 
    }