Skip to content

sync #

Description

sync provides cross platform handling of concurrency primitives.

On FreeBSD, mutexes store native pthread_mutex_t and pthread_rwlock_t values.

On macOS and default pthread platforms, zero-value RwMutex try locks initialize the native lock and wait for concurrent first-use initialization to finish before acquiring it.

Channels can carry function values, including aliases such as type Task = fn (). Function channels support explicit capacities and default initialization in structs and globals. Arrays of channels can use an explicit init expression to initialize each channel. A received function value can be called normally.

Barrier

new_barrier(n) creates a reusable rendezvous for a positive, fixed number of participants. Share the returned pointer between workers. Each participant calls wait() once per round; all wait until the last participant arrives. Exactly one wait returns true for that round, identifying its leader. Other waits return false. A completed round resets the arrival count automatically. With one participant, every wait returns immediately with true.

Timers

Use a timer when a wait needs to participate in a select:

import sync
import time

timer := sync.new_timer(500 * time.millisecond)
defer {
    timer.stop()
}
select {
    fired_at := <-timer.c {
        println('timer fired at ${fired_at}')
    }
    // another channel can be handled here
}

Concurrent maps

sync.new_concurrent_map[K, V]() creates a map with 32 independently locked shards. Use store, load, load_or_store, and delete for concurrent access. Key types follow builtin maps; pointer keys use pointer identity even when their pointed-to data changes. load_or_store atomically returns the existing value (with true) or stores its argument (with false). len visits shards separately and may reflect concurrent changes. range snapshots each shard before calling the callback, so callbacks can mutate the map. It visits each snapshot entry until the callback returns false; concurrent additions and removals may or may not be reflected. Locks protect map operations, not objects stored as values.

Phaser and StampedLock

new_phaser() creates a reusable barrier. Register each participating worker before the phase starts. Each registered party calls arrive_and_wait() once per phase, or calls arrive_and_deregister() once to leave. The phase advances only when all remaining parties have arrived. Both methods return the phase they arrived in. With no parties, arrival and deregistration leave the phase unchanged. phase_number() observes the phase.

new_stamped_lock() offers shared read_lock()/read_unlock() and exclusive write_lock()/write_unlock() pairs. A try_optimistic_read() stamp is nonzero while no writer is active, and zero during a write. validate(stamp) rejects zero and stamps invalidated by a write. Stamp operations briefly acquire an internal mutex; they do not hold a read lock or wait for a writer to finish. Optimistic access does not make ordinary concurrent reads and writes safe: use atomic data or other synchronization, and validate the stamp after reading. Use a regular read lock for non-atomic shared data. Locks are not reentrant and must be released once for each corresponding acquisition.

Latch

new_latch(n) creates a one-shot countdown gate, with a nonnegative initial count. Each count_down() decrements a positive count. wait() blocks until the count reaches zero; all current and future waiters then pass immediately. Further countdowns do nothing. count() reads the remaining count under the latch's lock. Construct the latch with new_latch, and share the returned pointer between workers.

Parker

Share a new_parker() pointer between a worker and its waker. The worker calls park(), and the waker calls unpark(worker_id) with the worker's sync.thread_id(). An unpark wakes a registered waiter; otherwise it retains one permit for that thread's next park. Repeated unparks before a park coalesce into one permit. A wake consumes the current park's permit, so another unpark may permit the next park even before the worker resumes. Each thread's permits and waits are independent. Keep a Parker scoped to the workers using it: permits for exited threads are not automatically removed.

OnceLock and LazyLock

new_once_lock[T]() creates an initially empty cell. set(value) stores the first value and returns true; subsequent sets return false and keep that value. get() returns none before initialization. get_or_init(f) runs one initializer and returns its value to every caller. All three methods acquire the same mutex, so a get() during initialization waits for the initializer to finish. The initializer must not call any method on the same cell, since that would deadlock.

