diff --git a/examples/thread_safety/.gitignore b/examples/thread_safety/.gitignore new file mode 100644 index 00000000000000..519ddf60e86f6a --- /dev/null +++ b/examples/thread_safety/.gitignore @@ -0,0 +1,2 @@ +*.c +*.out diff --git a/examples/thread_safety/atomic_counter.v b/examples/thread_safety/atomic_counter.v new file mode 100644 index 00000000000000..b6379e2d3bdfda --- /dev/null +++ b/examples/thread_safety/atomic_counter.v @@ -0,0 +1,52 @@ +/* +This code demonstrates thread safety using atomic operations in V. + +Thread safety is achieved by using atomic functions to manipulate the shared counter variable. +Atomic operations ensure that the read-modify-write sequence is performed as a single, indivisible operation, +preventing race conditions and ensuring data integrity when accessed by multiple threads concurrently. + +Key points: +1. **Atomic Fetch and Add**: The `C.atomic_fetch_add_u32` function atomically increments the counter. + This means that the increment operation is performed without interruption, ensuring that no two threads + can increment the counter simultaneously and cause a race condition. + +2. **Atomic Load**: The `C.atomic_load_u32` function atomically reads the value of the counter. + This ensures that the read operation is consistent and not affected by concurrent writes from other threads. + +3. **Thread Synchronization**: The `spawn` function is used to create new threads that run the `increment` function. + The `wait` method is called on each thread to ensure that the main thread waits for both threads to complete + before reading the final value of the counter. + +By using atomic operations and proper thread synchronization, the code ensures that the shared counter is +incremented safely and correctly by multiple threads. +*/ +$if windows { + #include "@VEXEROOT/thirdparty/stdatomic/win/atomic.h" +} $else { + #include "@VEXEROOT/thirdparty/stdatomic/nix/atomic.h" +} + +// Declare the atomic functions +fn C.atomic_fetch_add_u32(&u32, u32) u32 +fn C.atomic_load_u32(&u32) u32 + +// Function to increment the atomic counter +fn increment(atomic_counter &u32) { + C.atomic_fetch_add_u32(atomic_counter, 1) +} + +fn main() { + atomic_counter := u32(0) // Atomic counter variable + + // Spawn two threads that increment the atomic counter + t1 := spawn increment(&atomic_counter) + t2 := spawn increment(&atomic_counter) + + // Wait for both threads to complete + t1.wait() + t2.wait() + + // Load and print the final value of the atomic counter + final_count := C.atomic_load_u32(&atomic_counter) + println('Atomic Counter: ${final_count}') +} diff --git a/examples/thread_safety/concurrent_shared_data.v b/examples/thread_safety/concurrent_shared_data.v new file mode 100644 index 00000000000000..8eef7a750c9f93 --- /dev/null +++ b/examples/thread_safety/concurrent_shared_data.v @@ -0,0 +1,36 @@ +/* +This example demonstrates thread safety using V's concurrency features. +Key points: +- The `SharedData` struct contains a mutable counter that will be accessed by multiple threads. +- The `increment` function increments the counter within a lock to ensure that only one thread +can modify the counter at a time, preventing race conditions. +- In the `main` function, two threads are spawned to increment the shared counter concurrently. +- The `lock` keyword is used to ensure exclusive access to the shared data during modification, +and the `rlock` keyword is used to allow multiple threads to read the data concurrently without +modification. +This ensures that the counter is incremented safely and the final value is printed correctly. +*/ + +struct SharedData { +mut: + counter int +} + +fn increment(shared data SharedData) { + lock data { + data.counter++ + } +} + +fn main() { + shared data := SharedData{} + threads := [spawn increment(shared data), spawn increment(shared data)] + + for t in threads { + t.wait() // Wait for both threads to complete + } + + rlock data { + println('Counter: ${data.counter}') + } +} diff --git a/examples/thread_safety/queue.v b/examples/thread_safety/queue.v new file mode 100644 index 00000000000000..6228ee5562d5d6 --- /dev/null +++ b/examples/thread_safety/queue.v @@ -0,0 +1,49 @@ +/* +This example demonstrates thread safety using channels in V. + +### Functions: +- `producer(ch chan int)`: This function simulates a producer that sends integers from 1 to 99 to +the channel `ch`. It prints each produced item. +- `consumer(ch chan int)`: This function simulates a consumer that receives integers from the +channel `ch`. + +### Thread Safety: +- The use of channels ensures thread safety by providing a synchronized way to communicate between +the producer and consumer threads. +- Channels in V are designed to handle concurrent access, preventing race conditions and ensuring +that data is safely passed between threads. +- The `select` statement in the consumer function allows it to handle timeouts gracefully, +ensuring that the program does not hang if the producer is not ready. +*/ +import time + +fn producer(ch chan int) { + for i in 1 .. 100 { + ch <- i + println('Produced: ${i}') + } +} + +fn consumer(ch chan int) { + for { + select { + item := <-ch { + println('Consumed: ${item}') + } + 500 * time.millisecond { + println('Timeout: No producers were ready within 0.5s') + break + } + } + } +} + +fn main() { + ch := chan int{cap: 10} + + producer_thread := spawn producer(ch) + consumer_thread := spawn consumer(ch) + + producer_thread.wait() + consumer_thread.wait() +} diff --git a/examples/thread_safety/readme.md b/examples/thread_safety/readme.md new file mode 100644 index 00000000000000..9c4e2b4e83ce09 --- /dev/null +++ b/examples/thread_safety/readme.md @@ -0,0 +1,17 @@ +### Run + +```sh +v -prod -autofree ./queue.v -o ./queue.c && \ +gcc ./queue.c -o ./queue.out && \ +./queue.out +``` + +### Valgrind + +```sh +# Helgrind: a tool for detecting synchronisation errors in programs that use the POSIX pthreads threading primitives. +valgrind --tool=helgrind ./queue.out + +# DRD: a tool for detecting errors in multithreaded programs. The tool works for any program that uses the POSIX threading primitives or that uses threading concepts built on top of the POSIX threading primitives. +valgrind --tool=drd ./queue.out +```