Skip to content

Instantly share code, notes, and snippets.

@deze333
Last active December 12, 2017 06:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save deze333/82c0ee3e82fd250097449b1b200b7958 to your computer and use it in GitHub Desktop.
Save deze333/82c0ee3e82fd250097449b1b200b7958 to your computer and use it in GitHub Desktop.
Multi-thread to single-thread synchronized executor pattern in Swift using run loop and a custom operation object (playground)
// Multi to single thread execution
// Implemented via a run loop and a custom operation object reference passing
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3000)) {
print("Done")
PlaygroundPage.current.finishExecution()
}
/// Operation class.
class Op : NSObject {
private let op: (Int) -> Int
var result: Int? = nil
static var isRandomSleep = false
init(op: @escaping (Int) -> Int) {
self.op = op
super.init()
}
@objc private func _execute(val: Any) {
guard let val = val as? Int else { return }
// If some sleep is needed
if Op.isRandomSleep {
let t = arc4random_uniform(1000)
Thread.sleep(forTimeInterval: Double(t) * 0.001)
}
result = op(val)
}
func execute(on thread: Thread, with val: Int) {
perform(#selector(Op._execute(val:)), on: thread, with: val, waitUntilDone: true, modes: [RunLoopMode.commonModes.rawValue])
}
}
/// Sync processing in async context.
class BottleneckExecutor : NSObject {
private var thread: Thread!
func start() {
thread = Thread(target: self, selector: #selector(threadEntryPoint), object: nil)
thread.start()
}
@objc func threadEntryPoint() {
autoreleasepool {
Thread.current.name = "com.domain.app.background"
let runLoop = RunLoop.current
runLoop.add(NSMachPort(), forMode: .defaultRunLoopMode)
runLoop.run()
}
}
// Simple calculations
private func c_calcA(val: Int) -> Int {
return val * 3
}
private func c_calcB(val: Int) -> Int {
return val * 5
}
// Synchronous interface
func syncCalcA(val: Int) -> Int {
let op = Op(op: c_calcA)
op.execute(on: thread, with: val)
let verify = c_calcA(val: val)
if let r = op.result {
if r == verify {
return r
} else {
print("😡 result mismatch, \(r) != \(verify)")
}
} else {
print("😡 no result (nil)")
}
return -1
}
func syncCalcB(val: Int) -> Int {
let op = Op(op: c_calcB)
op.execute(on: thread, with: val)
let verify = c_calcB(val: val)
if let r = op.result {
if r == verify {
return r
} else {
print("😡 result mismatch, \(r) != \(verify)")
}
} else {
print("😡 no result (nil)")
}
return -1
}
}
/// Asynchronous caller thread
class Caller : Thread {
let executor: BottleneckExecutor
static let invocationsStarted = Date()
static var invocationsProcessed = 0
static var invocationsBenchmark = 1_000
init(executor: BottleneckExecutor) {
self.executor = executor
let _ = Caller.invocationsStarted
}
override func main() {
for i in 1...Caller.invocationsBenchmark {
switch arc4random_uniform(2) {
case 0:
let _ = executor.syncCalcA(val: i)
//print("calcA(\(i)) = \(r)")
default:
let _ = executor.syncCalcB(val: i)
//print("calcB(\(i)) = \(r)")
}
Caller.invocationsProcessed += 1
if Caller.invocationsProcessed >= Caller.invocationsBenchmark {
let dur = Date().timeIntervalSince(Caller.invocationsStarted)
let invocationDuration = dur / Double(Caller.invocationsProcessed)
print("Invocations processed : \(Caller.invocationsProcessed)")
print("Invocations duration : \(dur)")
print("Cost per invocation, sec: \(invocationDuration)")
// and quit...
PlaygroundPage.current.finishExecution()
}
}
}
}
// Test it out
let executor = BottleneckExecutor()
executor.start()
Op.isRandomSleep = false
var callers: [Caller] = []
for _ in 0...10 {
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