Skip to content

Commit

Permalink
atomics documentation and fixups (#111)
Browse files Browse the repository at this point in the history
  • Loading branch information
ethindp authored Oct 1, 2024
1 parent a9772c5 commit 9c6580d
Show file tree
Hide file tree
Showing 37 changed files with 546 additions and 18 deletions.
1 change: 1 addition & 0 deletions doc/src/references/builtin/Concurrency/!Concurrency.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ This section contains the documentation for all mechanisms revolving around seve
A warning that delving into this section will expose you to some rather low level concepts, the misapplication of which could result in your program crashing or acting oddly without the usual helpful error information provided by NVGT.

The highest level and most easily invoked method for multithreading in nvgt is the async template class, allowing you to call any script or system function on another thread and retrieve it's return value later.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# atomic_t

The base class for all atomic types that NVGT has to offer.

## Remarks:

An atomic type SHALL be defined as a data type for which operations must be performed atomically, ensuring that modifications are indivisible and uninterrupted within a concurrent execution environment. An atomic operation MUST either fully succeed or completely fail, with no possibility of intermediate states or partial completion.

When one thread writes to an atomic object and another thread reads from the same atomic object, the behavior MUST be well-defined and SHALL NOT result in a data race.

Operations on atomic objects MAY establish inter-thread synchronization and MUST order non-atomic memory operations as specified by the `memory_order` parameter. Memory effects MUST be propagated and observed according to the constraints of the specified memory ordering.

Within this documentation, the `atomic_T` class is a placeholder class for any atomic type. Specifically, `T` may be any primitive type except void, but MAY NOT be any complex type such as string. For example, `atomic_bool` is an actual class, where `bool` replaces `T`. Additionally, an `atomic_flag` class exists which does not offer load or store operations but is the most efficient implementation of boolean-based atomic objects, and has separate documentation from all other atomic types.

Please note: atomic floating-point types are not yet implemented, though they will be coming in a future release. However, until then, attempts to instantiate an atomic floating-point type will behave as though the class in question did not exist. This notice will be removed once atomic floating-point types have been implemented.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# compare_exchange_strong
Atomically compares the value representation of this atomic object with that of `expected`. If both are bitwise-equal, performs an atomic read-modify-write operation on this atomic object with `desired` (that is, replaces the current value of this atomic object with `desired`); otherwise, performs an atomic load of this atomic object and places it's actual value into `expected`. If failure is either `MEMORY_ORDER_RELEASE` or `MEMORY_ORDER_ACQ_REL`, the behavior is undefined.

1: `bool compare_exchange_strong(T& expected, T desired, memory_order success, memory_order failure);`
2: `bool compare_exchange_strong(T& expected, T desired, memory_order order = MEMORY_ORDER_SEQ_CST);`

## Parameters (1):
* `T& expected`: reference to the value expected to be found in this atomic object.
* `T desired`: the value that SHALL replace the one in this atomic object if and only if it is bitwise-equal to `expected`.
* `memory_order success`: the memory synchronization ordering that SHALL be used for the read-modify-write operation if the comparison succeeds.
* `memory_order failure`: the memory synchronization ordering that SHALL be used for the load operation if the comparison fails.
* `memory_order order`: the memory synchronization order that SHALL be used for both the read-modify-write operation and the load operation depending on whether the comparison succeeds or fails.

## Returns:
`true` if the atomic value was successfully changed, false otherwise.

## Remarks:
This function is available on all atomic types.

Within the above function signatures, `T` is used as a placeholder for the actual type. For example, if this object is an `atomic_int`, then `T` SHALL be `int`.

The operations of comparison and copying SHALL be executed in a bitwise manner. Consequently, no invocation of a constructor, assignment operator, or similar function SHALL occur, nor SHALL any comparison operators be utilized during these operations.

In contrast to the `compare_exchange_weak` function, this function SHALL NOT spuriously fail.

In scenarios where the use of the `compare_exchange_weak` function would necessitate iteration, whereas this function would not, the latter SHALL be considered preferable. Exceptions to this preference exist in cases where the object representation of type T might encompass trap bits or offer multiple representations for the same value, such as floating-point NaNs. Under these circumstances, `compare_exchange_weak` generally proves effective, as it tends to rapidly converge upon a stable object representation.
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# compare_exchange_weak:
Atomically compares the value representation of this atomic object with that of `expected`. If both are bitwise-equal, performs an atomic read-modify-write operation on this atomic object with `desired` (that is, replaces the current value of this atomic object with `desired`); otherwise, performs an atomic load of this atomic object and places it's actual value into `expected`. If failure is either `MEMORY_ORDER_RELEASE` or `MEMORY_ORDER_ACQ_REL`, the behavior is undefined.

```nvgt
bool compare_exchange_weak(T& expected, T desired, memory_order success, memory_order failure);
bool compare_exchange_weak(T& expected, T desired, memory_order order = MEMORY_ORDER_SEQ_CST);
```

## Parameters:
* `T& expected`: reference to the value expected to be found in this atomic object.
* `T desired`: the value that SHALL replace the one in this atomic object if and only if it is bitwise-equal to `expected`.
* `memory_order success`: the memory synchronization ordering that SHALL be used for the read-modify-write operation if the comparison succeeds.
* `memory_order failure`: the memory synchronization ordering that SHALL be used for the load operation if the comparison fails.
* `memory_order order`: the memory synchronization order that SHALL be used for both the read-modify-write operation and the load operation depending on whether the comparison succeeds or fails.

## Returns:
bool: `true` if the atomic value was successfully changed, false otherwise.

## Remarks:
This function is available on all atomic types.

Within the above function signatures, `T` is used as a placeholder for the actual type. For example, if this object is an `atomic_int`, then `T` SHALL be `int`.

The operations of comparison and copying SHALL be executed in a bitwise manner. Consequently, no invocation of a constructor, assignment operator, or similar function SHALL occur, nor SHALL any comparison operators be utilized during these operations.

In contrast to the `compare_exchange_strong` function, this function MAY fail spuriously. Specifically, even in instances where the value contained within this atomic object is equivalent to the expected value, the function is permitted to act as if the values are not equal. This characteristic allows the function to provide enhanced performance on certain platforms, particularly when employed within iterative loops.

In scenarios where the use of this function would necessitate iteration, whereas `compare_exchange_strong` would not, the latter SHALL be considered preferable. Exceptions to this preference exist in cases where the object representation of type T might encompass trap bits or offer multiple representations for the same value, such as floating-point NaNs. Under these circumstances, this function generally proves effective, as it tends to rapidly converge upon a stable object representation.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# exchange
Atomically replaces the value of this object with `desired` in such a way that the operation is a read-modify-write operation, then returns the prior value of this object. Memory is affected according to `order`.

```nvgt
T exchange(T desired, memory_order order = MEMORY_ORDER_SEQ_CST);
```

## Parameters:
* `T desired`: the value to exchange with the prior value.
* `memory_order order`: the memory ordering constraints to enforce.

## Returns
T: The prior value held within this atomic object before this function was called.

## Remarks:
This function is available on all atomic types.

Within the above function signature, `T` is used as a placeholder for the actual type. For example, if this object is an `atomic_int`, then `T` SHALL be `int`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# fetch_add
Atomically replaces the current value with the result of arithmetic addition of the value and `arg`. That is, it performs atomic post-increment. The operation is a read-modify-write operation. Memory is affected according to the value of `order`.

```nvgt
T fetch_add(T arg, memory_order order = MEMORY_ORDER_SEQ_CST);
```

## Parameters:
* `T arg`: the value to add to this atomic object.
* `memory_order order`: which memory order SHALL govern this operation.

## Returns:
T: The prior value of this atomic object.

## Remarks:
This function is only available on integral and floating-point atomic types.

Within the above function signature, `T` is used as a placeholder for the actual type. For example, if this object is an `atomic_int`, then `T` SHALL be `int`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# fetch_and
Atomically replaces the current value with the result of bitwise ANDing the value of this atomic object and `arg`. The operation is a read-modify-write operation. Memory is affected according to the value of `order`.

```nvgt
T fetch_and(T arg, memory_order order = MEMORY_ORDER_SEQ_CST);
```

## Parameters:
* `T arg`: the right-hand side of the bitwise AND operation.
* `memory_order order`: which memory order SHALL govern this operation.

## Returns:
The prior value of this atomic object.

## Remarks:
This function is only available on integral atomic types.

Within the above function signature, `T` is used as a placeholder for the actual type. For example, if this object is an `atomic_int`, then `T` SHALL be `int`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# fetch_or
Atomically replaces the current value with the result of bitwise ORing the value of this atomic object and `arg`. The operation is a read-modify-write operation. Memory is affected according to the value of `order`.

```nvgt
T fetch_or(T arg, memory_order order = MEMORY_ORDER_SEQ_CST);
```

## Parameters
* `T arg`: the right-hand side of the bitwise OR operation.
* `memory_order order`: which memory order SHALL govern this operation.

## Returns:
T: The prior value of this atomic object.

## Remarks:
This function is only available on integral atomic types.

Within the above function signature, `T` is used as a placeholder for the actual type. For example, if this object is an `atomic_int`, then `T` SHALL be `int`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# fetch_sub
Atomically replaces the current value with the result of arithmetic subtraction of the value and `arg`. That is, it performs atomic post-decrement. The operation is a read-modify-write operation. Memory is affected according to the value of `order`.

```nvgt
T fetch_sub(T arg, memory_order order = MEMORY_ORDER_SEQ_CST);
```

## Parameters:
* `T arg`: the value to subtract from this atomic object.
* `memory_order order`: which memory order SHALL govern this operation.

## Returns:
T: The prior value of this atomic object.

## Remarks:
This function is only available on integral and floating-point atomic types.

Within the above function signature, `T` is used as a placeholder for the actual type. For example, if this object is an `atomic_int`, then `T` SHALL be `int`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# fetch_xor
Atomically replaces the current value with the result of bitwise XORing the value of this atomic object and `arg`. The operation is a read-modify-write operation. Memory is affected according to the value of `order`.

```nvgt
T fetch_xor(T arg, memory_order order = MEMORY_ORDER_SEQ_CST);
```

## Parameters:
* `T arg`: the right-hand side of the bitwise XOR operation.
* `memory_order order`: which memory order SHALL govern this operation.

## Returns:
T: The prior value of this atomic object.

## Remarks:
This function is only available on integral atomic types.

Within the above function signature, `T` is used as a placeholder for the actual type. For example, if this object is an `atomic_int`, then `T` SHALL be `int`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# is_lock_free
Checks whether the atomic operations on all objects of this type are lock-free.

``nvgt
bool is_lock_free();
```
## Returns:
`bool: true` if the atomic operations on the objects of this type are lock-free, `false` otherwise.
## Remarks:
This function is available on all atomic types.
All atomic types, with the exception of `atomic_flag`, MAY be implemented utilizing mutexes or alternative locking mechanisms as opposed to employing lock-free atomic instructions provided by the CPU. This allows for the implementation flexibility where atomicity is achieved through synchronization primitives rather than hardware-based atomic instructions.
Atomic types MAY exhibit lock-free behavior under certain conditions. For instance, SHOULD a particular architecture support naturally atomic operations exclusively for aligned memory accesses, then any misaligned instances of the same atomic type MAY necessitate the use of locks to ensure atomicity.
While it is recommended, it is NOT a mandatory requirement that lock-free atomic operations be address-free. Address-free operations are those that are suitable for inter-process communication via shared memory, facilitating seamless data exchange without reliance on specific memory addresses.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# load
Atomically loads and returns the current value of the atomic variable. Memory is affected according to the value of `order`. If order is either `MEMORY_ORDER_RELEASE` or `MEMORY_ORDER_ACQ_REL`, the behavior is undefined.

```nvgt
T load(memory_order order = MEMORY_ORDER_SEQ_CST);
```

## Parameters:
* `memory_order order`: which memory order to enforce when performing this operation.

## Returns:
T: The value of this atomic object.

## Remarks:
This function is available on all atomic types.

Within the above function signature, `T` is used as a placeholder for the actual type. For example, if this object is an `atomic_int`, then `T` SHALL be `int`.

This operation is identical to using the IMPLICIT CONVERSION OPERATOR, except that it allows for the specification of a memory order when performing this operation. When using the IMPLICIT CONVERSION OPERATOR, the memory order SHALL be `MEMORY_ORDER_SEQ_CST`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# notify_all
Unblocks all threads blocked in atomic waiting operations (i.e., `wait()`) on this atomic object if there are any; otherwise does nothing.

```nvgt
void notify_all();
```

## Remarks:
This function is available on all atomic types.

This form of change detection is often more efficient than pure spinlocks or polling and should be preferred whenever possible.

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# notify_one
Unblocks at least one thread blocked in atomic waiting operations (i.e., `wait()`) on this atomic object if there is one; otherwise does nothing.

```nvgt
void notify_one();
```

## Remarks:
This function is available on all atomic types.

This form of change detection is often more efficient than pure spinlocks or polling and should be preferred whenever possible.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# store
Atomically replaces the current value with `desired`. Memory is affected according to the value of `order`. If order is either MEMORY_ORDER_ACQUIRE or MEMORY_ORDER_ACQ_REL, the behavior is undefined.

```nvgt
void store(T desired, memory_order order = MEMORY_ORDER_SEQ_CST);
```

## Parameters:
* `T desired`: the value that should be stored into this atomic object.
* `memory_order order`: which memory ordering constraints should be enforced during this operation.

## Remarks:
This function is available on all atomic types.

Within the above function signature, `T` is used as a placeholder for the actual type. For example, if this object is an `atomic_int`, then `T` SHALL be `int`.

This operation is identical to using the assignment operator, except that it allows for the specification of a memory order when performing this operation. When using the assignment operator, the memory order SHALL be `MEMORY_ORDER_SEQ_CST`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# wait
Atomically waits until the value of this atomic object has changed. If order is either `MEMORY_ORDER_RELEASE` or `MEMORY_ORDER_ACQ_REL`, the behavior is undefined.

```nvgt
void wait(T old, memory_order order = MEMORY_ORDER_SEQ_CST);
```

## Parameters:
* `T old`: The old (current) value of this atomic object as of the time of this call. This function will wait until this atomic object no longer contains this value.
* `memory_order order`: memory order constraints to enforce.

## Remarks:
This function is available on all atomic types.

Within the above function signature, `T` is used as a placeholder for the actual type. For example, if this object is an `atomic_int`, then `T` SHALL be `int`.

This function is guaranteed to return only when the value has changed, even if the underlying implementation unblocks spuriously.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# opAddAssign
Atomically replaces the current value with the result of computation involving the previous value and `arg`. The operation is a read-modify-write operation. Specifically, performs atomic addition. Equivalent to `return fetch_add(arg) + arg;`.

```nvgt
T opAddAssign( T arg );
```

## Returns:
T: The resulting value (that is, the result of applying the corresponding binary operator to the value immediately preceding the effects of the corresponding member function in the modification order of this atomic object).

## Remarks:
This operator is only available on integral and floating-point atomic types.

Within the above function signature, `T` is used as a placeholder for the actual type. For example, if this object is an `atomic_int`, then `T` SHALL be `int`.

Unlike most compound assignment operators, the compound assignment operators for atomic types do not return a reference to their left-hand arguments. They return a copy of the stored value instead.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# opAndAssign
Atomically replaces the current value with the result of computation involving the previous value and `arg`. The operation is a read-modify-write operation. Specifically, performs atomic bitwise AND. Equivalent to `return fetch_and(arg) & arg;`.

```nvgt
T opAndAssign(T arg);
```

## Returns:
The resulting value of this computation.

## Remarks:
This operator is only available on integral atomic types.

Within the above function signature, `T` is used as a placeholder for the actual type. For example, if this object is an `atomic_int`, then `T` SHALL be `int`.

Unlike most compound assignment operators, the compound assignment operators for atomic types do not return a reference to their left-hand arguments. They return a copy of the stored value instead.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# opAssign
Atomically assigns desired to the atomic variable. Equivalent to `store(desired)`.

```nvgt
T opAssign(T desired);
```

## Remarks:
This operator is available on all atomic types.

Within the above function signature, `T` is used as a placeholder for the actual type. For example, if this object is an `atomic_int`, then `T` SHALL be `int`.

Unlike most assignment operators, the assignment operators for atomic types do not return a reference to their left-hand arguments. They return a copy of the stored value instead.
Loading

0 comments on commit 9c6580d

Please sign in to comment.