new_lazy_lock(f) binds an initializer to a cell. Its get() runs the initializer on first use and returns the cached value thereafter. Concurrent calls wait for the same initialization. The initializer must not reenter get() on the same LazyLock.

Construct these cells with their factory functions and share the returned pointers. The locks protect initialization and retrieval; a returned pointer, map, or array may alias the stored data. Synchronize any later mutation of shared returned data separately.

fn channel_select #

fn channel_select(mut channels []&Channel, dir []Direction, mut objrefs []voidptr, timeout i64) int

channel_select waits timeout on any of channels[i] until one of them can push (dir[i] == .push) or pop (dir[i] == .pop) the object referenced by objrefs[i]. timeout = i64 max means wait unlimited time. timeout <= 0 means return immediately if no transaction can be performed without waiting. It returns the selected channel index, -1 on timeout, and -2 when all channels are closed.

fn new_barrier #

fn new_barrier(n int) &Barrier

new_barrier creates a Barrier that releases once n threads are waiting. n must be positive.

fn new_channel #

fn new_channel[T](n u32) &Channel

fn new_concurrent_map #

fn new_concurrent_map[K, V]() &ConcurrentMap[K, V]

new_concurrent_map creates an empty ConcurrentMap.

fn new_cond #

fn new_cond(m &Mutex) &Cond

new_cond creates new condition variable associated with given mutex

fn new_latch #

fn new_latch(n int) &Latch

new_latch creates a Latch with the given counter. A zero counter starts open.

fn new_lazy_lock #

fn new_lazy_lock[T](f fn () T) &LazyLock[T]

new_lazy_lock creates a lazy value computed by f on first get.

fn new_many_times #

fn new_many_times(times u64) &ManyTimes

new_many_times return a new ManyTimes struct.

fn new_mutex #

fn new_mutex() &Mutex

new_mutex creates and initialises a new mutex instance on the heap, then returns a pointer to it.

fn new_once #

fn new_once() &Once

new_once return a new Once struct.

fn new_once_lock #

fn new_once_lock[T]() &OnceLock[T]

new_once_lock creates an empty (uninitialised) OnceLock.

fn new_parker #

fn new_parker() &Parker

new_parker creates an empty Parker.

fn new_phaser #

fn new_phaser() &Phaser

new_phaser creates a Phaser with no registered parties.

fn new_rwmutex #

fn new_rwmutex() &RwMutex

new_rwmutex creates a new read/write mutex instance on the heap, and returns a pointer to it.

fn new_semaphore #

fn new_semaphore() &Semaphore

new_semaphore creates a new initialised Semaphore instance on the heap, and returns a pointer to it. The initial counter value of the semaphore is 0.

fn new_semaphore_init #

fn new_semaphore_init(n u32) &Semaphore

new_semaphore_init creates a new initialised Semaphore instance on the heap, and returns a pointer to it. The n parameter can be used to set the initial counter value of the semaphore.

fn new_spin_lock #

fn new_spin_lock() &SpinLock

new_spin_lock creates and returns a new SpinLock instance initialized to unlocked state.

fn new_stamped_lock #

fn new_stamped_lock() &StampedLock

new_stamped_lock creates an unlocked StampedLock.

fn new_timer #

fn new_timer(duration time.Duration) &Timer

new_timer creates a Timer that sends the current time on its unbuffered channel after duration.

fn new_tls #

fn new_tls[T](value T) !ThreadLocalStorage[T]

new_tls creates new TLS storage initialized with the given value

fn new_waitgroup #

fn new_waitgroup() &WaitGroup

new_waitgroup creates a new WaitGroup.

fn thread_id #

fn thread_id() u64

thread_id returns a unique identifier for the caller thread.

fn Direction.from #

fn Direction.from[W](input W) !Direction

from returns the Direction value of an integer or of a field name, or an error if there is none.

fn (ConcurrentMap[K, V]) store #

fn (m &ConcurrentMap[K, V]) store(key K, value V)

store sets the value for key.

