目录
前言
应用程序通常需要
- 图形化界面异步执行一部分代码,以提升交互体验
- 需要并发地执行以提升效率
- 服务端并发处理连接,提升响应速度和qps
这些功能都是通过并发实现地,在Go语言中,每一个并发的执行单元叫作一个goroutine,可以暂时理解为Java地线程Thread。语法非常简单,就是在调用需要异步执行的方法前加上go关键字:
f() // call f(); 同步调用,等待返回
go f() // 创建 goroutine调用f(); 不等待结果,当前goroutine继续执行下面地代码
1.goroutine
1.1 交互体验
当程序在做复杂性高的工作,用户等待在那里得不到响应,用户体验非常差,所以我们通常看到有很多应用会展示进度条或者一朵菊花似的圈圈在那转(如图),以提示用户等待⌛️。
下面是一个最简单的样例,在执行fib斐波那契数列计算时,并发地打印等待的圈圈spinner。两个独立执行单元分别在独立的函数中,但两个函数会并发地执行(cpu多核的情况可以同时执行,非多核是两个单元轮流执行一个时间片)。
func main() {
go spinner(100 * time.Millisecond) // 并发执行函数spinner
const n = 45
fibN := fib(n) // slow 递归深度大,执行效率低
fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)
}
执行结果:一直旋转,直到fib返回
主函数main也执行结束,主函数返回时,所有的goroutine都会被直接打断,程序退出。除了从主函数退出或者直接终止程序之外,没有其它的编程方法能够让一个goroutine来打断另一个的执行,但可以通过goroutine之间的通信来让一个goroutine A 请求goroutine B,并让goroutine B自行结束执行。
1.2 服务端
一个TCP服务端如果只能同步执行,那一次只能处理一个请求,执行结果如下,第一个客户端连接,能后拿到响应;第二个客户端连接了
// TCP 服务端
func main() {
listener, err := net.Listen("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
for {
conn, err := listener.Accept() // 监听连接,有连接进来返回一个连接
if err != nil {
log.Print(err) // e.g., connection aborted
continue
}
handleConn(conn) // 一次只能处理一个连接,一直等到这个函数返回,才能下个循环响应下一个连接
}
}
// 返回给客户端当前时间,死循环,一直不能退出
func handleConn(c net.Conn) {
defer c.Close()
for {
_, err := io.WriteString(c, time.Now().Format("15:04:05\n"))
if err != nil {
return // e.g., client disconnected
}
time.Sleep(1 * time.Second)
}
}
client1 | client2 |
---|---|
![]() |
![]() |