并发编程
协程Goroutine:Goroutine由Go运行时(runtime)管理,而不是操作系统内核。它比线程更轻量。启动Goroutine的代价很低,只需要在调用函数前加上go关键字。Goroutine让并发编程变得非常简单。我们只需在逻辑上将程序分解为多个并发任务,然后用go语句启动每个并发任务即可。运行时会做好线程管理、时间片调度等工作。
Goroutine使用协作式调度,而非抢占式,需要主动让出控制权。
Channel:Goroutine通过Channel进行通信
锁和线程同步:Go语言实现线程同步的主要方式是通过内置的sync包中的锁机制。
常用的锁类型包括: Mutex:基本的互斥锁,用于保护共享资源的同步访问。
RWMutex:读写互斥锁,允许多个只读线程同时访问,但只允许一个写线程访问。
WaitGroup:用于等待一组goroutine都完成的同步工具。
Cond:条件变量,用于复杂的线程同步场景。
Once:只执行一次,用于单例模式的实现。 使用锁的主要步骤:
- 使用var初始化一个锁。
- 在可能产生冲突的goroutine中使用lock()上锁。
- 在冲突访问完成后使用unlock()解锁。需要注意避免死锁的发生,同时锁的粒度要合适,过于细致会影响效率。
除锁外,Go还通过channel提供了基于通信的线程同步方式,是Go语言鼓励的先进的同步范式。
func main() {
fmt.Println("hello world")
add()
//CalSquare()
}
var (
x int64
lock sync.Mutex
)
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock()
x = x + 1
lock.Unlock()
}
}
func addWithoutLock() {
for i := 0; i < 2000; i++ {
x = x + 1
}
}
func add() {
x = 0
for i := 0; i < 5; i++ {
go addWithoutLock()
}
time.Sleep(time.Second)
fmt.Println("WithoutLock", x)
x = 0
for i := 0; i < 5; i++ {
go addWithLock()
}
time.Sleep(time.Second)
fmt.Println("WithLock", x)
}
func CalSquare() {
src := make(chan int)
dest := make(chan int, 3)
go func() {
defer close(src) //延迟执行,关闭通道
for i := 0; i < 10; i++ {
src <- i
}
}()
go func() {
defer close(dest)
for x := range src {
dest <- x * x
}
}()
for x := range dest {
fmt.Println(x)
}
}
Go Module:Go Module是Go语言从1.11版本开始正式推出的依赖管理系统。
它具有以下主要特征:
- 使用go.mod文件定义依赖:每个模块都有一个go.mod文件,明确声明依赖了哪些模块及版本。
- 支持语义化版本规范:依赖版本可以使用语义化版本号,如v1.2.3。
- 去中心化:不依赖中心仓库,可以使用任意仓库。
- 自动化vendor:通过go mod vendor可以自动把所有依赖打包进vendor目录。
- 向后兼容:老项目可以平滑过渡到模块项目。
- 支持GOPROXY:可配置代理来避免外网访问和提升下载速度。
- 支持Go Module Mirror:可以配置镜像加速模块访问。
Go Module为Go语言带来了语义化版本、离线构建、可复现的构建等特性,是包管理的重要进步。它是Go语言打造可靠、可扩展、安全软件的重要基石。