Skip to content

Commit

Permalink
DocC index
Browse files Browse the repository at this point in the history
  • Loading branch information
groue committed Oct 7, 2022
1 parent 48f99d6 commit 173fe8d
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 18 deletions.
55 changes: 39 additions & 16 deletions README.md
@@ -1,12 +1,14 @@
# Semaphore

**A Synchronization Primitive for Swift Concurrency**

**Requirements**: iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+ • Swift 5.7+ / Xcode 14+

---

`AsyncSemaphore` is an object that controls access to a resource across multiple execution contexts through use of a traditional counting semaphore.
This package provides `AsyncSemaphore`, a [traditional counting semaphore](https://en.wikipedia.org/wiki/Semaphore_(programming)).

Unlike [`DispatchSemaphore`], `AsyncSemaphore` does not block any thread. Instead, it suspends Swift concurrency tasks.
Unlike [`DispatchSemaphore`], it does not block any thread. Instead, Swift concurrency tasks are suspended "awaiting" for the semaphore.

### Usage

Expand All @@ -16,33 +18,54 @@ You can use a semaphore to suspend a task and resume it later:
let semaphore = AsyncSemaphore(value: 0)

Task {
// Suspends the task until a signal occurs.
await semaphore.wait()
await doSomething()
// Suspends the task until a signal occurs.
await semaphore.wait()
await doSomething()
}

// Resumes the suspended task.
semaphore.signal()
```

You can use a semaphore in order to make sure an actor's methods can't run concurrently:
An actor can use a semaphore in order to make its methods can't run concurrently:

```swift
actor MyActor {
private let semaphore = AsyncSemaphore(value: 1)
private let semaphore = AsyncSemaphore(value: 1)

func serializedMethod() async {
// Makes sure no two tasks can execute
// serializedMethod() concurrently.
await semaphore.wait()
defer { semaphore.signal() }

func serializedMethod() async {
// Makes sure no two tasks can execute self.serializedMethod() concurrently.
await semaphore.wait()
defer { semaphore.signal() }

await doSomething()
await doSomethingElse()
}
await doSomething()
await doSomethingElse()
}
}
```

A semaphore can generally limit the number of concurrent accesses to a resource:

```swift
class Downloader {
private let semaphore: AsyncSemaphore

/// Creates a Downloader that can run at most
/// `maxDownloadCount` concurrent downloads.
init(maxDownloadCount: Int) {
semaphore = AsyncSemaphore(value: maxDownloadCount)
}

func download(...) async throws -> Data {
try await semaphore.waitUnlessCancelled()
defer { semaphore.signal }
return try await ...
}
}
```

The `wait()` method has a `waitUnlessCancelled()` variant that throws `CancellationError` if the task is cancelled before a signal occurs.
You can see in the latest example that the `wait()` method has a `waitUnlessCancelled` variant that throws `CancellationError` if the task is cancelled before a signal occurs.

For a nice introduction to semaphores, see [The Beauty of Semaphores in Swift 🚦](https://medium.com/@roykronenfeld/semaphores-in-swift-e296ea80f860). The article discusses [`DispatchSemaphore`], but it can easily be ported to Swift concurrency: see the [demo playground](Demo/SemaphorePlayground.playground/Contents.swift) of this package.

Expand Down
4 changes: 2 additions & 2 deletions Sources/Semaphore/AsyncSemaphore.swift
Expand Up @@ -24,8 +24,8 @@ import Foundation
/// An object that controls access to a resource across multiple execution
/// contexts through use of a traditional counting semaphore.
///
/// Unlike `DispatchSemaphore`, ``AsyncSemaphore`` does not block any thread.
/// Instead, it suspends Swift concurrency tasks.
/// You increment a semaphore count by calling the ``signal()`` method, and
/// decrement a semaphore count by calling ``wait()`` or one of its variants.
///
/// ## Topics
///
Expand Down
70 changes: 70 additions & 0 deletions Sources/Semaphore/Documentation.docc/Documentation.md
@@ -0,0 +1,70 @@
# ``Semaphore``

A Synchronization Primitive for Swift Concurrency

## Overview

This package provides `AsyncSemaphore`, a [traditional counting semaphore](https://en.wikipedia.org/wiki/Semaphore_(programming)).

Unlike [`DispatchSemaphore`], it does not block any thread. Instead, Swift concurrency tasks are suspended "awaiting" for the semaphore.

## Usage

You can use a semaphore to suspend a task and resume it later:

```swift
let semaphore = AsyncSemaphore(value: 0)

Task {
// Suspends the task until a signal occurs.
await semaphore.wait()
await doSomething()
}

// Resumes the suspended task.
semaphore.signal()
```

An actor can use a semaphore in order to make its methods can't run concurrently:

```swift
actor MyActor {
private let semaphore = AsyncSemaphore(value: 1)

func serializedMethod() async {
// Makes sure no two tasks can execute
// serializedMethod() concurrently.
await semaphore.wait()
defer { semaphore.signal() }

await doSomething()
await doSomethingElse()
}
}
```

A semaphore can generally limit the number of concurrent accesses to a resource:

```swift
class Downloader {
private let semaphore: AsyncSemaphore

/// Creates a Downloader that can run at most
/// `maxDownloadCount` concurrent downloads.
init(maxDownloadCount: Int) {
semaphore = AsyncSemaphore(value: maxDownloadCount)
}

func download(...) async throws -> Data {
try await semaphore.waitUnlessCancelled()
defer { semaphore.signal }
return try await ...
}
}
```

You can see in the latest example that the ``AsyncSemaphore/wait()`` method has a ``AsyncSemaphore/waitUnlessCancelled()`` variant that throws `CancellationError` if the task is cancelled before a signal occurs.

For a nice introduction to semaphores, see [The Beauty of Semaphores in Swift 🚦](https://medium.com/@roykronenfeld/semaphores-in-swift-e296ea80f860).

[`DispatchSemaphore`]: https://developer.apple.com/documentation/dispatch/dispatchsemaphore

0 comments on commit 173fe8d

Please sign in to comment.