Skip to content

Instantly share code, notes, and snippets.

@jakebromberg
Last active May 2, 2018 22:57
Show Gist options
  • Save jakebromberg/77cd98a4c10fdd304d108c35df92e807 to your computer and use it in GitHub Desktop.
Save jakebromberg/77cd98a4c10fdd304d108c35df92e807 to your computer and use it in GitHub Desktop.
Operation and dispatch queues on their own don't guarantee any single thread of execution. Here's one way to fix that.
import Foundation
@objc public final class SingleThreadedOperationQueue: Thread {
public typealias Operation = () -> ()
public func addOperation(_ operation: @escaping Operation) {
enqueueLock.lock()
defer {
enqueueLock.unlock()
}
queue.enqueue(operation)
emptyQueueLock.signal()
}
@objc private func runOnCurrentThread(operation: Operation) {
operation()
}
public override func main() {
while !isCancelled {
enqueueLock.lock()
if let operation = queue.dequeue() {
operation()
}
enqueueLock.unlock()
if queue.isEmpty {
emptyQueueLock.lock()
emptyQueueLock.wait()
// waiting for signal...
emptyQueueLock.unlock()
}
}
}
// MARK - Private
// Locks when enqueuing or dequeuing
private let enqueueLock = NSLock()
// Locks when the queue is empty
private let emptyQueueLock = NSCondition()
private let queue = Queue<Operation>()
}
final class Queue<Element> {
func enqueue(_ element: Element) {
let node = Node(element)
node.next = tail
tail?.previous = node
tail = node
if head == nil {
head = node
}
}
func dequeue() -> Element? {
let result = head
head = head?.previous
return result?.element
}
var isEmpty: Bool {
return head == nil
}
private var head: Node?
private var tail: Node?
private final class Node {
let element: Element
var next: Node?
var previous: Node?
init(_ element: Element) {
self.element = element
}
}
}
let group = DispatchGroup()
group.enter()
let operationQueue = SingleThreadedOperationQueue()
operationQueue.addOperation {
print("1")
group.leave()
}
operationQueue.start()
group.wait()
group.enter()
operationQueue.addOperation {
print("2")
}
operationQueue.addOperation {
print("3")
group.leave()
}
for _ in 1...100 {
operationQueue.addOperation {
assert(Thread.current == operationQueue)
}
}
group.wait()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment