Improved concurrency docs. (#3841)
This commit is contained in:
+62
-3
@@ -10,6 +10,7 @@
|
|||||||
* Object subgraph freezing
|
* Object subgraph freezing
|
||||||
* Object subgraph detachment
|
* Object subgraph detachment
|
||||||
* Raw shared memory using C globals
|
* Raw shared memory using C globals
|
||||||
|
* Atomic primitives and references
|
||||||
* Coroutines for blocking operations (not covered in this document)
|
* Coroutines for blocking operations (not covered in this document)
|
||||||
|
|
||||||
### Workers
|
### Workers
|
||||||
@@ -64,7 +65,9 @@ future.consume {
|
|||||||
### Object transfer and freezing
|
### Object transfer and freezing
|
||||||
|
|
||||||
An important invariant that Kotlin/Native runtime maintains is that the object is either owned by a single
|
An important invariant that Kotlin/Native runtime maintains is that the object is either owned by a single
|
||||||
thread/worker, or it is immutable (_shared XOR mutable_). This ensures that the same data has a single mutator, and so there is no need for locking to exist. To achieve such an invariant, we use the concept of not externally referred object subgraphs.
|
thread/worker, or it is immutable (_shared XOR mutable_). This ensures that the same data has a single mutator,
|
||||||
|
and so there is no need for locking to exist. To achieve such an invariant, we use the concept of not externally
|
||||||
|
referred object subgraphs.
|
||||||
This is a subgraph which has no external references from outside of the subgraph, which could be checked
|
This is a subgraph which has no external references from outside of the subgraph, which could be checked
|
||||||
algorithmically with O(N) complexity (in ARC systems), where N is the number of elements in such a subgraph.
|
algorithmically with O(N) complexity (in ARC systems), where N is the number of elements in such a subgraph.
|
||||||
Such subgraphs are usually produced as a result of a lambda expression, for example some builder, and may not
|
Such subgraphs are usually produced as a result of a lambda expression, for example some builder, and may not
|
||||||
@@ -88,8 +91,34 @@ future.consume {
|
|||||||
a `COpaquePointer` value, which could be stored in `void*` data, so the disconnected object subgraphs
|
a `COpaquePointer` value, which could be stored in `void*` data, so the disconnected object subgraphs
|
||||||
can be stored in a C data structure, and later attached back with `DetachedObjectGraph<T>.attach()` in an arbitrary thread
|
can be stored in a C data structure, and later attached back with `DetachedObjectGraph<T>.attach()` in an arbitrary thread
|
||||||
or a worker. Combining it with [raw memory sharing](#shared) it allows side channel object transfer between
|
or a worker. Combining it with [raw memory sharing](#shared) it allows side channel object transfer between
|
||||||
concurrent threads, if the worker mechanisms are insufficient for a particular task.
|
concurrent threads, if the worker mechanisms are insufficient for a particular task. Note, that object detachment
|
||||||
|
may require explicit leaving function holding object references and then performing cyclic garbage collection.
|
||||||
|
For example, code like:
|
||||||
|
```$kotlin
|
||||||
|
val graph = DetachedObjectGraph {
|
||||||
|
val map = mutableMapOf<String, String>()
|
||||||
|
for (entry in map.entries) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
map
|
||||||
|
}
|
||||||
|
```
|
||||||
|
will not work as expected and will throw runtime exception, as there are uncollected cycles in the detached graph, while:
|
||||||
|
```$kotlin
|
||||||
|
val graph = DetachedObjectGraph {
|
||||||
|
{
|
||||||
|
val map = mutableMapOf<String, String>()
|
||||||
|
for (entry in map.entries) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
map
|
||||||
|
}().also {
|
||||||
|
kotlin.native.internal.GC.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
will work properly, as holding references will be released, and then cyclic garbage affecting reference counter is
|
||||||
|
collected.
|
||||||
|
|
||||||
<a name="shared"></a>
|
<a name="shared"></a>
|
||||||
### Raw shared memory
|
### Raw shared memory
|
||||||
@@ -149,3 +178,33 @@ the following mechanisms to prevent the unintended sharing of state via global o
|
|||||||
* enums are always frozen
|
* enums are always frozen
|
||||||
|
|
||||||
Combined, these mechanisms allow natural race-freeze programming with code reuse across platforms in MPP projects.
|
Combined, these mechanisms allow natural race-freeze programming with code reuse across platforms in MPP projects.
|
||||||
|
|
||||||
|
<a name="atomic_references"></a>
|
||||||
|
### Atomic primitives and references
|
||||||
|
|
||||||
|
Kotlin/Native standard library provides primitives for safe working with concurrently mutable data, namely
|
||||||
|
`AtomicInt`, `AtomicLong`, `AtomicNativePtr`, `AtomicReference` and `FreezableAtomicReference` in the package
|
||||||
|
`kotlin.native.concurrent`.
|
||||||
|
Atomic primitives allows concurrency-safe update operations, such as increment, decrement and compare-and-swap,
|
||||||
|
along with value setters and getters. Atomic primitives are considered always frozen by the runtime, and
|
||||||
|
while their fields can be updated with the regular `field.value += 1`, it is not concurrency safe.
|
||||||
|
Value must be be changed using dedicated operations, so it is possible to perform concurrent-safe
|
||||||
|
global counters and similar data structures.
|
||||||
|
|
||||||
|
Some algorithms require shared mutable references across the multiple workers, for example global mutable
|
||||||
|
configuration could be implemented as an immutable instance of properties list atomically replaced with the
|
||||||
|
new version on configuration update as the whole in a single transaction. This way no inconsistent configuration
|
||||||
|
could be seen, and at the same time configuration could be updated as needed.
|
||||||
|
To achieve such functionality Kotlin/Native runtime provides two related classes:
|
||||||
|
`kotlin.native.concurrent.AtomicReference` and `kotlin.native.concurrent.FreezableAtomicReference`.
|
||||||
|
Atomic reference holds reference to a frozen or immutable object, and its value could be updated by set
|
||||||
|
or compare-and-swap operation. Thus, dedicated set of objects could be used to create mutable shared object graphs
|
||||||
|
(of immutable objects). Cycles in the shared memory could be created using atomic references, and to collect them
|
||||||
|
Kotlin/Native runtime has special concurrent cycle collector, concurrently analyzing cyclic data rooted in atomic references.
|
||||||
|
When cycle of no longer used objects is detected, collector zeroes out reference stored in atomic reference and thus
|
||||||
|
allows cycle to be collected.
|
||||||
|
|
||||||
|
If atomic reference value is attempted to be set to non-frozen value runtime exception is thrown.
|
||||||
|
|
||||||
|
Freezable atomic reference is similar to the regular atomic reference, but until frozen behaves like regular box
|
||||||
|
for a reference. After freezing it behaves like an atomic reference, and can only hold a reference to a frozen object.
|
||||||
Reference in New Issue
Block a user