Last active
September 30, 2018 09:51
-
-
Save deze333/23d11123f02e65c456d16ffe5621e2ee to your computer and use it in GitHub Desktop.
Multi-thread to single-thread synchronized executor pattern in Swift (playground)
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
// Multi to single thread execution | |
// https://stackoverflow.com/questions/47724198/sync-calls-from-swift-to-c-thread-unsafe-library/47764223#47764223 | |
// Credits go to https://stackoverflow.com/users/819340/paulo-mattos | |
import Foundation | |
import PlaygroundSupport | |
PlaygroundPage.current.needsIndefiniteExecution = true | |
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(10 * 60)) { | |
print("Done") | |
PlaygroundPage.current.finishExecution() | |
} | |
/// Executes provided closure withing dedicated single thread context. | |
class SingleThreadExecutor { | |
private var thread: Thread! | |
private let threadAvailability = DispatchSemaphore(value: 1) | |
private var nextBlock: (() -> Void)? | |
private let nextBlockPending = DispatchSemaphore(value: 0) | |
private let nextBlockDone = DispatchSemaphore(value: 0) | |
init(label: String) { | |
thread = Thread(block: self.run) | |
thread.name = label | |
thread.start() | |
} | |
func sync(block: @escaping () -> Void) { | |
threadAvailability.wait() | |
nextBlock = block | |
nextBlockPending.signal() | |
nextBlockDone.wait() | |
nextBlock = nil | |
threadAvailability.signal() | |
} | |
private func run() { | |
while true { | |
nextBlockPending.wait() | |
nextBlock!() | |
nextBlockDone.signal() | |
} | |
} | |
} | |
// Example C-style functions | |
func c_calcA(val: Int) -> Int { | |
return val * 3 | |
} | |
func c_calcB(val: Int) -> Int { | |
return val * 5 | |
} | |
// Asynchronous caller thread | |
class Caller : Thread { | |
let executor: SingleThreadExecutor | |
init(executor: SingleThreadExecutor) { | |
self.executor = executor | |
} | |
override func main() { | |
for i in 1...1_000 { | |
let r: Int | |
let verify: Int | |
switch arc4random_uniform(2) { | |
case 0: | |
r = calcA(val: i) | |
verify = c_calcA(val: i) | |
default: | |
r = calcB(val: i) | |
verify = c_calcB(val: i) | |
} | |
if r != verify { | |
print("😡 result mismatch, \(r) != \(verify)") | |
} | |
} | |
} | |
func calcA(val: Int) -> Int { | |
var result: Int! | |
executor.sync { | |
result = c_calcA(val: val) | |
// Some random sleep | |
let t = arc4random_uniform(1000) | |
Thread.sleep(forTimeInterval: Double(t) * 0.001) | |
} | |
return result | |
} | |
func calcB(val: Int) -> Int { | |
var result: Int! | |
executor.sync { | |
result = c_calcB(val: val) | |
// Some random sleep | |
let t = arc4random_uniform(1000) | |
Thread.sleep(forTimeInterval: Double(t) * 0.001) | |
} | |
return result | |
} | |
} | |
// Try it out | |
let executor = SingleThreadExecutor(label: "single thread executor instance") | |
var callers: [Caller] = [] | |
for _ in 0...30 { | |
let caller = Caller(executor: executor) | |
callers.append(caller) | |
caller.start() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment