Last active
June 8, 2024 05:43
-
-
Save rnapier/f030f922a7c6f3dc4300d8ff8fc23033 to your computer and use it in GitHub Desktop.
Mutex backport
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Version of Swift 6 Mutex, with minimal API (just withLock), portable to at least iOS 15, probably much earlier. | |
// I cannot yet promise it's actually correct. | |
@frozen | |
public struct Mutex<Value> { | |
private let buffer: ManagedBuffer<Value, os_unfair_lock> | |
public init(_ initialValue: Value) { | |
buffer = ManagedBuffer<Value, os_unfair_lock>.create(minimumCapacity: 1) { _ in initialValue } | |
buffer.withUnsafeMutablePointerToElements { lockPtr in | |
lockPtr.initialize(to: os_unfair_lock()) | |
} | |
} | |
public func withLock<Result: Sendable>( | |
_ body: @Sendable (inout Value) throws -> Result | |
) rethrows -> Result { | |
try buffer.withUnsafeMutablePointers { value, lock in | |
os_unfair_lock_lock(lock) | |
defer { os_unfair_lock_unlock(lock) } | |
return try body(&value.pointee) | |
} | |
} | |
} | |
extension Mutex: @unchecked Sendable where Value: Sendable {} | |
// Based on Jared Sinclair's test: https://github.com/jaredsinclair/etcetera/blob/master/Tests/EtceteraTests/LockTests.swift | |
final class MutexTests: XCTestCase { | |
func testLock() { | |
let lock = Mutex(0) | |
let dispatchBlockCount = 16 | |
let iterationCountPerBlock = 100_000 | |
let queues = [ | |
DispatchQueue.global(qos: .userInteractive), | |
DispatchQueue.global(qos: .default), | |
DispatchQueue.global(qos: .utility), | |
] | |
let group = DispatchGroup() | |
for block in 0..<dispatchBlockCount { | |
group.enter() | |
let queue = queues[block % queues.count] | |
queue.async { | |
for _ in 0..<iterationCountPerBlock { | |
lock.withLock { value in | |
value = value + 2 | |
value = value - 1 | |
} | |
} | |
group.leave() | |
} | |
} | |
_ = group.wait(timeout: DispatchTime.distantFuture) | |
let finalValue = lock.withLock { $0 } | |
XCTAssertEqual(finalValue, dispatchBlockCount * iterationCountPerBlock) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment