DispatchQueue vs DispatchSemaphore

Definition

Semaphores and groups have, in a sense, opposite semantics. Both maintain a count. With a semaphore, a wait is allowed to proceed when the count is non-zero. With a group, a wait is allowed to proceed when the count is zero.

A semaphore is useful when you want to set a maximum on the number of threads operating on some shared resource at a time. One common use is when the maximum is 1 because the shared resource requires exclusive access.

A group is useful when you need to know when a bunch of tasks have all been completed. Reference

Simple words

Use a semaphore to limit the amount of concurrent work at a given time. Use a group to wait for any number of concurrent work to finish execution.

Reference

Example

Execute the following in a playground.

func performUsingGroup() {
    let dq1 = DispatchQueue.global(qos: .default)
    let dq2 = DispatchQueue.global(qos: .default)
    let group = DispatchGroup()

    for i in 1...3 {
        group.enter()
        dq1.async {
            print("\(#function) DispatchQueue 1: \(i)")
            group.leave()
        }
    }
    for i in 1...3 {
        group.enter()
        dq2.async {
            print("\(#function) DispatchQueue 2: \(i)")
            group.leave()
        }
    }

    group.notify(queue: DispatchQueue.main) {
        print("done by group")
    }
}

func performUsingSemaphore() {
    let dq1 = DispatchQueue.global(qos: .default)
    let dq2 = DispatchQueue.global(qos: .default)
    let semaphore = DispatchSemaphore(value: 1)

    for i in 1...3 {
        dq1.async {
            _ = semaphore.wait(timeout: DispatchTime.distantFuture)
            print("\(#function) DispatchQueue 1: \(i)")
            semaphore.signal()
        }
    }
    for i in 1...3 {
        dq2.async {
            _ = semaphore.wait(timeout: DispatchTime.distantFuture)
            print("\(#function) DispatchQueue 2: \(i)")
            semaphore.signal()
        }
    }
}

performUsingGroup()
print("=============")
performUsingSemaphore()
Output
performUsingGroup() DispatchQueue 1: 1
performUsingGroup() DispatchQueue 1: 2
performUsingGroup() DispatchQueue 2: 1
performUsingGroup() DispatchQueue 1: 3
performUsingGroup() DispatchQueue 2: 2
performUsingGroup() DispatchQueue 2: 3
=============
performUsingSemaphore() DispatchQueue 1: 1
performUsingSemaphore() DispatchQueue 1: 2
performUsingSemaphore() DispatchQueue 1: 3
performUsingSemaphore() DispatchQueue 2: 1
performUsingSemaphore() DispatchQueue 2: 2
performUsingSemaphore() DispatchQueue 2: 3
done by group