Timmmm
Timmmm

Reputation: 96605

Select on a go send and receive channel at the same time

Suppose I have a buffered send and unbuffered receive channel:

s := make(chan<- int, 5)
r := make(<-chan int)

Is it possible to select on them both, so that r will be selected if it has anything to read, and s will be selected if it is not full? Something equivalent to this, but not using 100% CPU:

for {
    if len(s) < cap(s) {
        // Send something
    }
    if len(r) > 0 {
        // Receive something
    }
}

Note that I want to decide what to send at the time that I send it, not earlier.

Edit

This question is basically equivalent to "Can I block until a channel is ready-to-send, without sending anything?"

Upvotes: 7

Views: 7318

Answers (4)

Dustin
Dustin

Reputation: 232

Instead of sending the value directly, you could send an object which can compute the value. Then you can detect when the object is sent, and then compute. You can use sync.Once to make sure the computation is done once, and gate access to the result. This avoids using Sleeps.

Something like this: https://play.golang.org/p/oL2HA2jl91

Upvotes: 1

Evan
Evan

Reputation: 6545

Can I block until a channel is ready-to-send, without sending anything?

Not with primitive go channels. You could probably manage to pull something together using the SharedBuffer type in my channels library, but even that is complicated and it uses a great deal of reflection under the covers.

https://godoc.org/github.com/eapache/channels#SharedBuffer

Upvotes: 0

icza
icza

Reputation: 417767

You can do this with select but since the value to be sent is evaluated only once, if both channel are not ready, the value to be sent would become outdated by the time it can be sent.

So add a default case which will be executed if none of the channels are ready, in which you just "sleep" a little, then try again (with an updated new value calculated/acquired to be sent). By sleeping you will not consume CPU resources:

s := make(chan<- int, 5)
r := make(<-chan int)

for {
    v := valueToSend() // Evaluated each time we try to send
    select {
    case s <- v:
        fmt.Println("Sent value:", v)
    case vr := <-r:
        fmt.Println("Received:", vr)
    default: // If none are ready currently, we end up here
        time.Sleep(time.Millisecond * 1)
    }
}

Note that checking the length or capacity of a channel and then sending/receiving is not considered a good solution because the channel might become not ready between the time you check its length/cap and you try to send/receive, as illustrated below:

if len(r) > 0 {
    // r is ready to receive

    // Optional other code here,
    // meanwhile another goroutine might receive the value from r!

    r <-  // If other goroutine received from r, this will block!
}

Upvotes: 5

Ainar-G
Ainar-G

Reputation: 36199

It's a simple select:

select {
case s <- n:
    // Successful send.
case n := <- r:
    // Successful receive. Do something with n.
}

Upvotes: 3

Related Questions