依赖管理 | 青训营笔记

61 阅读1分钟

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

并发与并行

image.png

协程Goroutine

image.png 读者应注意:上图中下面所写文字介绍中,线程与协程的定义写反了。

通过go关键字来开启goroutine;

goroutine是一个轻量级线程,其调度是由Golang运行时进行管理的。

package main

import (
	"fmt"
	"time"
)

func HelloPrint(i int) {
	// println("Hello goroutine : " + fmt.Sprint(i))
	fmt.Println("Hello goroutine :", i)
}

// 效果就是快速且无序打印

func HelloGoroutine() {
	for i := 0; i < 5; i++ {
		// 匿名函数是为了保护变量(大概)
		go func(j int) {
			HelloPrint(j)
		}(i)
	}
	// time.Sleep()的作用是:保证了子协程在执行完之前,主协程不退出。
	time.Sleep(time.Second)
}

func main() {
	HelloGoroutine()
}

image.png

Channel

image.png

通道是用来传递数据的一个数据结构,可以用于两个goroutine之间,通过传递一个指定类型的值来同步运行和通讯。

操作符<-用于指定通道的方向,实现发送or接收;

特别地,若未指定方向,则为双向通道;

若想要使用通道:

ch <- v    // 把 v 发送到通道 ch
v := <-ch  // 从 ch 接收数据   并把值赋给 v

下面是一个完整的例子:

package main

import (
	"fmt"
)

func CalcPow() {
	src := make(chan int)
	dest := make(chan int, 3)
	// 子协程src发送0~9数字
	go func() {
		defer close(src) // 当子协程src结束的时候再关闭,减少资源浪费
		for i := 0; i < 10; i++ {
			src <- i
		}
	}()
	// 子协程dest计算输入数字的平方
	go func() {
		defer close(dest)
        // 通过 range 关键字来实现遍历读取到的数据
		for i := range src {
			dest <- (i * i)
		}
	}()
	// 主协程输出最后的答案
	// 这里可以暂时认为子协程需要使用匿名函数
	for i := range dest {
        // 因为主协程可能会有更多的复杂操作,比较耗时,所以用带缓冲的通道可以避免问题
		fmt.Println(i)
	}
}

func main() {
	CalcPow()
}

下面为通过阅读其他同学的文章所得知:

如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。

range 函数遍历每个从通道接收到的数据,因为 src 在发送完 10个数据之后就关闭了通道,所以这里我们 range 函数在接收到 10个数据之后就结束了。如果上面的 src 通道不关闭,那么 range 函数就不会结束,从而在接收第 11个数据的时候就阻塞了。

defer语句

加上defer之后就会进入栈内,如下程序输出为333 222 111

package main

import "fmt"

func main() {
	defer fmt.Println("111")
	defer fmt.Println("222")
	defer fmt.Println("333")
}

init函数:

程序初始化顺序:变量初始化->init()->main()

package main

import "fmt"

var v int = VarInit()

func init() {
	fmt.Println("INITIALIZE!")
}

func VarInit() int {
	fmt.Println("The initialize of var is completed!")
	return 110
}

func main() {
	fmt.Println(v)
}

/*
Output:
	The initialize of var is completed!
	INITIALIZE!
	110
*/

Gopath是一个环境变量,其中有三个部分:

  • bin:项目编译的二进制文件
  • pkg:项目编译的中间产物,加速编译
  • src:项目源码,项目代码直接依赖src下的代码go get下载最新版本的包到src目录下

存在的弊端:无法实现package的多版本控制

Go Vendor

项目目录下增加vender文件,所有依赖包副本形式放在$ProjectRoot/vender

依赖寻址方式:vender -> GOPATH

通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题。

存在的弊端:更新项目的时候可能导致编译错误的冲突;无法控制依赖的版本。

Go Module(1.16以后默认开启)

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

终极目标:定义版本规则和管理项目依赖关系

依赖三要素:

  • 配置文件,描述以来——go.mod
  • 中心仓库管理依赖库——Proxy
  • 本地工具——go get/mod

image.png