Usage of Atomic, Monitor, and Timer

Usage of Atomic

Examples:

In a multi-threaded program, atomic operations are performed for counting.

import std.sync.*
import std.time.*
import std.collection.*

let count = AtomicInt64(0)

main(): Int64 {
    let list = ArrayList<Future<Int64>>()

    /* Creates 1000 threads */
    for (_ in 0..1000) {
        let fut = spawn {
            sleep(Duration.millisecond) /* Sleeps for 1 millisecond */
            count.fetchAdd(1)
        }
        list.add(fut)
    }

    /* Waits for completion of all threads */
    for (f in list) {
        f.get()
    }

    var val = count.load()
    println("count = ${val}")
    return 0
}

Output:

count = 1000

Usage of Monitor(deprecated)

NOTE

It will be deprecated in future releases and condition will be used instead.

Examples:

In different threads, Monitor is used to suspend and awaken threads.

import std.sync.*

var mon = Monitor()
var flag: Bool = true

main(): Int64 {
    let fut = spawn {
        mon.lock()
        while (flag) {
            println("New thread: before wait")
            mon.wait()
            println("New thread: after wait")
        }
        mon.unlock()
    }

    /* Sleep for 10 milliseconds so that the new thread can be executed */
    sleep(10 * Duration.millisecond)

    mon.lock()
    println("Main thread: set flag")
    flag = false
    mon.unlock()

    println("Main thread: notify")
    mon.lock()
    mon.notifyAll()
    mon.unlock()

    /* Wait for completion of the new thread */
    fut.get()
    return 0
}

Output:

New thread: before wait
Main thread: set flag
Main thread: notify
New thread: after wait

Usage of Mutex

Examples:

The Mutex lock and unlock operations are used in different threads:

import std.sync.*

var mt = Mutex()
var con = synchronized(mt) { mt.condition() }
var flag: Bool = true

main(): Int64 {
    let fut = spawn {
        mt.lock()
        while (flag) {
            println("New thread: before wait")
            con.wait()
            println("New thread: after wait")
        }
        mt.unlock()
    }
    /* Sleep for 10 ms to ensure that a new thread can be executed. */
    sleep(10 * Duration.millisecond)
    mt.lock()
    println("Main thread: set flag")
    flag = false
    mt.unlock()

    println("Main thread: notify")
    mt.lock()
    con.notifyAll()
    mt.unlock()

    /* Wait until the execution of the new thread is completed. */
    fut.get()
    return 0
}

Results:

New thread: before wait
Main thread: set flag
Main thread: notify
New thread: after wait

Usage of Condition

Examples:

Condition is used to suspend and wake up threads.

import std.sync.{Mutex, Condition, AtomicBool}

var mutex = Mutex()
var flag = AtomicBool(true)

main(): Int64 {
    let condition: Condition
    synchronized(mutex) {
        condition = mutex.condition() // Generate a condition instance when a lock is held.
    }

    let fut = spawn {
        synchronized(mutex) {
            println("New thread: before wait")
            condition.waitUntil {=> !flag.load() } // Suspend the current thread and wait until the value of flag is true.
            println("New thread: after wait")
        }
    }

    /* Sleep for 10 ms to ensure that a new thread can be executed. */
    sleep(10 * Duration.millisecond)

    synchronized(mutex) { // Wait until the sub-thread is suspended.
        println("Main thread: set flag")
        flag.store(false) // Change the value of flag.
        println("Main thread: notify")
        condition.notifyAll() // Wake up a worker thread in the waiting state.
    }

    /* Wait until the execution of the new thread is completed. */
    fut.get()
    return 0
}

Results:

New thread: before wait
Main thread: set flag
Main thread: notify
New thread: after wait

Usage of Timer

Examples:

Timer is used to create one-off and repetitive tasks.

import std.sync.*
import std.time.{Duration, DurationExtension}

main(): Int64 {
    let count = AtomicInt8(0)

    Timer.once(50 * Duration.millisecond) { =>
        println("run only once")
        count.fetchAdd(1)
    }

    let timer = Timer.repeat(100 * Duration.millisecond, 200 * Duration.millisecond, { =>
        println("run repetitively")
        count.fetchAdd(10)
    })

    sleep(Duration.second)
    timer.cancel()
    sleep(500 * Duration.millisecond)
    println("count = ${count.load()}")
    0
}

Output:

run only once
run repetitively
run repetitively
run repetitively
run repetitively
run repetitively
count = 51