From 3454677eb9445651a443c8dcbe1dc782677af9ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kr=C3=BCger?= <45282134+UweKrueger@users.noreply.github.com> Date: Sun, 20 Sep 2020 16:20:56 +0200 Subject: [PATCH] doc: update channel documentation (#6432) --- doc/docs.md | 114 +++++++++++++++++++++++++++++++++++++++++++++++- doc/upcoming.md | 94 --------------------------------------- 2 files changed, 113 insertions(+), 95 deletions(-) diff --git a/doc/docs.md b/doc/docs.md index 9949ff3309..a12d4a6479 100644 --- a/doc/docs.md +++ b/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: diff --git a/doc/upcoming.md b/doc/upcoming.md index c54117bb0c..24f0c57fcd 100644 --- a/doc/upcoming.md +++ b/doc/upcoming.md @@ -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(0) // unbuffered -mut ch2 := sync.new_channel(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.