go语言编程:为什么使用channel? | 豆包MarsCode AI刷题

35 阅读3分钟

在正式开始之前,我们先来看看,在Go语言中,channel是如何使用的:

package main
import "fmt"
// 定义一个函数,模拟通过channel发送数据
func sendData(ch chan string) {
    // 向 channel 发送数据
    ch <- "Hello from goroutine!"
}
func main() {
    // 创建一个 channel
    ch := make(chan string)
    // 启动一个 goroutine
    go sendData(ch)
    // 从 channel 接收数据
    msg := <-ch
    // 输出接收到的消息
    fmt.Println(msg)
}

channel有两个新的语法点,一个是使用make(chan type)函数来建立channel, 一个是使用<-符号来向channel写入或读出数据。

这里的<-符号和C家族中的结构体指针十分相似,也许是为了做出区分所以故意使用了相反的写法。另外使用箭头指向来表示输入输出的思想也和C++中iostream的<<和>>符号很相似。

channel本质上是一个符合先进先出原则的队列,或者说消息队列。在go中有这么一条原则:不要通过共享内存来通信,而要通过通信来实现内存共享。

要讨论这个问题,我们需要先分析为什么要使用消息队列:

异步处理

假设一种情况,一个用户注册了一个新账号,在这一过程重后端需要把账户信息写入数据库、发出验证邮件、发出验证短信。如果采用串行方式,每个步骤耗时100ms,那么最终会在300ms给用户响应。

如果使用并行方式,先写入数据库,然后同时发出短信和邮件,那么200ms就可以给用户响应。

但是实际上,只需要数据写入了数据库,就可以给用户响应了,而发送邮件和短信可以通过消息队列存储起来慢慢操作。假设写入消息队列需要10ms,那么只需要110ms就可以响应给用户。

应用解耦

再来看看下一种情况。假设A用户要转账到B用户,如果A一侧出了问题,那么交易会被终止;但如果是B一侧出了问题,A的交易同样会失败。此时A和B发生了耦合。

但实际上我们不希望B的问题会影响到A。此时可以把A转账的消息保存到消息队列重,此时A就可以离开了,不需要在意后续如何处理。

流量削峰

在一些流量异常高的情况下,后端服务可能会直接宕机。要防止这种情况,可以把所有消息先写入消息队列;如果请求过多了,可以直接拒绝请求,从而保证了服务器的稳定运行。

以上三种都或多或少可以由go的channel实现(虽然性能可能不如专门的中间件),但是其作为语言级的实现使得我们可以在程序中轻松地运用以上的思想,而不是仅仅运用在前后端之间或者微服务模块之间。

“不要通过共享内存来通信,而要通过通信来实现内存共享。”这句话某种程度也可以算是为了实现以上三点。通过使用channel,可以实现数据的异步处理,以及不同程序间的解耦。