-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Petr Prokop
committed
Mar 6, 2021
1 parent
f31172c
commit a37e739
Showing
5 changed files
with
178 additions
and
9 deletions.
There are no files selected for viewing
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import Foundation | ||
|
||
/// Thread-safe priority queue wrapper | ||
/// - Important: Note that this is a `class`, i.e. reference (not value) type | ||
public final class ConcurrentPriorityQueue<Element> { | ||
|
||
private var container: PriorityQueue<Element> | ||
private let rwlock = RWLock() | ||
|
||
public var count: Int { | ||
rwlock.readLock() | ||
defer { | ||
rwlock.unlock() | ||
} | ||
return container.count | ||
} | ||
|
||
public init(capacity: Int, complexComparator: @escaping PriorityQueue<Element>.ComplexComparator) { | ||
self.container = PriorityQueue(capacity: capacity, complexComparator: complexComparator) | ||
} | ||
|
||
public init(capacity: Int, comparator: @escaping PriorityQueue<Element>.Comparator) { | ||
self.container = PriorityQueue(capacity: capacity, comparator: comparator) | ||
} | ||
|
||
/// Creates `ConcurrentPriorityQueue` with default capacity | ||
/// - Parameter comparator: heap property will hold if `comparator(parent(i), i)` is `true` | ||
/// e.g. `<` will create a minimum-queue and `>` - maximum-queue | ||
public init(_ comparator: @escaping PriorityQueue<Element>.Comparator) { | ||
self.container = PriorityQueue(comparator) | ||
} | ||
|
||
public func insert(_ value: Element) { | ||
rwlock.writeLock() | ||
container.insert(value) | ||
rwlock.unlock() | ||
} | ||
|
||
/// Remove top element from the queue | ||
public func pop() -> Element { | ||
rwlock.writeLock() | ||
defer { | ||
rwlock.unlock() | ||
} | ||
|
||
return container.pop() | ||
} | ||
|
||
/// Get top element from the queue | ||
public func peek() -> Element { | ||
rwlock.readLock() | ||
defer { | ||
rwlock.unlock() | ||
} | ||
|
||
return container.peek() | ||
} | ||
|
||
/// Get top element from the queue, returns `nil`if queue is empty | ||
public func safePeek() -> Element? { | ||
rwlock.readLock() | ||
defer { | ||
rwlock.unlock() | ||
} | ||
return container.count > 0 | ||
? container.peek() | ||
: nil | ||
} | ||
|
||
/// Remove top element from the queue, returns `nil`if queue is empty | ||
public func safePop() -> Element? { | ||
rwlock.writeLock() | ||
defer { | ||
rwlock.unlock() | ||
} | ||
|
||
return container.count > 0 | ||
? container.pop() | ||
: nil | ||
} | ||
|
||
} |
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
78 changes: 78 additions & 0 deletions
78
SwiftConcurrentCollectionsTests/ConcurrentPriorityQueueTests.swift
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import XCTest | ||
@testable import SwiftConcurrentCollections | ||
|
||
class ConcurrentPriorityQueueTests: XCTestCase { | ||
|
||
func testConcurrentReadingAndWriting() { | ||
let startDate = Date().timeIntervalSince1970 | ||
let concurrentPriorityQueue = ConcurrentPriorityQueue<Int>(<) | ||
// Unsafe version: | ||
// var concurrentPriorityQueue = PriorityQueue<Int>(<) | ||
|
||
let readingQueue = DispatchQueue( | ||
label: "ConcurrentArrayTests.readingQueue", | ||
qos: .userInteractive, | ||
attributes: .concurrent | ||
) | ||
|
||
let writingQueue = DispatchQueue( | ||
label: "ConcurrentArrayTests.writingQueue", | ||
qos: .userInteractive, | ||
attributes: .concurrent | ||
) | ||
|
||
// 100 seems to be small enough not to cause performance problems | ||
for i in 0 ..< 100 { | ||
let writeExpectation = expectation(description: "Write expectation") | ||
let readExpectation = expectation(description: "Read expectation") | ||
|
||
writingQueue.async { | ||
concurrentPriorityQueue.insert(i) | ||
writeExpectation.fulfill() | ||
} | ||
|
||
readingQueue.async { | ||
let count = concurrentPriorityQueue.count | ||
guard count > 0 else { | ||
return | ||
} | ||
|
||
let value = concurrentPriorityQueue.peek() | ||
print(value) | ||
readExpectation.fulfill() | ||
} | ||
} | ||
|
||
waitForExpectations(timeout: 10, handler: nil) | ||
print("\(#function) took \(Date().timeIntervalSince1970 - startDate) seconds") | ||
} | ||
|
||
func testPop() { | ||
let concurrentPriorityQueue = ConcurrentPriorityQueue<Int>(<) | ||
|
||
concurrentPriorityQueue.insert(1) | ||
XCTAssertEqual(concurrentPriorityQueue.count, 1) | ||
XCTAssertEqual(concurrentPriorityQueue.peek(), 1) | ||
XCTAssertEqual(concurrentPriorityQueue.pop(), 1) | ||
XCTAssertEqual(concurrentPriorityQueue.count, 0) | ||
} | ||
|
||
func testSafePop() { | ||
let concurrentPriorityQueue = ConcurrentPriorityQueue<Int>(<) | ||
|
||
XCTAssertEqual(concurrentPriorityQueue.safePop(), nil) | ||
concurrentPriorityQueue.insert(1) | ||
XCTAssertEqual(concurrentPriorityQueue.safePop(), 1) | ||
XCTAssertEqual(concurrentPriorityQueue.count, 0) | ||
} | ||
|
||
func testSafePeek() { | ||
let concurrentPriorityQueue = ConcurrentPriorityQueue<Int>(<) | ||
|
||
XCTAssertEqual(concurrentPriorityQueue.safePeek(), nil) | ||
concurrentPriorityQueue.insert(1) | ||
XCTAssertEqual(concurrentPriorityQueue.safePeek(), 1) | ||
XCTAssertEqual(concurrentPriorityQueue.count, 1) | ||
} | ||
|
||
} |