Binary vs. counting is not the problem. The problem is that there's no such thing as acquiring a semaphore; the semaphore gets signaled by an arbitrary, unpredictable thread. This is not a problem with locks because the thread that acquired a lock is required to be the thread that releases the lock.
An API like this could be implemented in a way that avoids priority inversion:
struct AsyncLimiter: ~Copyable {
init(limit: Int, inOrder: Bool = false)
/// At most `limit` tasks will be performing the operation concurrently
func withControl<R,E>(operation: () async throws(E) -> R) async throws(E) -> R
}
Note that this is just a sort of async mutex when limit == 1
.
It would need to be implemented in the Swift concurrency library because of the transitive inheritance issues that @Alejandro points out (which is something we already handle with e.g. Task.get()
), but it would be implementable. How many of your uses of semaphores would that eliminate?