aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/old/doc/upcoming.md
blob: 45a1d98e27ae94d11fbe8bafda2f942a5428e150 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# V Work In Progress

***This document describes features that are not implemented, yet.
Please refer to [docs.md](https://github.com/vlang/v/blob/master/doc/docs.md)
for the current state of V***

## Table of Contents

* [Concurrency](#concurrency)
    * [Variable Declarations](#variable-declarations)
	* [Strengths](#strengths)
	* [Weaknesses](#weaknesses)
	* [Compatibility](#compatibility)
	* [Automatic Lock](#automatic-lock)
	* [Channels](#channels)

## Concurrency

### Variable Declarations

Objects that are supposed to be used to exchange data between
coroutines have to be declared with special care. Exactly one of the following
4 kinds of declaration has to be chosen:

```v ignore
a := ...
mut b := ...
shared c := ...
atomic d := ...
```

- `a` is declared as *constant* that can be passed to
  other coroutines and read without limitations. However
  it cannot be changed.
- `b` can be accessed reading and writing but only from one
  coroutine. That coroutine *owns* the object. A `mut` variable can
  be passed to another coroutine (as receiver or function argument in
  the `go` statement or via a channel) but then ownership is passed,
  too, and only the other coroutine can access the object.<sup>1</sup>
- `c` can be passed to coroutines an accessed
  *concurrently*.<sup>2</sup> In order to avoid data races it has to
  be locked before access can occur and unlocked to allow access to
  other coroutines. This is done by one the following block structures:
  ```v ignore
  lock c {
      // read, modify, write c
      ...
  }
  ```
  
  ```v ignore
  rlock c {
      // read c
      ...
  }
  ```
  Several variables may be specified: `lock x, y, z { ... }`.
  They are unlocked in the opposite order.
- `d` can be passed to coroutines and accessed *concurrently*,
  too.<sup>3</sup> No lock is needed in this case, however
  `atomic` variables can only be 32/64 bit integers (or pointers)
  and access is limited to a small set of predefined idioms that have
  native hardware support.

To help making the correct decision the following table summarizes the
different capabilities:

|                           | *default* | `mut` | `shared` | `atomic` |
| :---                      |   :---:   | :---: |  :---:   |  :---:   |
| write access              |           |   +   |     +    |    +     |
| concurrent access         |     +     |       |     +    |    +     |
| performance               |    ++     |  ++   |          |    +     |
| sophisticated operations  |     +     |   +   |     +    |          |
| structured data types     |     +     |   +   |     +    |          |

### Strengths
**default**
- very fast
- unlimited access from different coroutines
- easy to handle

**`mut`**
- very fast
- easy to handle

**`shared`**
- concurrent access from different coroutines
- data type may be complex structure
- sophisticated access possible (several statements within one `lock`
  block)

**`atomic`**
- concurrent access from different coroutines
- reasonably fast

### Weaknesses
**default**
- read only

**`mut`**
- access only from one coroutine at a time

**`shared`**
- lock/unlock are slow
- moderately difficult to handle (needs `lock` block)

**`atomic`**
- limited to single (max. 64 bit) integers (and pointers)
- only a small set of predefined operations possible
- very difficult to handle correctly

<sup>1</sup> The owning coroutine will also free the memory space used
for the object when it is no longer needed.  
<sup>2</sup> For `shared` objects the compiler adds code for reference
counting. Once the counter reaches 0 the object is automatically freed.  
<sup>3</sup> Since an `atomic` variable is only a few bytes in size
allocation would be an unnecessary overhead. Instead the compiler
creates a global.

### Compatibility
Outside of `lock`/`rlock` blocks function arguments must in general
match - with the familiar exception that objects declared `mut` can be
used to call functions expecting immutable arguments:

```v ignore
fn f(x St) {...}
fn g(mut x St) {...}
fn h(shared x St) {...}
fn i(atomic x u64) {...}

a := St{...}
f(a)

mut b := St{...}
f(b)
go g(mut b)
// `b` should not be accessed here any more

shared c := St{...}
h(shared c)

atomic d &u64
i(atomic d)
```

Inside a `lock c {...}` block `c` behaves like a `mut`,
inside an `rlock c {...}` block like an immutable:
```v ignore
shared c := St{...}
lock c {
    g(mut c)
    f(c)
    // call to h() not allowed inside `lock` block
    // since h() will lock `c` itself
}
rlock c {
    f(c)
    // call to g() or h() not allowed
}
```

### Automatic Lock
In general the compiler will generate an error message when a `shared`
object is accessed outside of any corresponding `lock`/`rlock`
block. However in simple and obvious cases the necessary lock/unlock
can be generated automatically for `array`/`map` operations:

```v ignore
shared a := []int{cap: 5}
go h2(shared a)
a << 3
// keep in mind that `h2()` could change `a` between these statements
a << 4
x := a[1] // not necessarily `4`

shared b := map[string]int{}
go h3(shared b)
b['apple'] = 3
c['plume'] = 7
y := b['apple'] // not necesarily `3`

// iteration over elements
for k, v in b {
    // concurrently changed k/v pairs may or my not be included
}
```

This is handy, but since other coroutines might access the `array`/`map`
concurrently between the automatically locked statements, the results
are sometimes surprising. Each statement should be seen as a single
transaction that is unrelated to the previous or following
statement. Therefore - but also for performance reasons - it's often
better to group consecutive coherent statements in an explicit `lock` block.

### Channels