fn (ConcurrentMap[K, V]) load #

fn (m &ConcurrentMap[K, V]) load(key K) ?V

load returns the value for key, or none if absent.

fn (ConcurrentMap[K, V]) load_or_store #

fn (m &ConcurrentMap[K, V]) load_or_store(key K, value V) (V, bool)

load_or_store returns the existing value for key, or stores and returns value when absent. The second result reports whether the value was loaded (true) rather than stored (false).

fn (ConcurrentMap[K, V]) delete #

fn (m &ConcurrentMap[K, V]) delete(key K)

delete removes key. It reports nothing: deleting an absent key is a no-op.

fn (ConcurrentMap[K, V]) range #

fn (m &ConcurrentMap[K, V]) range(f fn (key K, val V) bool)

range calls f for every entry. Returning false from f stops the iteration. Entries stored during iteration may or may not be visited.

fn (ConcurrentMap[K, V]) len #

fn (m &ConcurrentMap[K, V]) len() int

len returns the total number of entries.

fn (LazyLock[T]) get #

fn (mut l LazyLock[T]) get() T

get returns the value, running the initializer once on the first call. Concurrent callers block until the initialisation finishes. The initializer must not call get on the same LazyLock: that would deadlock.

fn (OnceLock[T]) get #

fn (o &OnceLock[T]) get() ?T

get returns the stored value, or none if the cell is not initialised yet. If an initializer is running, get waits for it to finish.

fn (OnceLock[T]) set #

fn (mut o OnceLock[T]) set(value T) bool

set stores value and returns true, unless the cell was already initialised — in that case the stored value is kept and false is returned.

fn (OnceLock[T]) get_or_init #

fn (mut o OnceLock[T]) get_or_init(f fn () T) T

get_or_init returns the stored value, running f to initialise the cell on the first call. Concurrent callers block until the initialisation finishes and all observe the same value.

Note: the initializer runs under the cell's mutex. If f calls get, set, or get_or_init on the same cell, it will deadlock.

fn (ThreadLocalStorage[T]) set #

@[inline]
fn (mut t ThreadLocalStorage[T]) set(value T) !

set updates the value in TLS storage.

fn (ThreadLocalStorage[T]) get #

@[inline]
fn (mut t ThreadLocalStorage[T]) get() !T

get retrieves the current value from TLS storage.

fn (ThreadLocalStorage[T]) destroy #

@[inline]
fn (mut t ThreadLocalStorage[T]) destroy() !

destroy releases TLS resources (must be called manually).

enum Direction #

enum Direction {
	pop
	push
}

struct Barrier #

@[heap]
struct Barrier {
mut:
	mutex      &Mutex
	cond       &Cond
	n          int
	count      u64
	generation u64
}

Barrier is a reusable rendezvous point for a fixed number of threads. All n threads must call wait before any of them is released, and exactly one of them (the "leader") observes a true return value per round. Barrier is the V-idiomatic equivalent of Rust's std::sync::Barrier.

fn (Barrier) wait #

fn (mut b Barrier) wait() bool

wait blocks until n threads have called it, then releases all of them. It returns true for exactly one thread per round (the leader) and false for the rest. The barrier is reusable: the next round starts immediately.

struct C.CONDITION_VARIABLE #

@[typedef]

Available on Windows only.

struct C.CONDITION_VARIABLE {}

struct C.atomic_uintptr_t #

@[typedef]
struct C.atomic_uintptr_t {}

struct C.pthread_cond_t #

@[typedef]

Available on all platforms except Windows and FreeBSD.

struct C.pthread_cond_t {}

struct C.pthread_condattr_t #

@[typedef]

Available on all platforms except Windows and FreeBSD.

struct C.pthread_condattr_t {}

struct C.pthread_mutex_t #

@[typedef]

Available on Unix only.

struct C.pthread_mutex_t {}

struct C.pthread_rwlock_t #

@[typedef]

Available on Unix only.

