doc: update channel documentation (#6432)
parent
d7fee91655
commit
3454677eb9
114
doc/docs.md
114
doc/docs.md
|
@ -63,6 +63,7 @@ you can do in V.
|
||||||
* [Option/Result types & error handling](#optionresult-types-and-error-handling)
|
* [Option/Result types & error handling](#optionresult-types-and-error-handling)
|
||||||
* [Generics](#generics)
|
* [Generics](#generics)
|
||||||
* [Concurrency](#concurrency)
|
* [Concurrency](#concurrency)
|
||||||
|
* [Channels](#channels)
|
||||||
* [Decoding JSON](#decoding-json)
|
* [Decoding JSON](#decoding-json)
|
||||||
* [Testing](#testing)
|
* [Testing](#testing)
|
||||||
* [Memory management](#memory-management)
|
* [Memory management](#memory-management)
|
||||||
|
@ -1798,7 +1799,118 @@ fn main() {
|
||||||
// done
|
// done
|
||||||
```
|
```
|
||||||
|
|
||||||
Unlike Go, V has no channels (yet). Nevertheless, data can be exchanged between a coroutine
|
### Channels
|
||||||
|
Channels are the preferred way to communicate between coroutines. V's channels work basically like
|
||||||
|
those in Go. You can push objects into a channel on one end and pop objects from the other end.
|
||||||
|
Channels can be buffered or unbuffered and it is possible to `select` from multiple channels.
|
||||||
|
|
||||||
|
#### Syntax and Usage
|
||||||
|
Channels have the type `chan objtype`. An optional buffer length can specified as `cap` property
|
||||||
|
in the declaration:
|
||||||
|
|
||||||
|
```v
|
||||||
|
ch := chan int{} // unbuffered - "synchronous"
|
||||||
|
ch2 := chan f64{cap: 100} // buffer length 100
|
||||||
|
```
|
||||||
|
|
||||||
|
Channels do not have to be declared as `mut`. The buffer length is not part of the type but
|
||||||
|
a property of the individual channel object. Channels can be passed to coroutines like normal
|
||||||
|
variables:
|
||||||
|
|
||||||
|
```v
|
||||||
|
fn f(ch chan int) {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
...
|
||||||
|
go f(ch)
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Objects can be pushed to channels using the arrow operator. The same operator can be used to
|
||||||
|
pop objects from the other end:
|
||||||
|
|
||||||
|
```v
|
||||||
|
n := 5
|
||||||
|
x := 7.3
|
||||||
|
ch <- n // push
|
||||||
|
ch2 <- x
|
||||||
|
|
||||||
|
mut y := f64(0.0)
|
||||||
|
m := <-ch // pop creating new variable
|
||||||
|
y = <-ch2 // pop into existing variable
|
||||||
|
```
|
||||||
|
|
||||||
|
A channel can be closed to indicate that no further objects can be pushed. Any attempt
|
||||||
|
to do so will then result in a runtime panic (with the exception of `select` and
|
||||||
|
`try_push()` - see below). Attempts to pop will return immediately if the
|
||||||
|
associated channel has been closed and the buffer is empty. This situation can be
|
||||||
|
handled using an or branch (see [Handling Optionals](#handling-optionals)).
|
||||||
|
|
||||||
|
```v
|
||||||
|
ch.close()
|
||||||
|
...
|
||||||
|
m := <-ch or {
|
||||||
|
println('channel has been closed')
|
||||||
|
}
|
||||||
|
|
||||||
|
// propagate error
|
||||||
|
y := <-ch2 ?
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Channel Select
|
||||||
|
|
||||||
|
The `select` command allows monitoring several channels at the same time without noticeable CPU load. It consists
|
||||||
|
of a list of possible transfers and associated branches of statements - similar to the [match](#match) command:
|
||||||
|
```v
|
||||||
|
select {
|
||||||
|
a := <-ch {
|
||||||
|
// do something with `a`
|
||||||
|
}
|
||||||
|
b = <-ch2 {
|
||||||
|
// do something with predeclared variable `b`
|
||||||
|
}
|
||||||
|
ch3 <- c {
|
||||||
|
// do something if `c` was sent
|
||||||
|
}
|
||||||
|
> 500 * time.millisecond {
|
||||||
|
// do something if no channel has become ready within 0.5s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The timeout branch is optional. If it is absent `select` waits for an unlimited amount of time.
|
||||||
|
It is also possible to proceed immediately if no channel is ready in the moment `select` is called
|
||||||
|
by adding an `else { ... }` branch. `else` and `> timeout` are mutually exclusive.
|
||||||
|
|
||||||
|
The `select` command can be used as an *expression* of type `bool` that becomes `false` if all channels are closed:
|
||||||
|
```v
|
||||||
|
if select {
|
||||||
|
ch <- a {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// channel is closed
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Special Channel Features
|
||||||
|
|
||||||
|
For special purposes there are some builtin properties and methods:
|
||||||
|
```v
|
||||||
|
res := ch.try_push(a) // try to perform `ch <- a`
|
||||||
|
res2 := ch2.try_pop(mut b) // try to perform `b = <-ch2
|
||||||
|
l := ch.len // number of elements in queue
|
||||||
|
c := ch.cap // maximum queue length
|
||||||
|
```
|
||||||
|
The `try_push/pop()` methods will return immediately with one of the results `.success`, `.not_ready`
|
||||||
|
or `.closed` - dependent on whether the object has been transferred or the reason why not. Usage
|
||||||
|
of these methods and properties in production is not recommended - algorithms based on them are often subject
|
||||||
|
to race conditions. Use `select` instead.
|
||||||
|
|
||||||
|
Data can be exchanged between a coroutine
|
||||||
and the calling thread via a shared variable. This variable should be created as reference and passed to
|
and the calling thread via a shared variable. This variable should be created as reference and passed to
|
||||||
the coroutine as `mut`. The underlying `struct` should also contain a `mutex` to lock concurrent access:
|
the coroutine as `mut`. The underlying `struct` should also contain a `mutex` to lock concurrent access:
|
||||||
|
|
||||||
|
|
|
@ -191,97 +191,3 @@ are sometimes surprising. Each statement should be seen as a single
|
||||||
transaction that is unrelated to the previous or following
|
transaction that is unrelated to the previous or following
|
||||||
statement. Therefore - but also for performance reasons - it's often
|
statement. Therefore - but also for performance reasons - it's often
|
||||||
better to group consecutive coherent statements in an explicit `lock` block.
|
better to group consecutive coherent statements in an explicit `lock` block.
|
||||||
|
|
||||||
### Channels
|
|
||||||
Channels in V work basically like those in Go. You can `push()` objects into
|
|
||||||
a channel and `pop()` objects from a channel. They can be buffered or unbuffered
|
|
||||||
and it is possible to `select` from multiple channels.
|
|
||||||
|
|
||||||
#### Syntax and Usage
|
|
||||||
There is no support for channels in the core language (yet), so all functions
|
|
||||||
are in the `sync` library. Channels must be created as `mut` objects.
|
|
||||||
|
|
||||||
```v
|
|
||||||
mut ch := sync.new_channel<int>(0) // unbuffered
|
|
||||||
mut ch2 := sync.new_channel<f64>(100) // buffer length 100
|
|
||||||
```
|
|
||||||
|
|
||||||
Channels can be passed to coroutines like normal `mut` variables:
|
|
||||||
|
|
||||||
```v
|
|
||||||
fn f(mut ch sync.Channel) {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
...
|
|
||||||
go f(mut ch)
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The routines `push()` and `pop()` both use *references* to objects. This way
|
|
||||||
unnecessary copies of large objects are avoided and the call to `cannel_select()`
|
|
||||||
(see below) is simpler:
|
|
||||||
|
|
||||||
```v
|
|
||||||
n := 5
|
|
||||||
x := 7.3
|
|
||||||
ch.push(&n)
|
|
||||||
ch2.push(&x)
|
|
||||||
|
|
||||||
mut m := int(0)
|
|
||||||
mut y := f64(0.0)
|
|
||||||
ch.pop(&m)
|
|
||||||
ch2.pop(&y)
|
|
||||||
```
|
|
||||||
|
|
||||||
A channel can be closed to indicate that no further objects can be pushed. Any attempt
|
|
||||||
to do so will then result in a runtime panic. The `pop()` method will return immediately `false`
|
|
||||||
if the associated channel has been closed and the buffer is empty.
|
|
||||||
|
|
||||||
```v
|
|
||||||
ch.close()
|
|
||||||
...
|
|
||||||
if ch.pop(&m) {
|
|
||||||
println('got $m')
|
|
||||||
} else {
|
|
||||||
println('channel has been closed')
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
There are also methods `try_push()` and `try_pop()` which return immediatelly with the return value `.not_ready` if the transaction
|
|
||||||
cannot be performed without waiting. The return value is of type `sync.TransactionState` which can also be
|
|
||||||
`.success` or `.closed`.
|
|
||||||
|
|
||||||
To monitor a channel there is a method `len()` which returns the number of elements currently in the queue and the attribute
|
|
||||||
`cap` for the queue length. Please be aware that in general `channel.len() > 0` does not guarantee that the next
|
|
||||||
`pop()` will succeed without waiting, since other threads may already have "stolen" elements from the queue. Use `try_pop()` to
|
|
||||||
accomplish this kind of task.
|
|
||||||
|
|
||||||
The select call is somewhat tricky. The `channel_select()` function needs three arrays that
|
|
||||||
contain the channels, the directions (pop/push) and the object references and
|
|
||||||
a timeout of type `time.Duration` (`time.infinite` or `-1` to wait unlimited) as parameters. It returns the
|
|
||||||
index of the object that was pushed or popped or `-1` for timeout.
|
|
||||||
|
|
||||||
```v
|
|
||||||
mut chans := [ch, ch2] // the channels to monitor
|
|
||||||
directions := [sync.Direction.pop, .pop] // .push or .pop
|
|
||||||
mut objs := [voidptr(&m), &y] // the objects to push or pop
|
|
||||||
|
|
||||||
// idx contains the index of the object that was pushed or popped, -1 means timeout occured
|
|
||||||
idx := sync.channel_select(mut chans, directions, mut objs, 0) // wait unlimited
|
|
||||||
match idx {
|
|
||||||
0 {
|
|
||||||
println('got $m')
|
|
||||||
}
|
|
||||||
1 {
|
|
||||||
println('got $y')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// idx = -1
|
|
||||||
println('Timeout')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
If all channels have been closed `-2` is returned as index.
|
|
||||||
|
|
Loading…
Reference in New Issue