也许我只是没有正确阅读规范,或者我的心态仍然停留在较早的同步方法上,但是Go 中发送一种类型作为接收其他类型作为响应 的正确方法 是什么?
我想到的一种方法是
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) }
该高速缓存对于此示例并不是真正必需的,但否则将导致数据争用。
这是我应该做的吗?
有很多可能性,这取决于解决问题的最佳方法。当您从某个渠道收到消息时,没有什么比默认的响应方式更好的了–您需要自己构建流程(肯定是在问题示例中进行的)。与每个请求一起发送响应通道可为您提供极大的灵活性,因为每个请求都可以选择将响应路由到何处,但是通常没有必要。
以下是一些其他示例:
1.从同一频道发送和接收
您可以将无缓冲通道用于发送和接收响应。这很好地说明了未缓冲的通道实际上是程序中的一个同步点。限制当然是我们需要发送与请求和响应完全相同的类型:
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.发送到一个频道,从另一个频道接收
您可以分离输入和输出通道。如果愿意,您将可以使用缓冲版本。这可以用作请求/响应方案,并且可以让您拥有一个负责发送请求的路由,另一个负责处理请求的路由,另一个负责接收响应的路由。例:
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.发送每个请求的响应通道
这就是您在问题中提出的内容。使您可以灵活地指定响应路径。如果您希望响应达到特定的处理例程,这很有用,例如,您有许多要执行某些任务的客户端,并且希望同一客户端接收响应。
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() }
值得一提的是,无需按任务返回通道来实现此方案。如果结果具有某种客户端上下文(例如客户端ID),则单个多路复用器可能会接收所有响应,然后根据上下文对其进行处理。
有时,让渠道参与以实现简单的请求- 响应模式是没有意义的。在设计go程序时,我发现自己试图向系统中注入太多通道(只是因为我认为它们确实很棒)。过去,好的函数调用有时就是我们所需要的:
package main import ( "fmt" ) func pow2(x int) int { return x*x } func main() { fmt.Println(pow2(2)) fmt.Println(pow2(4)) }
(而且,如果有人遇到与您的示例类似的问题,这可能是一个很好的解决方案。回显您在问题下收到的评论,必须保护单个结构(例如缓存),创建一个结构并公开一些结构 可能 会更好。方法,以保护并发使用互斥体。)