struct C.pthread_rwlock_t {}

struct C.pthread_rwlockattr_t #

@[typedef]

Available on Unix only.

struct C.pthread_rwlockattr_t {}

struct C.sem_t #

@[typedef]

Available on Unix only.

struct C.sem_t {}

struct C.timespec #

Available on Unix only.

struct C.timespec {
pub mut:
	tv_sec  i64
	tv_nsec i64
}

struct Channel #

struct Channel {
	ringbuf   &u8 = unsafe { nil } // queue for buffered channels
	statusbuf &u8 = unsafe { nil } // flags to synchronize write/read in ringbuf
	objsize   u32
mut:
	// atomic
	writesem           Semaphore // to wake thread that wanted to write, but buffer was full
	readsem            Semaphore // to wake thread that wanted to read, but buffer was empty
	writesem_im        Semaphore
	readsem_im         Semaphore
	write_adr          C.atomic_uintptr_t // if != NULL the next obj can be written here without wait
	read_adr           C.atomic_uintptr_t // if != NULL an obj can be read from here without wait
	adr_read           C.atomic_uintptr_t // used to identify origin of writesem
	adr_written        C.atomic_uintptr_t // used to identify origin of readsem
	write_free         u32                // for queue state
	read_avail         u32
	buf_elem_write_idx u32
	buf_elem_read_idx  u32
	// for select
	write_subscriber &Subscription = unsafe { nil }
	read_subscriber  &Subscription = unsafe { nil }
	write_sub_mtx    &SpinLock
	read_sub_mtx     &SpinLock
	closed           u16
	close_claimed    u16 // set by the close() that closes the channel, before `closed`
	close_err        IError = none
pub:
	cap u32 // queue length in #objects
}

fn (Channel) close #

fn (mut ch Channel) close(errs ...IError)

close closes the channel and optionally stores an error that will be returned by receive operations that use or {} or ? after the buffered values have been drained.

fn (Channel) len #

@[inline]
fn (mut ch Channel) len() int

fn (Channel) closed #

@[inline]
fn (mut ch Channel) closed() bool

fn (Channel) push #

@[inline]
fn (mut ch Channel) push(src voidptr)

fn (Channel) try_push #

@[inline]
fn (mut ch Channel) try_push(src voidptr) ChanState

fn (Channel) pop #

@[inline]
fn (mut ch Channel) pop(dest voidptr) bool

fn (Channel) try_pop #

@[inline]
fn (mut ch Channel) try_pop(dest voidptr) ChanState

try_pop returns .success if an object is popped without blocking. Pass the destination as mut: ch.try_pop(mut value), not ch.try_pop(&value).

struct ConcurrentMap #

@[noinit]
struct ConcurrentMap[K, V] {
mut:
	shards []&ConcurrentShard[K, V]
}

ConcurrentMap is a thread-safe map with store/load/delete and Go-style range iteration. It shards entries over 32 independently locked partitions, so disjoint keys proceed in parallel; only same-shard keys serialize. It is the V-idiomatic equivalent of Go's sync.Map / .NET's ConcurrentDictionary. Key types follow the same constraints as builtin map[K]V.

struct Cond #

@[heap]
struct Cond {
mut:
	// Externally provided mutex for shared resource protection
	mutex &Mutex
	// Internal lock for protecting wait queue access
	inner_mutex Mutex
	// Queue of waiting channels
	waiters []chan bool
}

fn (Cond) wait #

@[direct_array_access]
fn (mut c Cond) wait()

wait waits for condition notification.

Note: Spurious wakeups are possible; always use in a loop: mutex.lock() for !condition { cond.wait() } mutex.unlock()

fn (Cond) signal #

@[direct_array_access]
fn (mut c Cond) signal()

signal wakes one waiting thread.

fn (Cond) broadcast #

@[direct_array_access]
fn (mut c Cond) broadcast()

broadcast wakes all waiting threads.

struct Latch #

