Go后端学习- Go工程进阶 | 青训营笔记

50 阅读1分钟

这是我参与「第五届青训营 」笔记创作活动的第2天

1. Go语言并发编程

1.1 Goroutine

go语言协程的特点

  1. 有独立的栈空间
  2. 共享程序堆空间
  3. 调度由用户控制
  4. 协程是轻量级的线程

创建goroutine

在函数调用语句之前加go关键字即创建开启并发单元

package main

import (
	"fmt"
	"strconv"
	"time"
)

//编写一个函数,每隔一秒输出 "hello,world"
func test() {
	for i := 1; i <= 10; i++ {
		fmt.Println("test hello,world " + strconv.Itoa(i))
		time.Sleep(time.Second)
	}
}
func main() {

	go test() //开启了一个协程,使其同时执行
	for i := 1; i <= 3; i++ {
		fmt.Println("mian() hello,world " + strconv.Itoa(i))
		time.Sleep(time.Second)
	}
	//输出结果:
	// mian() hello,world 1
	// test hello,world 1
	// test hello,world 2
	// mian() hello,world 2
	// test hello,world 3
	// mian() hello,world 3
}

说明

Go语言的协程是抢占式调度的,当遇到长时间执行或者进行系统调用时,会主动把当前goroutine的CPU 转让出去,让其他goroutine能被调度并执行。

1.2 Channel

创建Channel

Channel的定义有三种类型,可接受和发送某数据类型的数据、只接受某数据类型的数据和只发送某数据类型的数据。make函数可以初始化Channel并设置缓冲容量。

package concurrence

func CalSquare() {
	src := make(chan int)
	dest := make(chan int, 3)
	//创建两个通道,一个用于发送数字,一个用于接收数字
	//发送数字到src通道
	go func() {
		defer close(src)
		for i := 0; i < 10; i++ {
			src <- i
		}
	}()
	//从src通道中取出数字,计算平方,发送到dest通道
	go func() {
		defer close(dest)
		for i := range src {
			dest <- i * i
		}
	}()
	for i := range dest {
		println(i)
	}
}

说明

Channel是Go中的一个核心类型,可以把它看成一个管道,通过它并发核心单元就可以实现协程之间的内存共享,实现通信。

1.3 Sync

并发安全

为什么会有并发安全问题?

并发编程的并发体在同一个时间内访问共享数据,如果处理不当,就会导致实际结果偏离理论结果,影响程序的运行。

案例说明

通过互斥锁来保证并发安全

package main

import (
	"sync"
	"time"
)

var (
	x    int
	lock sync.Mutex
)

func addwithlock() {
	lock.Lock()
	x++
	lock.Unlock()
}
func addwithoutlock() {
	x++
}

func main() {
	x = 0
	for i := 0; i < 100000; i++ {
		go addwithlock()
	}
	time.Sleep(time.Second)
	println("x with lock:", x)

	x = 0
	for i := 0; i < 100000; i++ {
		go addwithoutlock()
	}
	time.Sleep(time.Second)
	println("x without lock:", x)
	//输出结果:
	// x with lock: 100000
	// x without lock: 94542
}

WaitGroup

WaitGroup是go语言中一个用来计数的信号量,可以用来解决部分并发安全问题。

案例说明

package concurrence

import (
	"fmt"
	"sync"
)

func hello(i int) {
	println("hello world : " + fmt.Sprint(i))
}

func ManyGo() {
	var wg sync.WaitGroup
	//创建一个等待组
	//通过控制信号量来控制goroutine的数量
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func(j int) {
			defer wg.Done()
			//goroutine执行完毕,将信号量减1
			hello(j)
		}(i)
	}
	wg.Wait()
	//等待所有的goroutine执行完毕
}

2. Go语言依赖管理

2.1 GOPATH

GOPATH目录下一共包含三个子目录

  • bin:存储所编译生成的二进制文件。
  • pkg:存储预编译的目标文件,以加快程序的后续编译速度。
  • src:存储所有.go文件或源代码。

弊端:无法传递版本信息,无法实现package的多版本控制

2.2 Go Vendor

vendor是go语言的包管理工具,它会把依赖包的副本存到vendor目录下,使得其他机器在编译的时候不用再下载相关依赖,但是无法解决版本变动和项目使用同一包的不同版本问题。

2.3 Go Module

  • 通过go.mod文件管理依赖包版本
  • 通过go get/mod指令工具管理依赖包

go mod 基本操作

go mod init 初始化当前文件夹,创建 go.mod 文件

go mod edit 编辑 go.mod 文件

go mod tidy 增加缺少的包,删除无用的包

go mod download 下载依赖包到本地(默认在 GOPATH/pkg/mod 目录)

go mod graph 打印模块依赖图

go mod vendor 将依赖复制到 vendor 目录下

go mod verify 校验依赖

go list -m all 查看所有的依赖