Atomics

V has no special support for atomics, yet, nevertheless it’s possible to treat variables as atomics by calling C functions from V. The standard C11 atomic functions like atomic_store() are usually defined with the help of macros and C compiler magic to provide a kind of overloaded C functions. Since V does not support overloading functions by intention there are wrapper functions defined in C headers named atomic.h that are part of the V compiler infrastructure.

There are dedicated wrappers for all unsigned integer types and for pointers. (byte is not fully supported on Windows) – the function names include the type name as suffix. e.g. C.atomic_load_ptr() or C.atomic_fetch_add_u64().

To use these functions the C header for the used OS has to be included and the functions that are intended to be used have to be declared. Example:

  1. // globals
  2. $if windows {
  3. #include "@VEXEROOT/thirdparty/stdatomic/win/atomic.h"
  4. } $else {
  5. #include "@VEXEROOT/thirdparty/stdatomic/nix/atomic.h"
  6. }
  7. // declare functions we want to use - V does not parse the C header
  8. fn C.atomic_store_u32(&u32, u32)
  9. fn C.atomic_load_u32(&u32) u32
  10. fn C.atomic_compare_exchange_weak_u32(&u32, &u32, u32) bool
  11. fn C.atomic_compare_exchange_strong_u32(&u32, &u32, u32) bool
  12. const num_iterations = 10000000
  13. // see section "Global Variables" below
  14. __global (
  15. atom u32 // ordinary variable but used as atomic
  16. )
  17. fn change() int {
  18. mut races_won_by_change := 0
  19. for {
  20. mut cmp := u32(17) // addressable value to compare with and to store the found value
  21. // atomic version of `if atom == 17 { atom = 23 races_won_by_change++ } else { cmp = atom }`
  22. if C.atomic_compare_exchange_strong_u32(&atom, &cmp, 23) {
  23. races_won_by_change++
  24. } else {
  25. if cmp == 31 {
  26. break
  27. }
  28. cmp = 17 // re-assign because overwritten with value of atom
  29. }
  30. }
  31. return races_won_by_change
  32. }
  33. fn main() {
  34. C.atomic_store_u32(&atom, 17)
  35. t := go change()
  36. mut races_won_by_main := 0
  37. mut cmp17 := u32(17)
  38. mut cmp23 := u32(23)
  39. for i in 0 .. num_iterations {
  40. // atomic version of `if atom == 17 { atom = 23 races_won_by_main++ }`
  41. if C.atomic_compare_exchange_strong_u32(&atom, &cmp17, 23) {
  42. races_won_by_main++
  43. } else {
  44. cmp17 = 17
  45. }
  46. desir := if i == num_iterations - 1 { u32(31) } else { u32(17) }
  47. // atomic version of `for atom != 23 {} atom = desir`
  48. for !C.atomic_compare_exchange_weak_u32(&atom, &cmp23, desir) {
  49. cmp23 = 23
  50. }
  51. }
  52. races_won_by_change := t.wait()
  53. atom_new := C.atomic_load_u32(&atom)
  54. println('atom: $atom_new, #exchanges: ${races_won_by_main + races_won_by_change}')
  55. // prints `atom: 31, #exchanges: 10000000`)
  56. println('races won by\n- `main()`: $races_won_by_main\n- `change()`: $races_won_by_change')
  57. }

In this example both main() and the spawned thread change() try to replace a value of 17 in the global atom with a value of 23. The replacement in the opposite direction is done exactly 10000000 times. The last replacement will be with 31 which makes the spawned thread finish.

It is not predictable how many replacements occur in which thread, but the sum will always be 10000000. (With the non-atomic commands from the comments the value will be higher or the program will hang – dependent on the compiler optimization used.)