@[heap]
struct Latch {
mut:
	mutex &Mutex
	cond  &Cond
	count int
}

Latch is a one-shot countdown gate: threads calling wait block until the counter reaches zero through count_down calls, then all current and future waiters pass immediately. Unlike Barrier it never resets. It is the V-idiomatic equivalent of C++'s std::latch.

fn (Latch) count_down #

fn (mut l Latch) count_down()

count_down decrements the counter and releases all waiters when it reaches zero. Calls after zero are no-ops.

fn (Latch) wait #

fn (mut l Latch) wait()

wait blocks until the counter reaches zero, then returns immediately.

fn (Latch) count #

fn (mut l Latch) count() int

count returns the current counter value.

struct LazyLock #

@[heap]
struct LazyLock[T] {
mut:
	inner &OnceLock[T]
	init  fn () T
}

LazyLock is a value that is computed on first access and is safe to share between threads. It is the V-idiomatic equivalent of Rust's std::sync::LazyLock.

struct ManyTimes #

struct ManyTimes {
mut:
	m RwMutex
pub:
	times u64 = 1
	count u64
}

fn (ManyTimes) do #

fn (mut m ManyTimes) do(f fn ())

do execute the function only setting times.

struct Mutex #

Available on macOS only.

@[heap]
struct Mutex {
	mutex  C.pthread_mutex_t
	inited u32
}

[init_with=new_mutex] // TODO: implement support for this struct attribute, and disallow Mutex{} from outside the sync.new_mutex() function.

Available on all platforms except macOS and Windows.

@[heap]
struct Mutex {
	mutex C.pthread_mutex_t
}

Available on Windows only.

@[heap]
struct Mutex {
mut:
	mx C.SRWLOCK // mutex handle
}

SRWLOCK is much more performant that Mutex on Windows, so use that in both cases since we don't want to share with other processes

fn (Mutex) str #

fn (m &Mutex) str() string

str returns a string representation of the Mutex pointer.

fn (Mutex) init #

fn (mut m Mutex) init()

init initialises the mutex. Calling it on an already initialised mutex has no effect.

fn (Mutex) lock #

@[inline]
fn (mut m Mutex) lock()

lock locks the mutex instance (lock is a keyword). If the mutex was already locked, it will block, till it is unlocked.

fn (Mutex) try_lock #

@[inline]
fn (mut m Mutex) try_lock() bool

try_lock try to lock the mutex instance and return immediately. If the mutex was already locked, it will return false.

fn (Mutex) unlock #

@[inline]
fn (mut m Mutex) unlock()

unlock unlocks the mutex instance. The mutex is released, and one of the other threads, that were blocked, because they called lock can continue.

fn (Mutex) destroy #

fn (mut m Mutex) destroy()

destroy frees the resources associated with the mutex instance.

Note: the mutex itself is not freed.

struct Once #

struct Once {
mut:
	m RwMutex
pub:
	count u64
}

fn (Once) do #

fn (mut o Once) do(f fn ())

do executes the function f() only once, regardless of how many goroutines call it concurrently. When do returns, f() has completed.

Note: if f() calls do on the same Once, it will deadlock.

fn (Once) do_with_param #

fn (mut o Once) do_with_param(f fn (voidptr), param voidptr)

do_with_param executes the function f(param) only once, regardless of how many goroutines call it concurrently. When do_with_param returns, f() has completed.

Note: if f() calls do_with_param on the same Once, it will deadlock.

struct OnceLock #

@[heap]
struct OnceLock[T] {
mut:
	mutex &Mutex
	done  bool
	value T
}

OnceLock is a cell that can be written to at most once and is safe to share between threads. It is the V-idiomatic equivalent of Rust's std::sync::OnceLock: get_or_init runs the initializer exactly once no matter how many threads race on it. Returned reference and container values can still alias the stored value; callers must synchronize later mutations. A OnceLock must be created with new_once_lock, since the internal mutex needs initialization.

struct Parker #

