Go语言进阶依赖管理

1. Go语言进阶

1.1 Goroutine

package mainimport ("fmt""time"
)func hello(i int) {println("hello goroutine : " + fmt.Sprint(i))
}func main() {for i := 0; i < 5; i++ {go func(j int) { hello(j) }(i) // 启动一个新的 goroutine,传入参数}time.Sleep(time.Second) // 主 goroutine 睡眠 1 秒
}

输出如下:

hello goroutine : 3
hello goroutine : 0
hello goroutine : 4
hello goroutine : 1
hello goroutine : 2
  1. hello(i int) 函数:
    hello 函数接受一个 int 参数,并打印 “hello goroutine : i”,其中 i 是传入的整数。

  2. main 函数的循环:
    在 main 函数中,for 循环执行 5 次,i 从 0 到 4。每次循环都会启动一个新的 goroutine。
    这里,go 关键字用于启动一个新的 goroutine,并将当前的 i 值传递给一个匿名函数。匿名函数接收一个 j 参数,并调用 hello(j)。

关键点是这里的 匿名函数。在 Go 中,匿名函数可以定义并立刻调用,而它的参数可以是传递给它的值。在这个例子中,i 是从外部传递给匿名函数的参数 j。

  1. 为什么要用闭包(匿名函数)?
    Go 语言中的 goroutine 是并发执行的。如果我们直接将 i 传递给 hello(i),我们就会遇到一个 竞争条件 问题,因为 i 是一个在主线程中递增的变量,它的值在每次 goroutine 启动时都可能变化。也就是说,所有 goroutine 可能都能获取到相同的 i 值,导致它们都打印相同的值。
    通过使用 闭包(匿名函数),每个 goroutine 都会捕获它所启动时的 i 值(即使外部循环 i 的值发生变化),避免了共享变量的问题。匿名函数会传递当前的 i 值作为参数 j,从而确保每个 goroutine 得到正确的值。

  2. time.Sleep(time.Second)
    因为 goroutine 是并发执行的,main 函数在启动 goroutine 后会立刻返回。为了确保主 goroutine(main 函数的执行线程)能够等待其他 goroutines 完成工作,代码调用了 time.Sleep(time.Second),这会使主线程暂停 1 秒钟。这样,主线程会等待一段时间,让其他 goroutines 有机会打印它们的信息。

注意:这里的 time.Sleep 只是为了让 goroutines 有时间执行。在实际的生产代码中,我们可能会使用更合适的同步机制(如 sync.WaitGroup)来确保 goroutines 执行完毕。

1.2 通信

Go通过通信共享内存而不是通过共享内存实现通信。

在这里插入图片描述

1.2.1 Channel

make(chan元素类型,[缓冲大小])

无缓冲通道 make(chan int)
有缓冲通道 make(chan int,2)
Channel

package mainimport ("fmt"
)func hello(i int) {println("hello goroutine : " + fmt.Sprint(i))
}func main() {src := make(chan int)              // 创建一个无缓冲的 channel 用于传递整数dest := make(chan int, 3)          // 创建一个带缓冲区大小为3的 channel,用于存储计算结果// 启动第一个 goroutine,向 src channel 发送数据go func() {defer close(src)               // 完成后关闭 src channelfor i := 0; i < 10; i++ {src <- i                   // 向 src channel 发送数据 i}}()// 启动第二个 goroutine,从 src channel 接收数据并计算平方,然后将结果发送到 dest channelgo func() {defer close(dest)              // 完成后关闭 dest channelfor i := range src {           // 从 src 接收数据直到 src 关闭dest <- i * i               // 将接收到的数据平方后发送到 dest}}()// 主 goroutine 从 dest 接收并处理数据for i := range dest {             // 从 dest 接收计算结果直到 dest 关闭// 复杂操作(这里只是打印)println(i)                      // 打印每个接收到的结果}
}
  • Channel 的创建与作用
    src := make(chan int):这是一个 无缓冲的 channel,它用于在第一个 goroutine 中传递数据。因为它是无缓冲的,每次发送操作都会阻塞,直到有其他 goroutine 来接收数据。
    dest := make(chan int, 3):这是一个 带缓冲的 channel,它用于在第二个 goroutine 中传递计算后的数据。缓冲区大小为 3,意味着它最多可以存储 3 个数据,超出会阻塞发送操作,直到有地方消费数据。

执行流程

第一个 goroutine 会依次将数字 0 到 9 发送到 src channel 中。
第二个 goroutine 从 src 接收每个数字,计算它的平方,然后发送到 dest channel。
主 goroutine 从 dest 接收平方结果并打印。

1.3 并发安全Lock

对变量执行2000次+1操作,5个协程并发执行

带缓冲的channel不会因为消费者的消费速度影响生产者的生产效率

package mainimport ("sync""time"
)var (x    int64      // 全局共享变量 xlock sync.Mutex // 互斥锁,用于保护对 x 的并发访问
)// addWithLock 函数:使用互斥锁来保证对 x 的并发访问安全
func addWithLock() {for i := 0; i < 2000; i++ {lock.Lock()   // 上锁,确保只有一个 goroutine 能访问 xx += 1        // 对共享变量 x 进行加法操作lock.Unlock() // 解锁,允许其他 goroutine 访问 x}
}// addWithoutLock 函数:不使用互斥锁直接对 x 进行并发加法操作
func addWithoutLock() {for i := 0; i < 2000; i++ {x += 1 // 不加锁的并发访问,可能会导致数据竞争}
}// Add 函数:演示加锁和不加锁情况下的并发访问
func Add() {x = 0 // 重置 x 的值为 0// 启动 5 个 goroutine 并发执行 addWithoutLock 函数(不使用锁)for i := 0; i < 5; i++ {go addWithoutLock() // 启动一个新的 goroutine,进行不加锁的操作}// 等待一段时间,确保 goroutines 执行完毕time.Sleep(time.Second)// 打印不加锁情况下的 x 值println("WithoutLock:", x)// 重置 x 的值为 0x = 0// 启动 5 个 goroutine 并发执行 addWithLock 函数(使用锁)for i := 0; i < 5; i++ {go addWithLock() // 启动一个新的 goroutine,进行加锁的操作}// 等待一段时间,确保 goroutines 执行完毕time.Sleep(time.Second)// 打印加锁情况下的 x 值println("WithLock:", x)
}func main() {Add() // 调用 Add 函数,启动并发操作
}

(1)全局变量

x:这是一个全局共享变量,它将在多个 goroutines 中被访问和修改。
lock:一个 sync.Mutex 类型的变量,用于在 addWithLock 函数中对共享数据 x 加锁,确保在并发情况下对 x 的访问是安全的。

(2)addWithLock 函数

该函数每次在修改共享数据 x 时都会通过 lock.Lock() 来加锁,修改完后再通过 lock.Unlock() 来释放锁。
由于加了锁,确保了在同一时刻只有一个 goroutine 能对 x 进行操作,避免了数据竞争(data race)。
addWithoutLock 函数:

该函数与 addWithLock 函数类似,但没有加锁保护共享数据 x。
因为没有锁的保护,多个 goroutine 并发修改 x 时会发生数据竞争,可能导致结果不正确(即多个 goroutine 同时读取和修改 x 的值,造成丢失更新)。

(3)Add 函数

首先将 x 重置为 0,然后启动 5 个 goroutines 来执行 addWithoutLock 函数,这些 goroutines 在不加锁的情况下并发地修改 x。

主 goroutine 在启动所有 goroutines 后,调用 time.Sleep(time.Second) 来等待这些 goroutines 执行完毕。

然后打印 x 的值。由于没有加锁,可能会出现 x 的值不准确的情况,因为并发修改会引发数据竞争。

接下来,重置 x 为 0,启动 5 个 goroutines 来执行 addWithLock 函数,这些 goroutines 通过 sync.Mutex 来确保对 x 的访问是互斥的。

主 goroutine 等待这些 goroutines 执行完毕,并打印出 x 的值。由于加了锁,x 的值应该是准确的,每个 goroutine 都会安全地对 x 进行加法操作。

运行结果:

WithoutLock: 7728
WithLock: 10000

2. 依赖管理

2.1 背景与演进

  • GOPATH
  • Go Vendor
  • Go Module

2.2 GOPATH

GOPATH是Go语言支持的一个环境变量,vlue是Go项目的工作区。
GOPATH

目录有以下结构:

  • src:存放Go项目的源码(项目代码直接依赖src下的代码)
  • pkg:存放编译的中间产物,加快编译速度
  • bin:存放Go项目编译生成的二进制文件

go get下载最新版本的包到src目录下。

弊端:不同项目不能依赖同一个库的不同版本。

2.3 Go Vendor

Go Vendor

Vendor是当前项目中的一个目录,其中存放了当前项目依赖的副本。在Vendor机制下,如果当前项目存在Vendor目录,会优先使用该目录下的依赖,如果依赖不存在,会从GOPATH中寻找。通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题。

项目目录下增加vendor文件,所有依赖包副本形式放在$ProjectRoot/vendor
依赖寻址方式:vendor=>GOPATH

但vendor无法很好解决依赖包的版本变动问题和一个项目依赖同一个包的不同版本的问题。

弊端:

Go Vendor弊端

  • 无法控制依赖的版本变动问题和一个项目依赖同一个包的不同版本的问题
  • 更新项目又可能出现依赖冲突,导致编译出错

2.4 Go Module

Go Modules是Go语言官方推出的依赖管理系统,解决了之前依赖管理系统存在的诸如无法依赖同一个库的多个版本等问题,go module从Go1.11开始实验性引入,Go1.16默认开启,一般读为go mod。

2.4.1 依赖管理三要素

1.配置文件,描述依赖 go.mod

2.中心仓库管理依赖库 Proxy

3.本地工具 go get/mod

2.4.2 依赖配置——go.mod

依赖的原生sdk版本最下面是单元依赖,每个依赖单元用模块路径+版本来唯一标示。
go.mod

依赖标识:[Module Path][Version/Pseudo-version]

模块路径用来标识一个模块,从模块路径可以看出从哪里找到该模块,如果是github前缀则表示可以从Github仓库找到该模块,依赖包的源代码由github托管,如果项目的子包想被单独引用,则需要通过单独的init go.mod文件进行管理。

2.4.3 依赖配置——version

version

go path和go vendor都是源码副本方式依赖,没有版本规则概念,而go mod为了放方便管理定义了版本规则,分为语义化版本和基于commit的伪版本。

语义化版本包括不同的MAJOR、MINOR、PATCH。

  • MAJOR版本表示是不兼容的API,所以即使是同一个库,MAJOR版本不同也会被认为是不同的模块。
  • MINOR版本通常是新增函数或功能,前后兼容。
  • PATCH版本一般是修复bug。

基于commit的伪版本包括基础版本前缀、时间戳和12位哈希码前缀。

  • 基础版本前缀是和语义化版本一样的。
  • 时间戳,也就是提交Commit的时间。
  • 校验码(abcdefabcdef),包含12位的哈希前缀,每次提交commit后Go都会默认生成一个伪版本号。

2.4.4 依赖配置——indirect

  1. indirect后缀,表示go.mod对应的当前模块,没有直接导入该依赖模块的包,也就是非直接依赖,标示间接依赖。

  2. incompatible,主版本2+模块会在模块路径增加/vN后缀,这能让go module按照不同的模块来处理同一个项目不同主版本的依赖。
    由于go module是1.11实验性引入所以这项规则提出之前已经有一些仓库打上了2或者更高版本的tag了,为了兼容这部分仓库,对于没有go.mod文件并且主版本在2或者以上的依赖,会在版本号后加上+incompatible后缀。

依赖图

2.5 依赖配置——回源

github是比较常见的代码托管系统平台,而Go Modules系统中定义的依赖,最终可以对应到多版本代码管理系统中某一项目的特定提交或版本,对于go.mod中定义的依赖,则直接可以从对应仓库中下载指定软件依赖,从而完成依赖分发。

但直接使用版本管理仓库下载依赖,存在多个问题。

  • 无法保证构建确定性:软件作者可以直接代码平台增加/修改/删除软件版本,导致下次构建使用另外版本的依赖,或者找不到依赖版本。
  • 无法保证依赖可用性:依赖软件作者可以直接代码平台删除软件,导致依赖不可用。
  • 大幅增加第三方代码托管平台压力。

2.5 依赖分发——Proxy

Proxy

Go Proxy是一个服务站点,它会缓源站中的软件内容,缓存的软件版本不会改变,并且在源站软件删除之后依然可用,从而实现了供"immutability’"和"available’"的依赖分发;使用Go Proxy之后,构建时会直接从Go Proxy站点拉取依赖。

2.6 依赖分发——变量 GOPROXY

GOPROXY
Go Modulesi通过GOPROXY环境变量控制,GOPROXY是一个Go Proxy站点URL列表,使用"direct”表示源站。对于示例配置,整体的依赖寻址路径,会优先从proy1下载依赖,如果proxy1不存在,后下钻proxy2寻找,如果proy2,中不存在则会回源到源站直接下载依赖,缓存到proy站点中。

2.7 工具——go get

go get

2.7 工具——go mod

go mod

尽量提交之前执行下go tidy,减少构建时无效依赖包的拉取。

3. 测试

3.1 分类

测试分类

  1. 回归测试一般是QA同学手动通过终端回归一些固定的主流程场景。
  2. 集成测试是对系统功能维度做测试验证。
  3. 单元测试测试开发阶段,开发者对单独的函数、模块做功能验证

层级从上至下,测试成本逐渐减低,而测试覆盖率确逐步上升,所以单元测试的覆盖率一定程度上决定这代码的质量。

3.2 单元测试

单元测试

单元测试主要包括,输入,测试单元,输出,以及校对。

单元的概念比较广,包括接口,函数,模块等;用最后的校对来保证代码的功能与我们的预期相符;单测可以保证质量,在整体覆盖率足够的情况下,一定程度上既保证了新功能本身的正确性,又未破坏原有代码的正确性。可以提升效率,在代码有bug的情况下,通过编写单测,可以在一个较短周期内定位和修复问题。

3.2.1 单元测试——规则

单元测试——规则

3.2.2 单元测试——覆盖率

  • 一般覆盖率:50%~60%,较高覆盖率80%+。
  • 测试分支相互独立、全面覆盖。
  • 测试单元粒度足够小,函数单一职责。

3.2.3 单元测试——依赖

单测需要保证稳定性和幂等性,稳定是指相互隔离,能在任何时间,任何环境,运行测试。幂等是指每一次测试运行都应该产生与之前一样的结果。要实现这一目的就要用到mock机制。

单元测试——依赖

单测需要依赖本地的文件,如果文件被修改或者删除测试就会fail。为了保证测试case的稳定性,我们对读取文件函数进行mock,屏蔽对于文件的依赖。

工具:monkey https://github.com/bouk/monkey

是一个开源的mock测试库,可以对method,或者实例的方法进行mock反射,指针赋值。

Mockey Patch的作用域在Runtime,在运行时通过通过Go的unsafe包,能够将内存中函数的地址替换为运行时函
数的地址。将待打桩函数或方法的实现跳转。

在这里插入图片描述
通过patch对Readfineline进行打桩mock,默认返回ine110。

通过deferf卸载mock,这样整个测试函数就摆脱了本地文件的束缚和依赖。

在这里插入图片描述

3.3 基准测试

Go语言还提供了基准测试框架,基准测试是指测试一段程序的运行性能及耗费CPU的程度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/476711.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Java Springboot高考志愿填报辅助系统

一、作品包含 源码数据库全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据库&#xff1a;…

autoware(2)运行自己的数据集

上一节完成了autoware.ai的安装和编译跑通了demo数据集&#xff0c;本将自己录制的数据包用于测试 1.修改点云地图 将加载点云地图的my_map.launch文件复制并命名为my_map_test.launch&#xff0c; &#xff08;1&#xff09;point cloud处替代原来的点云地图为自己的&#…

el-select 和el-tree二次封装

前言 本文章是本人在开发过程中&#xff0c;遇到使用树形数据&#xff0c;动态单选或多选的需求&#xff0c;element中没有这种组件&#xff0c;故自己封装一个&#xff0c;欢迎多多指教 开发环境&#xff1a;element-UI、vue2 组件效果 单选 多选 组件引用 <treeselec…

【LeetCode热题100】栈

这道题一共记录了关于栈的5道题目&#xff1a;删除字符串中所有相邻重复项、比较含退格的字符串、基本计算器II、字符串解码、验证栈序列。 class Solution { public:string removeDuplicates(string s) {string ret;for(auto c : s){if(ret.size() 0 || c ! ret.back()) ret …

《Python基础》之pip换国内镜像源

目录 推荐镜像源网址&#xff1a; 方法一&#xff1a;手动换源 方法二&#xff1a;命令提示符指令换源 临时换源 推荐镜像源网址&#xff1a; 阿里云&#xff1a;Simple Indexhttp://mirrors.aliyun.com/pypi/simple/ 华为云&#xff1a;Index of python-local https://m…

全面击破工程级复杂缓存难题

目录 一、走进业务中的缓存 &#xff08;一&#xff09;本地缓存 &#xff08;二&#xff09;分布式缓存 二、缓存更新模式分析 &#xff08;一&#xff09;Cache Aside Pattern&#xff08;旁路缓存模式&#xff09; 读操作流程 写操作流程 流程问题思考 问题1&#…

Kafka 分区分配及再平衡策略深度解析与消费者事务和数据积压的简单介绍

Kafka&#xff1a;分布式消息系统的核心原理与安装部署-CSDN博客 自定义 Kafka 脚本 kf-use.sh 的解析与功能与应用示例-CSDN博客 Kafka 生产者全面解析&#xff1a;从基础原理到高级实践-CSDN博客 Kafka 生产者优化与数据处理经验-CSDN博客 Kafka 工作流程解析&#xff1a…

[Unity Demo]从零开始制作空洞骑士Hollow Knight第二十集:制作专门渲染HUD的相机HUD Camera和画布HUD Canvas

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、制作HUD Camera以及让两个相机同时渲染屏幕二、制作HUD Canvas 1.制作法力条Soul Orb引入库2.制作生命条Health读入数据3.制作吉欧统计数Geo Counter4.制作…

30. 并发编程

一、什么是多任务 如果一个操作系统上同时运行了多个程序&#xff0c;那么称这个操作系统就是 多任务的操作系统&#xff0c;例如&#xff1a;Windows、Mac、Android、IOS、Harmony 等。如果是一个程序&#xff0c;它可以同时执行多个事情&#xff0c;那么就称为 多任务的程序。…

ElasticSearch学习篇17_《检索技术核心20讲》最邻近检索-局部敏感哈希、乘积量化PQ思路

目录 场景在搜索引擎和推荐引擎中&#xff0c;对相似文章去重是一个非常重要的环节&#xff0c;另外是拍照识花、摇一摇搜歌等场景都可以使用它快速检索。 基于敏感性哈希的检索更擅长处理字面上的相似而不是语义上的相似。 向量空间模型ANN检索加速思路 局部敏感哈希编码 随…

mongodb多表查询,五个表查询

需求是这样的&#xff0c;而数据是从mysql导入进来的&#xff0c;由于mysql不支持数组类型的数据&#xff0c;所以有很多关联表。药剂里找药物&#xff0c;需要药剂与药物的关联表&#xff0c;然后再找药物表。从药物表里再找药物与成分关联表&#xff0c;最后再找成分表。 这里…

《机器人控制器设计与编程》考试试卷**********大学2024~2025学年第(1)学期

消除误解&#xff0c;课程资料逐步公开。 复习资料&#xff1a; Arduino-ESP32机器人控制器设计练习题汇总_arduino编程语言 题-CSDN博客 试卷样卷&#xff1a; 开卷考试&#xff0c;时间&#xff1a; 2024年11月16日 001 002 003 004 005 ……………………装………………………

DataWorks快速入门

DataWorks基于MaxCompute、Hologres、EMR、AnalyticDB、CDP等大数据引擎&#xff0c;为数据仓库、数据湖、湖仓一体等解决方案提供统一的全链路大数据开发治理平台。本文以DataWorks的部分核心功能为例&#xff0c;指导您使用DataWorks接入数据并进行业务处理、周期调度以及数据…

0基础跟德姆(dom)一起学AI NLP自然语言处理01-自然语言处理入门

1 什么是自然语言处理 自然语言处理&#xff08;Natural Language Processing, 简称NLP&#xff09;是计算机科学与语言学中关注于计算机与人类语言间转换的领域. 2 自然语言处理的发展简史 3 自然语言处理的应用场景 语音助手机器翻译搜索引擎智能问答...

Python Matplotlib 安装指南:使用 Miniconda 实现跨 Linux、macOS 和 Windows 平台安装

Python Matplotlib 安装指南&#xff1a;使用 Miniconda 实现跨 Linux、macOS 和 Windows 平台安装 Matplotlib是Python最常用的数据可视化工具之一&#xff0c;结合Miniconda可以轻松管理安装和依赖项。在这篇文章中&#xff0c;我们将详细介绍如何使用Miniconda在Linux、mac…

Cmakelist.txt之win-c-udp-client

1.cmakelist.txt cmake_minimum_required(VERSION 3.16) ​ project(c_udp_client LANGUAGES C) ​ add_executable(c_udp_client main.c) ​ target_link_libraries(c_udp_client wsock32) ​ ​ include(GNUInstallDirs) install(TARGETS c_udp_clientLIBRARY DESTINATION $…

移动充储机器人“小奥”的多场景应用(上)

一、高速公路服务区应用 在高速公路服务区&#xff0c;新能源汽车的充电需求得到“小奥”机器人的及时响应。该机器人配备有储能电池和自动驾驶技术&#xff0c;能够迅速定位至指定充电点&#xff0c;为待充电的新能源汽车提供服务。得益于“小奥”的机动性&#xff0c;其服务…

Mono Repository方案与ReactPress的PNPM实践

ReactPress Github项目地址&#xff1a;https://github.com/fecommunity/reactpress 欢迎Star。 Mono Repository方案与ReactPress的PNPM实践 在当今软件开发领域&#xff0c;Mono Repository&#xff08;简称Monorepo&#xff09;已成为一种流行的代码管理方式&#xff0c;特…

人工智能(AI)与机器学习(ML)基础知识

目录 1. 人工智能与机器学习的核心概念 什么是人工智能&#xff08;AI&#xff09;&#xff1f; 什么是机器学习&#xff08;ML&#xff09;&#xff1f; 什么是深度学习&#xff08;DL&#xff09;&#xff1f; 2. 机器学习的三大类型 &#xff08;1&#xff09;监督式学…

ROS之什么是Node节点和Package包?

1.什么是ROS&#xff1f; 官方术语&#xff1a;ROS&#xff08;Robot Operating System&#xff0c;机器人操作系统&#xff09;是一个开源的、模块化的机器人软件框架。它为机器人开发提供了一套工具和库&#xff0c;用于实现硬件抽象、设备驱动、消息传递、多线程管理等功能…