Last active
May 2, 2018 22:57
-
-
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.
This file contains hidden or 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
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 | |
} | |
} | |
} |
This file contains hidden or 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
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