@[heap]
struct Parker {
mut:
	mutex   &Mutex
	permits map[u64]bool
	waiters map[u64]chan bool
}

Parker is a caller-owned registry for parking and unparking threads by thread id (see thread_id). It is the V-idiomatic equivalent of Rust's std::thread::{park, unpark}: unlike free functions, it needs no globals, so it cannot disturb other code in the module.

A Parker is typically shared between a worker thread, which calls park, and the thread that later wakes it with unpark.

fn (Parker) park #

fn (mut p Parker) park()

park blocks the calling thread until another thread calls unpark with its thread id. If unpark was already called for this thread, park returns immediately (stored permit), so wakeups are never lost.

fn (Parker) unpark #

fn (mut p Parker) unpark(tid u64)

unpark wakes the thread currently parked with the given thread id. If no thread is parked with that id yet, a permit is stored so its next park returns immediately. Repeated unparks without a waiting thread retain one permit.

struct Phaser #

@[heap]
struct Phaser {
mut:
	mutex   &Mutex
	cond    &Cond
	parties int
	arrived u64
	phase   u64
}

Phaser is a reusable barrier whose parties can join and leave at runtime. Every arrival (waiting or deregistering) counts toward the current phase; once all registered parties have arrived, the phase advances and every waiter is released. It is the V-idiomatic equivalent of Java's java.util.concurrent.Phaser.

fn (Phaser) register #

fn (mut p Phaser) register()

register adds one party to the current phase.

fn (Phaser) arrive_and_wait #

fn (mut p Phaser) arrive_and_wait() u64

arrive_and_wait records one arrival and blocks until every registered party has arrived, advancing the phase. It returns the completed phase number. With zero parties it returns the current phase immediately.

fn (Phaser) arrive_and_deregister #

fn (mut p Phaser) arrive_and_deregister() u64

arrive_and_deregister records one arrival and removes one party. If that completes the phase, the phase advances and waiters are released. It returns the phase in which the party arrived, even if it is not yet complete.

fn (Phaser) phase_number #

fn (p &Phaser) phase_number() u64

phase_number returns the current phase without arriving.

struct RwMutex #

Available on all platforms except Windows and FreeBSD.

@[heap]
struct RwMutex {
	mutex  C.pthread_rwlock_t
	inited u32
}

Available on FreeBSD only.

@[heap]
struct RwMutex {
	mutex  C.pthread_rwlock_t
	inited u32
	writer u32 // tid of current write-lock holder, 0 when none; used for self-deadlock detection
}

Available on Windows only.

@[heap]
struct RwMutex {
mut:
	mx C.SRWLOCK // mutex handle
}

fn (RwMutex) str #

fn (m &RwMutex) str() string

str returns a string representation of the RwMutex pointer.

fn (RwMutex) init #

fn (mut m RwMutex) init()

init initialises the RwMutex instance. It should be called once before the rw mutex is used, since it creates the associated resources needed for the mutex to work properly.

fn (RwMutex) rlock #

@[inline]
fn (mut m RwMutex) rlock()

rlock locks the given RwMutex instance for reading. If the mutex was already locked, it will block, and will try to get the lock, once the lock is released by another thread calling unlock. Once it succeds, it returns.

Note: there may be several threads that are waiting for the same lock.

Note: RwMutex has separate read and write locks.

fn (RwMutex) lock #

@[inline]
fn (mut m RwMutex) lock()

lock locks the given RwMutex instance for writing. If the mutex was already locked, it will block, till it is unlocked, then it will try to get the lock, and if it can, it will return, otherwise it will continue waiting for the mutex to become unlocked.

Note: there may be several threads that are waiting for the same lock.

Note: RwMutex has separate read and write locks.

fn (RwMutex) try_rlock #

@[inline]
fn (mut m RwMutex) try_rlock() bool

try_rlock try to lock the given RwMutex instance for reading and return immediately. If the mutex was already locked, it will return false.

