How do we implement wait / notify in Swift
Asked Answered
E

2

7

In Java, we can do something like this:

synchronized(a) {
    while(condition == false) {
        a.wait(time);
    }
    //critical section ...
    //do something
}

The above is a conditional synchronized block, that waits for a condition to become successful to execute a critical section.

When a.wait is executed (for say 100 ms), the thread exits critical section for that duration & some other critical section synchronized by object a executes, which makes condition true.

When the condition becomes successful, next time current thread enters the critical section and evaluates condition, loop exits and code executes.

Important points to note: 1. Multiple critical sections synchronized by same object. 2. A thread is not in critical section for only the duration of wait. Once wait comes out, the thread is in critical section again.

Is the below the proper way to do the same in Swift 4 using DispatchSemaphore?

while condition == false {
    semaphore1.wait(duration)
}
semaphore1.wait()
//execute critical section
semaphore1.signal()

The condition could get modified by the time we enter critical section.

So, we might have to do something like below to achieve the Java behavior. Is there a simpler way to do this in Swift?

while true {
    //lock
    if condition == false {
        //unlock
        //sleep for sometime to prevent frequent polling
        continue
    } else {
        //execute critical section
        //...
        //unlock
        break
    }
}
Edelsten answered 11/8, 2018 at 9:38 Comment(0)
U
10

Semaphores

You can solve this problem with a DispatchSemaphore.

Let's look at this code. Here we have a semaphore, storage property of type String? and a serial queue

let semaphore = DispatchSemaphore(value: 0)
var storage: String? = nil
let serialQueue = DispatchQueue(label: "Serial queue")

Producer

func producer() {
    DispatchQueue.global().asyncAfter(deadline: .now() + 3) {
        storage = "Hello world!"
        semaphore.signal()
    }
}

Here we have a function that:

  1. Waits for 3 seconds
  2. Writes "Hello world" into storage
  3. Sends a signal through the semaphore

Consumer

func consumer() {
    serialQueue.async {
        semaphore.wait()
        print(storage)
    }
}

Here we have a function that

  1. Waits for a signal from the semaphore
  2. Prints the content of storage

Test

Now I'm going to run the consumer BEFORE the producer function

consumer()
producer()

Result

Optional("Hello world!")

How does it work?

func consumer() {
    serialQueue.async {
        semaphore.wait()
        print(storage)
    }
}

The body of the consumer() function is executed asynchronously into the serial queue.

serialQueue.async {
    ...
}

This is the equivalent of your synchronized(a). Infact, by definition, a serial queue will run one closure at the time.

The first line inside the closure is

semaphore.wait()

So the execution of the closure is stopped, waiting for the green light from the semaphore.

This is happening on a different queue (not the main one) so we are not blocking the main thread.

func producer() {
    DispatchQueue.global().asyncAfter(deadline: .now() + 3) {
        storage = "Hello world!"
        semaphore.signal()
    }
}

Now producer() is executed. It waits for 3 seconds on a queue different from the main one and then populates storageand send a signal via the semaphore.

Finally consumer() receives the signal and can run the last line

print(storage)

Playground

If you want to run this code in Playground remember to

import PlaygroundSupport

and to run this line

PlaygroundPage.current.needsIndefiniteExecution = true
Urethra answered 13/8, 2018 at 6:54 Comment(1)
Like idea of 'lightweight' semaphore-based synchronization rather than Notification Center. However, if you need to signal waiter from a different source file, you're stuck passing semaphore ref around as func argument directly or indirectly, whereas with Notification center waiter can be signaled using an agreed notification name (string). So you're you're either stuck with C header-like approach of defining a global string constant somewhere, or passing a reference around.Vertex
E
-2

Answering my question.

Used an instance of NSLock to lock and unlock in the below pseudo code.

while true {
    //lock
    if condition == false {
        //unlock
        //sleep for sometime to prevent frequent polling
        continue
    } else {
        //execute critical section
        //...
        //unlock
        break
    }
}
Edelsten answered 11/8, 2018 at 11:14 Comment(3)
“while true” is actually a terrible ideaUrethra
the loop spends most of the time in nslock lock or in sleep. the while true is for flow control only. it worked fine..Edelsten
This is a VERY bad and unsafe solution. Use semaphore!Lehrer

© 2022 - 2024 — McMap. All rights reserved.