Skip to content

Instantly share code, notes, and snippets.

@deze333
Last active September 30, 2018 09:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save deze333/23d11123f02e65c456d16ffe5621e2ee to your computer and use it in GitHub Desktop.
Save deze333/23d11123f02e65c456d16ffe5621e2ee to your computer and use it in GitHub Desktop.
Multi-thread to single-thread synchronized executor pattern in Swift (playground)
// 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