fn (RwMutex) try_wlock #

@[inline]
fn (mut m RwMutex) try_wlock() bool

try_wlock try to lock the given RwMutex instance for writing and return immediately. If the mutex was already locked, it will return false.

fn (RwMutex) destroy #

fn (mut m RwMutex) destroy()

destroy frees the resources associated with the rwmutex instance.

Note: the mutex itself is not freed.

fn (RwMutex) runlock #

@[inline]
fn (mut m RwMutex) runlock()

runlock unlocks the RwMutex instance, locked for reading.

Note: Windows SRWLocks have different function to unlocking. To have a common portable API, there are two methods for unlocking here as well, even though that they do the same on !windows platforms.

fn (RwMutex) unlock #

@[inline]
fn (mut m RwMutex) unlock()

unlock unlocks the RwMutex instance, locked for writing.

Note: Windows SRWLocks have different function to unlocking. To have a common portable API, there are two methods for unlocking here as well, even though that they do the same on !windows platforms.

struct Semaphore #

Available on macOS only.

@[heap]
struct Semaphore {
	mtx  C.pthread_mutex_t
	cond C.pthread_cond_t
mut:
	count u32
}

Available on all platforms except macOS, Windows and FreeBSD.

@[heap]
struct Semaphore {
	sem C.sem_t
	// the mutex/condvar state of the Apple fallback described above; it is unused
	// on the platforms where `sem_init` works
	mtx  C.pthread_mutex_t
	cond C.pthread_cond_t
mut:
	count u32
}

Available on FreeBSD only.

@[heap]
struct Semaphore {
	sem C.sem_t
}

Available on Windows only.

@[heap]
struct Semaphore {
	mtx  C.SRWLOCK
	cond C.CONDITION_VARIABLE
mut:
	count u32
}

fn (Semaphore) init #

fn (mut sem Semaphore) init(n u32)

init initialises the Semaphore instance with n as its initial counter value. It should be called once before the semaphore is used, since it creates the associated resources needed for the semaphore to work properly.

fn (Semaphore) post #

fn (mut sem Semaphore) post()

post increases the counter of the semaphore by 1. If the resulting counter value is > 0, and if there is a thread waiting on the semaphore, the waiting thread will decrement the counter by 1, and then will continue running. See also .wait() .

fn (Semaphore) wait #

fn (mut sem Semaphore) wait()

wait will just decrement the semaphore count, if it was positive. It it was not positive, it will waits for the semaphore count to reach a positive number. When that happens, it will decrease the semaphore count, and will return. In effect, it allows you to block threads, until the semaphore, is posted by another thread. See also .post() .

fn (Semaphore) try_wait #

fn (mut sem Semaphore) try_wait() bool

try_wait tries to decrease the semaphore count by 1, if it was positive. If it succeeds in that, it returns true, otherwise it returns false.

Note: try_wait should return as fast as possible so error handling is only done when debugging

fn (Semaphore) timed_wait #

fn (mut sem Semaphore) timed_wait(timeout i64) bool

timed_wait is similar to .wait(), but it also accepts a timeout duration, thus it can return false early, if the timeout passed before the semaphore was posted.

fn (Semaphore) destroy #

Available on Unix only.

fn (mut sem Semaphore) destroy()

Available on Windows only.

fn (s Semaphore) destroy()

destroy frees the resources associated with the Semaphore instance.

Note: the semaphore instance itself is not freed.

struct SpinLock #

@[noinit]
struct SpinLock {
mut:
	locked  u8     // Lock state: 0 = unlocked, 1 = locked
	padding [63]u8 // Cache line padding (fills to 64 bytes total)
}

SpinLock is a mutual exclusion lock that busy-waits (spins) when locked. When one thread holds the lock, any other thread attempting to acquire it will loop repeatedly until the lock becomes available.

fn (SpinLock) lock #

@[inline]
fn (s &SpinLock) lock()

