From 78aac03c10fb98b034e1ba74098bdc64a07b4c78 Mon Sep 17 00:00:00 2001
From: Watson David <30525704+B1ggDave@users.noreply.github.com>
Date: Mon, 10 Sep 2018 17:19:18 +0200
Subject: [PATCH] proofread Watson (#2019)
---
CONCURRENCY.md | 87 +++++++++++++++++++++++++-------------------------
1 file changed, 43 insertions(+), 44 deletions(-)
diff --git a/CONCURRENCY.md b/CONCURRENCY.md
index 0a716fbf89e..d4c5c804388 100644
--- a/CONCURRENCY.md
+++ b/CONCURRENCY.md
@@ -2,9 +2,9 @@
Kotlin/Native runtime doesn't encourage a classical thread-oriented concurrency
model with mutually exclusive code blocks and conditional variables, as this model is
- known to be error-prone and unreliable. Instead, we suggest collection of
- alternative approaches, allowing to use hardware concurrency and implement blocking IO.
- Those approaches are as following, and will be elaborated in further sections:
+ known to be error-prone and unreliable. Instead, we suggest a collection of
+ alternative approaches, allowing you to use hardware concurrency and implement blocking IO.
+ Those approaches are as follows, and they will be elaborated on in further sections:
* Workers with message passing
* Object subgraph ownership transfer
* Object subgraph freezing
@@ -14,15 +14,15 @@
## Workers
- Instead of threads Kotlin/Native runtime offers concept of workers: concurrently executing
- control flow streams with an associated request queue. Workers are very similar to actors
- in the Actor Model. Worker can exchange Kotlin objects with other workers, so that at the moment
- each mutable object is owned by the single worker, but ownership could be transferred.
+ Instead of threads Kotlin/Native runtime offers the concept of workers: concurrently executed
+ control flow streams with an associated request queue. Workers are very similar to the actors
+ in the Actor Model. A worker can exchange Kotlin objects with another worker, so that at any moment
+ each mutable object is owned by a single worker, but ownership can be transferred.
See section [Object transfer and freezing](#transfer).
- Once worker is started with `Worker.start` function call, it can be uniquely addressed with an integer
- worker id. Other workers, or non-worker concurrency primitives, such as OS threads, could send a message
- to the worker with `execute` call.
+ Once a worker is started with the `Worker.start` function call, it can be addressed with its own unique integer
+ worker id. Other workers, or non-worker concurrency primitives, such as OS threads, can send a message
+ to the worker with the `execute` call.
```kotlin
val future = execute(TransferMode.SAFE, { SomeDataForWorker() }) {
// data returned by the second function argument comes to the
@@ -39,58 +39,57 @@
result -> println("result is $result")
}
```
- The call to `execute` uses function passed as its second parameter to produce an object subgraph
- (i.e. set of mutually referring objects) which is passed as the whole to that worker, and no longer
+ The call to `execute` uses a function passed as its second parameter to produce an object subgraph
+ (i.e. set of mutually referring objects) which is then passed as a whole to that worker, it is then no longer
available to the thread that initiated the request. This property is checked if the first parameter
- is `TransferMode.SAFE` by graph traversal and just assumed to be true, if it is `TransferMode.UNCHECKED`.
- Last parameter to `execute` is a special Kotlin lambda, which is not allowed to capture any state,
- and is actually invoked in target worker's context. Once processed, result is transferred to whoever consumes
- the future, and is attached to object graph of that worker/thread.
+ is `TransferMode.SAFE` by graph traversal and is just assumed to be true, if it is `TransferMode.UNSAFE`.
+ The last parameter to `execute` is a special Kotlin lambda, which is not allowed to capture any state,
+ and is actually invoked in the target worker's context. Once processed, the result is transferred to whatever consumes
+ it in the future, and it is attached to the object graph of that worker/thread.
If an object is transferred in `UNSAFE` mode and is still accessible from multiple concurrent executors,
program will likely crash unexpectedly, so consider that last resort in optimizing, not a general purpose
mechanism.
- For more complete example please refer to the [workers example](https://github.com/JetBrains/kotlin-native/tree/master/samples/workers)
+ For a more complete example please refer to the [workers example](https://github.com/JetBrains/kotlin-native/tree/master/samples/workers)
in the Kotlin/Native repository.
## Object transfer and freezing
- Important invariant that Kotlin/Native runtime maintains is that object is either owned by a single
- thread/worker, or is immutable (_shared XOR mutable_). This ensures that the same data has a single mutator, and so no need for
- locking exists. To achieve such an invariant, we use concept of not externally referred object subgraphs.
- This is a subgraph which has no external references from outside of the subgraph, what could be checked
- algorithmically with O(N) complexity (in ARC systems), where N is number of elements in such a subgraph.
- Such subgraphs are usually produced as a result of lambda expression, for example some builder, and may not
- contain objects, referred externally.
-
- Freezing is a runtime operation making given object subgraph immutable, by modifying the object header
- so that future mutation attempts lead to throwing an `InvalidMutabilityException`. It is deep, so
- if an object has a pointer to another objects - transitive closure of such objects will be frozen.
- Freezing is the one way transformation, frozen objects cannot be unfrozen. Frozen objects have a nice
+ 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.
+ 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.
+ Such subgraphs are usually produced as a result of a lambda expression, for example some builder, and may not
+ contain objects, referred to externally.
+
+ Freezing is a runtime operation making a given object subgraph immutable, by modifying the object header
+ so that future mutation attempts throw an `InvalidMutabilityException`. It is deep, so
+ if an object has a pointer to other objects - transitive closure of such objects will be frozen.
+ Freezing is a one way transformation, frozen objects cannot be unfrozen. Frozen objects have a nice
property that due to their immutability, they can be freely shared between multiple workers/threads
- not breaking the "mutable XOR shared" invariant.
+ without breaking the "mutable XOR shared" invariant.
- If object is frozen could be checked with an extension property `isFrozen`, and if it is, object sharing
- is allowed. Currently, Kotlin/Native runtime only freezes enum objects after creation, although additional
+ If an object is frozen it can be checked with an extension property `isFrozen`, and if it is, object sharing
+ is allowed. Currently, Kotlin/Native runtime only freezes the enum objects after creation, although additional
autofreezing of certain provably immutable objects could be implemented in the future.
## Object subgraph detachment
- Object subgraph without external references could be disconnected using `detachObjectGraph` to
- a `COpaquePointer` value, which could be stored in `void*` data, so disconnected object subgraphs
- could be stored in C data structure, and later attached back with `attachObjectGraph` in arbitrary thread
- or worker. Combined with [raw memory sharing](#shared) it allows side channel object transfer between
- concurrent threads, if worker mechanisms are insufficient for the particular task.
+ An object subgraph without external references can be disconnected using `detachObjectGraph` to
+ 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 `attachObjectGraph` in an arbitrary thread
+ 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.
## Raw shared memory
- Considering strong ties of Kotlin/Native with C via interoperability, in conjunction with other mechanisms
- mentioned above one could build popular data structures, like concurrent hashmap or shared cache with
- Kotlin/Native. One could rely upon shared C data, and store in it references to detached object subgraphs.
+ Considering the strong ties between Kotlin/Native and C via interoperability, in conjunction with the other mechanisms
+ mentioned above it is possible to build popular data structures, like concurrent hashmap or shared cache with
+ Kotlin/Native. It is possible to rely upon shared C data, and store in it references to detached object subgraphs.
Consider the following .def file:
```
package = global
@@ -103,7 +102,7 @@ typedef struct {
SharedData sharedData;
```
-After running cinterop tool it allows sharing Kotlin data in versionized global structure,
+After running the cinterop tool it can share Kotlin data in a versionized global structure,
and interact with it from Kotlin transparently via autogenerated Kotlin like this:
```kotlin
class SharedData(rawPtr: NativePtr) : CStructVar(rawPtr) {
@@ -111,14 +110,14 @@ class SharedData(rawPtr: NativePtr) : CStructVar(rawPtr) {
var kotlinObject: COpaquePointer?
}
```
-So combined with the top level variable declared above, it allows seeing the same memory from different
+So in combination with the top level variable declared above, it can allow looking at the same memory from different
threads and building traditional concurrent structures with platform-specific synchronization primitives.
## Global variables and singletons
Frequently, global variables are a source of unintended concurrency issues, so _Kotlin/Native_ implements
-following mechanisms to prevent unintended sharing of state via global objects:
+the following mechanisms to prevent the unintended sharing of state via global objects:
* global variables, unless specially marked, can be only accessed from the main thread (that is, the thread
_Kotlin/Native_ runtime was first initialized), if other thread access such a global, `IncorrectDereferenceException` is thrown
@@ -130,4 +129,4 @@ following mechanisms to prevent unintended sharing of state via global objects:
unless cyclic frozen structures were attempted to be created
* enums are always frozen
- Combined, those mechanisms allows natural race-freeze programming with code reuse across platforms in MPP projects.
\ No newline at end of file
+ Combined, these mechanisms allow natural race-freeze programming with code reuse across platforms in MPP projects.