GO语言中的协程|青训营

53 阅读3分钟

Go语言中的协程

通过使用关键字 go,可以快速启动一个并发运行的 goroutine,而 goroutine 底层实际上是使用协程来实现并发的。

go func(a int){
    fmt.Println(a)
}

协程和线程在功能上实现了类似的功能,但协程的重点在于它的"轻量"性。相较于需要数兆字节的内核态线程,协程在用户态只需要几KB 的内存,因此使用协程更加简洁高效,有时候也被称为"微线程"。因此,在Go的开发过程中,goroutine是一种可以随意创建的资源,只要你有足够的能力,你可以随意创建,而不必担心资源开销会拖累业务。然而,协程的并发控制问题仍然存在,这也是使用过程中的一个主要难点。

Go语言的CSP模型

在主流编程语言中,实现并发通常有两种方法:

通过共享内存进行通信,不同的线程之间通过共享内存进行交互,但需要引入同步机制如锁来保护共享数据。然而,当涉及大量中间变量和复杂逻辑时,这种方式可能会导致复杂的管理问题,比如死锁。

通过通信来实现内存共享,避免了共享内存带来的高耦合问题。尽管如此,仍然可能出现数据竞争的情况,因此Go语言引入了通道(channel)来实现通信。

通道(channel)

make(chan int)
make(chan int, 10)
<-chan
chan<-

通道在某种程度上可以被视为一种锁。当试图占用某块数据时,通过通道接收对该内存块的引用。接收成功意味着其他人无法再使用该内存块。在使用完毕后,将内存块的引用发送到通道,以便让其他人使用。

通道可以分为有缓冲(类似于"同步模式")和无缓冲(类似于"异步模式")两种。

同步机制

尽管Go语言引入了通道,但并没有完全抛弃共享内存的方式。在某些情况下,这两种方式并没有那么大的差距。

包依赖管理

GOPATH

最早的Go语言包管理方式是通过设置 GOPATH 环境变量来查找项目依赖的第三方包。如果依赖不存在,就需要使用 go get 命令来下载。然而,go get 会自动下载最新版本的依赖包,这可能导致版本不一致的问题。

Vendor

为了解决 go get 可能导致的版本问题,Go官方引入了 vendor 机制。通过在项目目录下创建一个名为 vendor 的文件夹,可以将所有依赖包缓存在其中。在编译时,Go编译器会优先查找 vendor 目录中的依赖包,而不是 GOPATH 中的路径。然而,这种方式仍然无法很好地应对复杂的依赖关系,当依赖过多时,vendor 目录可能会变得臃肿,影响版本控制。

Modules

使用 go module 来管理依赖后,项目根目录下会生成 go.mod 和 go.sum 两个文件。

go.mod 记录了项目依赖的包信息。 go.sum 记录了每个依赖库的版本和哈希值,用于校验包的真实性。 使用GoLand等IDE进行开发时,务必配置好代理(GO PROXY)来支持 go module。