lock acquires the spin lock. If the lock is currently held by another thread, this function will spin (busy-wait) until the lock becomes available.

fn (SpinLock) try_lock #

@[inline]
fn (s &SpinLock) try_lock() bool

try_lock try to lock the spin lock instance and return immediately. If the spin lock was already locked, it will return false.

fn (SpinLock) unlock #

@[inline]
fn (s &SpinLock) unlock()

unlock releases the spin lock, making it available to other threads. IMPORTANT: Must only be called by the thread that currently holds the lock.

fn (SpinLock) destroy #

fn (s &SpinLock) destroy()

destroy frees the resources associated with the spin lock instance.

struct StampedLock #

@[heap]
struct StampedLock {
mut:
	mutex   &Mutex
	cond    &Cond
	readers int
	writing bool
	version u64
}

StampedLock is a lock with optimistic reads: readers can take a stamp and validate it afterwards without holding a read lock, falling back to a regular read lock when validation fails. It is the V-idiomatic equivalent of Java's java.util.concurrent.locks.StampedLock.

fn (StampedLock) try_optimistic_read #

fn (mut l StampedLock) try_optimistic_read() u64

try_optimistic_read returns a nonzero stamp, or zero while a writer is active. It briefly acquires the internal mutex but does not wait for a writer to finish. Optimistic access must use atomic data or other synchronization to avoid data races.

fn (StampedLock) validate #

fn (mut l StampedLock) validate(stamp u64) bool

validate reports whether no write happened since stamp was taken and no write is in progress.

fn (StampedLock) read_lock #

fn (mut l StampedLock) read_lock()

read_lock acquires shared read access, blocking while a writer is active. Any number of readers may hold the lock at once.

fn (StampedLock) read_unlock #

fn (mut l StampedLock) read_unlock()

read_unlock releases one shared read hold.

fn (StampedLock) write_lock #

fn (mut l StampedLock) write_lock()

write_lock acquires exclusive access, blocking while readers hold or another writer is active.

fn (StampedLock) write_unlock #

fn (mut l StampedLock) write_unlock()

write_unlock releases exclusive access, invalidating outstanding optimistic stamps, and wakes waiters.

struct Timer #

struct Timer {
	stop chan chan bool
	done chan bool
pub:
	c chan time.Time
}

Timer sends the current time on c after its duration elapses.

fn (Timer) stop #

fn (timer &Timer) stop() bool

stop prevents the Timer from firing and reports whether it stopped an active timer.

struct WaitGroup #

@[heap]
struct WaitGroup {
mut:
	state u64       // high 32 bits: task count, low 32 bits: wait count
	sem   Semaphore // Blocks wait() until add() drops the task count to zero
}

WaitGroup Do not copy an instance of WaitGroup, use a ref instead.

usage: in main thread: wg := sync.new_waitgroup() wg.add(nr_jobs) before starting jobs with go ... wg.wait() to wait for all jobs to have finished

in each parallel job: wg.done() when finished

[init_with=new_waitgroup] // TODO: implement support for init_with struct attribute, and disallow WaitGroup{} from outside the sync.new_waitgroup() function.

fn (WaitGroup) init #

fn (mut wg WaitGroup) init()

init initializes a WaitGroup.

fn (WaitGroup) add #

fn (mut wg WaitGroup) add(delta int)

add increments (+ve delta) or decrements (-ve delta) task count by delta and unblocks any wait() calls if task count becomes zero. add panics if task count drops below zero.

fn (WaitGroup) done #

fn (mut wg WaitGroup) done()

done is a convenience fn for add(-1).

fn (WaitGroup) wait #

fn (mut wg WaitGroup) wait()

wait blocks until all tasks are done (task count becomes zero).

fn (WaitGroup) go #

fn (mut wg WaitGroup) go(f fn ())

go starts f in a new thread, arranging to call wg.add(1) before that, and wg.done() in the same thread. The function f should not panic. Calls to wg.go() should happen before the call to wg.wait().