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)
|
||||
* [Generics](#generics)
|
||||
* [Concurrency](#concurrency)
|
||||
* [Channels](#channels)
|
||||
* [Decoding JSON](#decoding-json)
|
||||
* [Testing](#testing)
|
||||
* [Memory management](#memory-management)
|
||||
|
@ -1798,7 +1799,118 @@ fn main() {
|
|||
// 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
|
||||
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
|
||||
statement. Therefore - but also for performance reasons - it's often
|
||||
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