-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
examples: add a thread_safety/ folder with several examples of using …
…different thread synchronisation mechanisms (#22561)
- Loading branch information
Showing
5 changed files
with
156 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
*.c | ||
*.out |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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}') | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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}') | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
``` |