summary refs log tree commit diff stats
path: root/doc/manual/threads.txt
diff options
context:
space:
mode:
Diffstat (limited to 'doc/manual/threads.txt')
-rw-r--r--doc/manual/threads.txt221
1 files changed, 0 insertions, 221 deletions
diff --git a/doc/manual/threads.txt b/doc/manual/threads.txt
deleted file mode 100644
index 53071b5a5..000000000
--- a/doc/manual/threads.txt
+++ /dev/null
@@ -1,221 +0,0 @@
-Threads
-=======
-
-To enable thread support the ``--threads:on`` command line switch needs to
-be used. The ``system`` module then contains several threading primitives.
-See the `threads <threads.html>`_ and `channels <channels.html>`_ modules
-for the low level thread API. There are also high level parallelism constructs
-available. See `spawn <#parallel-spawn>`_ for further details.
-
-Nim's memory model for threads is quite different than that of other common
-programming languages (C, Pascal, Java): Each thread has its own (garbage
-collected) heap and sharing of memory is restricted to global variables. This
-helps to prevent race conditions. GC efficiency is improved quite a lot,
-because the GC never has to stop other threads and see what they reference.
-Memory allocation requires no lock at all! This design easily scales to massive
-multicore processors that are becoming the norm.
-
-
-Thread pragma
--------------
-
-A proc that is executed as a new thread of execution should be marked by the
-``thread`` pragma for reasons of readability. The compiler checks for
-violations of the `no heap sharing restriction`:idx:\: This restriction implies
-that it is invalid to construct a data structure that consists of memory
-allocated from different (thread local) heaps.
-
-A thread proc is passed to ``createThread`` or ``spawn`` and invoked
-indirectly; so the ``thread`` pragma implies ``procvar``.
-
-
-GC safety
----------
-
-We call a proc ``p`` `GC safe`:idx: when it doesn't access any global variable
-that contains GC'ed memory (``string``, ``seq``, ``ref`` or a closure) either
-directly or indirectly through a call to a GC unsafe proc.
-
-The `gcsafe`:idx: annotation can be used to mark a proc to be gcsafe,
-otherwise this property is inferred by the compiler. Note that ``noSideEffect``
-implies ``gcsafe``. The only way to create a thread is via ``spawn`` or
-``createThread``. ``spawn`` is usually the preferable method. Either way
-the invoked proc must not use ``var`` parameters nor must any of its parameters
-contain a ``ref`` or ``closure`` type. This enforces
-the *no heap sharing restriction*.
-
-Routines that are imported from C are always assumed to be ``gcsafe``.
-To disable the GC-safety checking the ``--threadAnalysis:off`` command line
-switch can be used. This is a temporary workaround to ease the porting effort
-from old code to the new threading model.
-
-To override the compiler's gcsafety analysis a ``{.gcsafe.}`` pragma block can
-be used:
-
-.. code-block:: nim
-
-  var
-    someGlobal: string = "some string here"
-    perThread {.threadvar.}: string
-
-  proc setPerThread() =
-    {.gcsafe.}:
-      deepCopy(perThread, someGlobal)
-
-
-Future directions:
-
-- A shared GC'ed heap might be provided.
-
-
-Threadvar pragma
-----------------
-
-A global variable can be marked with the ``threadvar`` pragma; it is
-a `thread-local`:idx: variable then:
-
-.. code-block:: nim
-  var checkpoints* {.threadvar.}: seq[string]
-
-Due to implementation restrictions thread local variables cannot be
-initialized within the ``var`` section. (Every thread local variable needs to
-be replicated at thread creation.)
-
-
-Threads and exceptions
-----------------------
-
-The interaction between threads and exceptions is simple: A *handled* exception
-in one thread cannot affect any other thread. However, an *unhandled* exception
-in one thread terminates the whole *process*!
-
-
-
-Parallel & Spawn
-================
-
-Nim has two flavors of parallelism:
-1) `Structured`:idx: parallelism via the ``parallel`` statement.
-2) `Unstructured`:idx: parallelism via the standalone ``spawn`` statement.
-
-Nim has a builtin thread pool that can be used for CPU intensive tasks. For
-IO intensive tasks the ``async`` and ``await`` features should be
-used instead. Both parallel and spawn need the `threadpool <threadpool.html>`_
-module to work.
-
-Somewhat confusingly, ``spawn`` is also used in the ``parallel`` statement
-with slightly different semantics. ``spawn`` always takes a call expression of
-the form ``f(a, ...)``. Let ``T`` be ``f``'s return type. If ``T`` is ``void``
-then ``spawn``'s return type is also ``void`` otherwise it is ``FlowVar[T]``.
-
-Within a ``parallel`` section sometimes the ``FlowVar[T]`` is eliminated
-to ``T``. This happens when ``T`` does not contain any GC'ed memory.
-The compiler can ensure the location in ``location = spawn f(...)`` is not
-read prematurely within a ``parallel`` section and so there is no need for
-the overhead of an indirection via ``FlowVar[T]`` to ensure correctness.
-
-**Note**: Currently exceptions are not propagated between ``spawn``'ed tasks!
-
-
-Spawn statement
----------------
-
-`spawn`:idx: can be used to pass a task to the thread pool:
-
-.. code-block:: nim
-  import threadpool
-
-  proc processLine(line: string) =
-    discard "do some heavy lifting here"
-
-  for x in lines("myinput.txt"):
-    spawn processLine(x)
-  sync()
-
-For reasons of type safety and implementation simplicity the expression
-that ``spawn`` takes is restricted:
-
-* It must be a call expression ``f(a, ...)``.
-* ``f`` must be ``gcsafe``.
-* ``f`` must not have the calling convention ``closure``.
-* ``f``'s parameters may not be of type ``var``.
-  This means one has to use raw ``ptr``'s for data passing reminding the
-  programmer to be careful.
-* ``ref`` parameters are deeply copied which is a subtle semantic change and
-  can cause performance problems but ensures memory safety. This deep copy
-  is performed via ``system.deepCopy`` and so can be overridden.
-* For *safe* data exchange between ``f`` and the caller a global ``TChannel``
-  needs to be used. However, since spawn can return a result, often no further
-  communication is required.
-
-
-``spawn`` executes the passed expression on the thread pool and returns
-a `data flow variable`:idx: ``FlowVar[T]`` that can be read from. The reading
-with the ``^`` operator is **blocking**. However, one can use ``awaitAny`` to
-wait on multiple flow variables at the same time:
-
-.. code-block:: nim
-  import threadpool, ...
-
-  # wait until 2 out of 3 servers received the update:
-  proc main =
-    var responses = newSeq[FlowVarBase](3)
-    for i in 0..2:
-      responses[i] = spawn tellServer(Update, "key", "value")
-    var index = awaitAny(responses)
-    assert index >= 0
-    responses.del(index)
-    discard awaitAny(responses)
-
-Data flow variables ensure that no data races
-are possible. Due to technical limitations not every type ``T`` is possible in
-a data flow variable: ``T`` has to be of the type ``ref``, ``string``, ``seq``
-or of a type that doesn't contain a type that is garbage collected. This
-restriction is not hard to work-around in practice.
-
-
-
-Parallel statement
-------------------
-
-Example:
-
-.. code-block:: nim
-  # Compute PI in an inefficient way
-  import strutils, math, threadpool
-
-  proc term(k: float): float = 4 * math.pow(-1, k) / (2*k + 1)
-
-  proc pi(n: int): float =
-    var ch = newSeq[float](n+1)
-    parallel:
-      for k in 0..ch.high:
-        ch[k] = spawn term(float(k))
-    for k in 0..ch.high:
-      result += ch[k]
-
-  echo formatFloat(pi(5000))
-
-
-The parallel statement is the preferred mechanism to introduce parallelism
-in a Nim program. A subset of the Nim language is valid within a
-``parallel`` section. This subset is checked to be free of data races at
-compile time. A sophisticated `disjoint checker`:idx: ensures that no data
-races are possible even though shared memory is extensively supported!
-
-The subset is in fact the full language with the following
-restrictions / changes:
-
-* ``spawn`` within a ``parallel`` section has special semantics.
-* Every location of the form ``a[i]`` and ``a[i..j]`` and ``dest`` where
-  ``dest`` is part of the pattern ``dest = spawn f(...)`` has to be
-  provably disjoint. This is called the *disjoint check*.
-* Every other complex location ``loc`` that is used in a spawned
-  proc (``spawn f(loc)``) has to be immutable for the duration of
-  the ``parallel`` section. This is called the *immutability check*. Currently
-  it is not specified what exactly "complex location" means. We need to make
-  this an optimization!
-* Every array access has to be provably within bounds. This is called
-  the *bounds check*.
-* Slices are optimized so that no copy is performed. This optimization is not
-  yet performed for ordinary slices outside of a ``parallel`` section.
class="p">(n.sons[0], b, marker) if result == arYes: return for i in countup(1, sonsLen(n) - 1): case n.sons[i].kind of nkOfBranch, nkElse: result = isPartOfAux(lastSon(n.sons[i]), b, marker) if result == arYes: return else: discard "isPartOfAux(record case branch)" of nkSym: result = isPartOfAux(n.sym.typ, b, marker) else: discard proc isPartOfAux(a, b: PType, marker: var IntSet): TAnalysisResult = result = arNo if a == nil or b == nil: return if containsOrIncl(marker, a.id): return if compareTypes(a, b, dcEqIgnoreDistinct): return arYes case a.kind of tyObject: if a.sons[0] != nil: result = isPartOfAux(a.sons[0].skipTypes(skipPtrs), b, marker) if result == arNo: result = isPartOfAux(a.n, b, marker) of tyGenericInst, tyDistinct, tyAlias, tySink: result = isPartOfAux(lastSon(a), b, marker) of tyArray, tySet, tyTuple: for i in countup(0, sonsLen(a) - 1): result = isPartOfAux(a.sons[i], b, marker) if result == arYes: return else: discard proc isPartOf(a, b: PType): TAnalysisResult = ## checks iff 'a' can be part of 'b'. Iterates over VALUE types! var marker = initIntSet() # watch out: parameters reversed because I'm too lazy to change the code... result = isPartOfAux(b, a, marker) proc isPartOf*(a, b: PNode): TAnalysisResult = ## checks if location `a` can be part of location `b`. We treat seqs and ## strings as pointers because the code gen often just passes them as such. ## ## Note: `a` can only be part of `b`, if `a`'s type can be part of `b`'s ## type. Since however type analysis is more expensive, we perform it only ## if necessary. ## ## cases: ## ## YES-cases: ## x <| x # for general trees ## x[] <| x ## x[i] <| x ## x.f <| x ## ## NO-cases: ## x !<| y # depending on type and symbol kind ## x[constA] !<| x[constB] ## x.f !<| x.g ## x.f !<| y.f iff x !<= y ## ## MAYBE-cases: ## ## x[] ?<| y[] iff compatible type ## ## ## x[] ?<| y depending on type ## if a.kind == b.kind: case a.kind of nkSym: const varKinds = {skVar, skTemp, skProc, skFunc} # same symbol: aliasing: if a.sym.id == b.sym.id: result = arYes elif a.sym.kind in varKinds or b.sym.kind in varKinds: # actually, a param could alias a var but we know that cannot happen # here. XXX make this more generic result = arNo else: # use expensive type check: if isPartOf(a.sym.typ, b.sym.typ) != arNo: result = arMaybe of nkBracketExpr: result = isPartOf(a[0], b[0]) if len(a) >= 2 and len(b) >= 2: # array accesses: if result == arYes and isDeepConstExpr(a[1]) and isDeepConstExpr(b[1]): # we know it's the same array and we have 2 constant indexes; # if they are var x = if a[1].kind == nkHiddenStdConv: a[1][1] else: a[1] var y = if b[1].kind == nkHiddenStdConv: b[1][1] else: b[1] if sameValue(x, y): result = arYes else: result = arNo # else: maybe and no are accurate else: # pointer derefs: if result != arYes: if isPartOf(a.typ, b.typ) != arNo: result = arMaybe of nkDotExpr: result = isPartOf(a[0], b[0]) if result != arNo: # if the fields are different, it's not the same location if a[1].sym.id != b[1].sym.id: result = arNo of nkHiddenDeref, nkDerefExpr: result = isPartOf(a[0], b[0]) # weaken because of indirection: if result != arYes: if isPartOf(a.typ, b.typ) != arNo: result = arMaybe of nkHiddenStdConv, nkHiddenSubConv, nkConv: result = isPartOf(a[1], b[1]) of nkObjUpConv, nkObjDownConv, nkCheckedFieldExpr: result = isPartOf(a[0], b[0]) else: discard # Calls return a new location, so a default of ``arNo`` is fine. else: # go down recursively; this is quite demanding: const Ix0Kinds = {nkDotExpr, nkBracketExpr, nkObjUpConv, nkObjDownConv, nkCheckedFieldExpr, nkHiddenAddr} Ix1Kinds = {nkHiddenStdConv, nkHiddenSubConv, nkConv} DerefKinds = {nkHiddenDeref, nkDerefExpr} case b.kind of Ix0Kinds: # a* !<| b.f iff a* !<| b result = isPartOf(a, b[0]) of DerefKinds: # a* !<| b[] iff if isPartOf(a.typ, b.typ) != arNo: result = isPartOf(a, b[0]) if result == arNo: result = arMaybe of Ix1Kinds: # a* !<| T(b) iff a* !<| b result = isPartOf(a, b[1]) of nkSym: # b is an atom, so we have to check a: case a.kind of Ix0Kinds: # a.f !<| b* iff a.f !<| b* result = isPartOf(a[0], b) of Ix1Kinds: result = isPartOf(a[1], b) of DerefKinds: if isPartOf(a.typ, b.typ) != arNo: result = isPartOf(a[0], b) if result == arNo: result = arMaybe else: discard of nkObjConstr: result = arNo for i in 1..<b.len: let res = isPartOf(a, b[i][1]) if res != arNo: result = res if res == arYes: break else: discard