Idiomatic way to make a request-response communication using channels

后端 未结 1 1793
无人及你
无人及你 2021-02-05 10:33

Maybe I\'m just not reading the spec right or my mindset is still stuck with older synchronization methods, but what is the right way in Go to send one type as receive s

相关标签:
1条回答
  • 2021-02-05 11:01

    There're plenty of possibilities, depends what is best approach for your problem. When you receive something from a channel, there is nothing like a default way for responding – you need to build the flow by yourself (and you definitely did in the example in your question). Sending a response channel with every request gives you a great flexibility as with every request you can choose where to route the response, but quite often is not necessary.

    Here are some other examples:

    1. Sending and receiving from the same channel

    You can use unbuffered channel for both sending and receiving the responses. This nicely illustrates that unbuffered channels are in fact a synchronisation points in your program. The limitation is of course that we need to send exactly the same type as request and response:

    package main
    
    import (
        "fmt"
    )
    
    func pow2() (c chan int) {
        c = make(chan int)
        go func() {
            for x := range c {
                c <- x*x
            }
        }()
        return c
    }
    
    func main() {
        c := pow2()
        c <- 2
        fmt.Println(<-c) // = 4
        c <- 4
        fmt.Println(<-c) // = 8
    }
    

    2. Sending to one channel, receiving from another

    You can separate input and output channels. You would be able to use buffered version if you wish. This can be used as request/response scenario and would allow you to have a route responsible for sending the requests, another one for processing them and yet another for receiving responses. Example:

    package main
    
    import (
        "fmt"
    )
    
    func pow2() (in chan int, out chan int) {
        in = make(chan int)
        out = make(chan int)
        go func() {
            for x := range in {
                out <- x*x
            }       
        }()
        return
    }
    
    func main() {
        in, out := pow2()
        go func() {
            in <- 2
            in <- 4
        }()
        fmt.Println(<-out) // = 4
        fmt.Println(<-out) // = 8
    }
    

    3. Sending response channel with every request

    This is what you've presented in the question. Gives you a flexibility of specifying the response route. This is useful if you want the response to hit the specific processing routine, for example you have many clients with some tasks to do and you want the response to be received by the same client.

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    type Task struct {
        x int
        c chan int
    }
    
    func pow2(in chan Task) {
        for t := range in {
            t.c <- t.x*t.x
        }       
    }
    
    func main() {
        var wg sync.WaitGroup   
        in := make(chan Task)
    
        // Two processors
        go pow2(in)
        go pow2(in)
    
        // Five clients with some tasks
        for n := 1; n < 5; n++ {
            wg.Add(1)
            go func(x int) {
                defer wg.Done()
                c := make(chan int)
                in <- Task{x, c}
                fmt.Printf("%d**2 = %d\n", x, <-c)
            }(n)
        }
    
        wg.Wait()
    }
    

    Worth saying this scenario doesn't necessary need to be implemented with per-task return channel. If the result has some sort of the client context (for example client id), a single multiplexer could be receiving all the responses and then processing them according to the context.

    Sometimes it doesn't make sense to involve channels to achieve simple request-response pattern. When designing go programs, I caught myself trying to inject too many channels into the system (just because I think they're really great). Old good function calls is sometimes all we need:

    package main
    
    import (
        "fmt"
    )
    
    func pow2(x int) int {
        return x*x
    }
    
    func main() {
        fmt.Println(pow2(2))
        fmt.Println(pow2(4))
    }
    

    (And this might be a good solution if anyone encounters similar problem as in your example. Echoing the comments you've received under your question, having to protect a single structure, like cache, it might be better to create a structure and expose some methods, which would protect concurrent use with mutex.)

    0 讨论(0)
提交回复
热议问题