Idiomatic method of transmitting a request-response using channels

Maybe I just do not read the specification, or my thinking is still stuck in outdated synchronization methods, but how to choose Go correctly to send one type, how to get something else as an answer?

One of the ways I came up with was

package main import "fmt" type request struct { out chan string argument int } var input = make(chan *request) var cache = map[int]string{} func processor() { for { select { case in := <- input: if result, exists := cache[in.argument]; exists { in.out <- result } result := fmt.Sprintf("%d", in.argument) cache[in.argument] = result in.out <- result } } } func main() { go processor() responseCh := make(chan string) input <- &request{ responseCh, 1, } result := <- responseCh fmt.Println(result) } 

This cache is not needed for this example, but otherwise it will call datarace.

Is that what I have to do?

+6
source share
1 answer

There are many possibilities, depending on what is best for your problem. When you get something from the channel, there is nothing like the default method for the answer - you need to build the stream yourself (and you definitely did in the example in your question). Sending a response channel with each request gives you more flexibility, since with each request you can choose where to send the response, but it is often not required.

Here are a few other examples:

1. Sending and receiving from the same channel

You can use the unbuffered channel to send and receive responses. This perfectly illustrates that unbuffered channels are actually the synchronization points in your program. Of course, the limitation is that we need to send exactly the same type as the 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 the input and output channels. If you want, you can use the buffered version. This can be used as a request / response script and will allow you to have a route responsible for sending requests, another for processing them, and 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 a response channel with each request

This is what you presented in the question. Gives you the flexibility to determine the response route. This is useful if you want the answer to go to a specific processing procedure, for example, you have many clients with some tasks, and you want the answer 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 { tc <- tx*tx } } 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() } 

It is worth saying that this scenario does not have to be implemented with a return channel for each task. If the result has some kind of client context (for example, a client identifier), one multiplexer can receive all responses and then process them in accordance with the context.

Sometimes it makes no sense to engage channels to achieve a simple request-response template. When developing go programs, I tried to catch too many channels in the system (simply because I think they are really great). Sometimes the good old function calls are 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 may be a good solution if someone encounters a similar problem, as in your example. Repeating the comments that you received on your question in order to protect one structure, for example a cache, it would be better to create a structure and expose some methods that protect simultaneous use with the mutex.)

+8
source

Source: https://habr.com/ru/post/978959/


All Articles