oscon2015/3_effects.rst
Yuriy Glukhov b368e91e5d typo
2015-07-20 16:03:59 -07:00

415 lines
7.6 KiB
ReStructuredText

=============
Effect system
=============
NoSideEffect
============
.. code-block:: nim
:number-lines:
cov:
proc toTest(x, y: int): int {.noSideEffect.} =
case x
of 8:
if y > 9: 8+1
else: 8+2
of 9: 9
else: 100
# Error: 'toTest' can have side-effects
NoSideEffect
============
.. code-block:: nim
:number-lines:
var
track = [("line 9", false), ("line 13", false), ...]
proc toTest(x, y: int): int {.noSideEffect.} =
case x
of 8:
if y > 9:
track[0][1] = true
...
NoSideEffect
============
.. code-block:: nim
:number-lines:
var
track = [("line 9", false), ("line 13", false), ...]
proc setter(x: int) =
track[x][1] = true
type HideEffects = proc (x: int) {.noSideEffect, raises: [], tags: [].}
proc toTest(x, y: int): int =
case x
of 8:
if y > 9:
cast[HideEffects](setter)(0)
...
Effect System
=============
- tracks side effects
- tracks exceptions
- tracks "tags": ReadIOEffect, WriteIoEffect, TimeEffect,
ReadDirEffect, **ExecIOEffect**
- tracks locking levels; deadlock prevention at compile-time
..
Think of ``(T, E)`` as opposed to ``E[T]``.
Exceptions
==========
.. code-block:: nim
:number-lines:
import strutils
proc readFromFile() {.raises: [].} =
# read the first two lines of a text file that should contain numbers
# and tries to add them
var
f: File
if open(f, "numbers.txt"):
try:
var a = readLine(f)
var b = readLine(f)
echo("sum: " & $(parseInt(a) + parseInt(b)))
except OverflowError:
echo("overflow!")
except ValueError:
echo("could not convert string to integer")
except IOError:
echo("IO error!")
except:
echo("Unknown exception!")
finally:
close(f)
..
- describe inference algorithm
proc noRaise(x: proc()) {.raises: [].} =
# unknown call that might raise anything, but valid:
x()
proc doRaise() {.raises: [IOError].} =
raise newException(IOError, "IO")
proc use() {.raises: [].} =
# doesn't compile! Can raise IOError!
noRaise(doRaise)
Tags
====
.. code-block:: nim
:number-lines:
type
TagA = object of RootEffect
TagB = object of RootEffect
proc a() {.tags: [TagA].} = discard
proc b() {.tags: [TagB].} = discard
proc x(input: int) {.tags: [ ? ].} =
if input < 0: a()
else: b()
..
Just demonstrate 'doc2' here
Tags
====
.. code-block:: nim
:number-lines:
type
TagA = object of RootEffect
TagB = object of RootEffect
proc a() {.tags: [TagA].} = discard
proc b() {.tags: [TagB].} = discard
proc x(input: int) {.tags: [TagA, TagB].} =
if input < 0: a()
else: b()
Tags
====
.. code-block:: nim
:number-lines:
proc execProcesses(commands: openArray[string],
beforeRunEvent: proc (command: string) = nil): int
{.tags: [ExecIOEffect].}
## executes the commands in parallel. The highest return value of
## all processes is returned. Runs `beforeRunEvent` before running each
## command.
proc echoCommand(command: string) {.tags: [WriteIOEffect].} =
echo command
proc compose*() =
execProcesses(["gcc -o foo foo.c",
"gcc -o bar bar.c",
"gcc -o baz baz.c"],
echoCommand)
GC safety
=========
- a ``spawn``'ed proc must be ``gcsafe``
- ``gcsafe``: Does not access global variables containing GC'ed memory
- ``noSideEffect``: Does not access global variables
- ``noSideEffect`` implies ``gcsafe``
GC safety
=========
.. code-block:: nim
:number-lines:
import tables, strutils, threadpool
const
files = ["data1.txt", "data2.txt", "data3.txt", "data4.txt"]
var tab = newCountTable[string]()
proc countWords(filename: string) =
## Counts all the words in the file.
for word in readFile(filename).split:
tab.inc word
for f in files:
spawn countWords(f)
sync()
tab.sort()
echo tab.largest
GC safety
=========
.. code-block:: nim
:number-lines:
import threadpool, tables, strutils
{.pragma: isolated, threadvar.}
var tab {.isolated.}: CountTable[string]
proc rawPut(key: string) =
inc(tab, key)
proc put(key: string) =
pinnedSpawn 0, rawPut(key)
proc rawGet(): string =
tab.sort()
result = tab.largest()[0]
proc getMax(): string =
let flow = pinnedSpawn(0, rawGet())
result = ^flow
proc main =
pinnedSpawn 0, (proc () = tab = initCountTable[string]())
for x in split(readFile("readme.txt")):
put x
echo getMax()
main()
Guards and locks
================
- common low level concurrency mechanisms like locks, atomic instructions or
condition variables are available
- guards fight data races
- locking levels fight deadlocks
Data race
=========
A data race occurs when:
- two or more threads access the same memory location concurrently
- at least one of the accesses is for writing
- the threads are not using any exclusive locks to control their accesses
Guards fight data races
=======================
- Object fields and global variables can be annotated via a ``guard`` pragma
- Access then has to be within a ``locks`` section:
.. code-block:: nim
:number-lines:
var glock: Lock
var gdata {.guard: glock.}: int
proc invalid =
# invalid: unguarded access:
echo gdata
proc valid =
# valid access:
{.locks: [glock].}:
echo gdata
Guards fight data races
=======================
.. code-block:: nim
:number-lines:
template lock(a: Lock; body: untyped) =
pthread_mutex_lock(a)
{.locks: [a].}:
try:
body
finally:
pthread_mutex_unlock(a)
Guards fight data races
=======================
.. code-block:: nim
:number-lines:
var dummyLock {.compileTime.}: int
var atomicCounter {.guard: dummyLock.}: int
template atomicRead(x): expr =
{.locks: [dummyLock].}:
memoryReadBarrier()
x
echo atomicRead(atomicCounter)
Deadlocks
=========
A deadlock occurs when:
- thread A acquires lock L1
- thread B acquires lock L2
- thread A tries to acquire lock L2
- thread B tries to acquire lock L1
Solution?
Deadlocks
=========
A deadlock occurs when:
- thread A acquires lock L1
- thread B acquires lock L2
- thread A tries to acquire lock L2
- thread B tries to acquire lock L1
Solution?
- enforce L1 is always acquired before L2
Locking levels fight deadlocks
==============================
.. code-block:: nim
:number-lines:
var a, b: Lock[2]
var x: Lock[1]
# invalid locking order: Lock[1] cannot be acquired before Lock[2]:
{.locks: [x].}:
{.locks: [a].}:
...
# valid locking order: Lock[2] acquired before Lock[1]:
{.locks: [a].}:
{.locks: [x].}:
...
# invalid locking order: Lock[2] acquired before Lock[2]:
{.locks: [a].}:
{.locks: [b].}:
...
# valid locking order, locks of the same level acquired at the same time:
{.locks: [a, b].}:
...
Locking levels fight deadlocks
==============================
.. code-block:: nim
:number-lines:
template multilock(a, b: ptr Lock; body: stmt) =
if cast[ByteAddress](a) < cast[ByteAddress](b):
pthread_mutex_lock(a)
pthread_mutex_lock(b)
else:
pthread_mutex_lock(b)
pthread_mutex_lock(a)
{.locks: [a, b].}:
try:
body
finally:
pthread_mutex_unlock(a)
pthread_mutex_unlock(b)
Locking levels fight deadlocks
==============================
.. code-block:: nim
:number-lines:
proc p() {.locks: 3.} = discard
var a: Lock[4]
{.locks: [a].}:
# p's locklevel (3) is strictly less than a's (4) so the call is allowed:
p()