专栏首页腾讯云TStack专栏一文读懂Go Modules原理

一文读懂Go Modules原理

点击上方“腾讯云TStack”关注我们

获取最in云端资讯和海量技术干货

本文作者 / 阿杜

腾讯云高级工程师

热衷于开源、容器和Kubernetes

目前主要从事镜像仓库、边缘计算

以及云原生架构相关研发工作

1

前 言

In the world of software management there exists a dreaded place called “dependency hell.” The bigger your system grows and the more packages you integrate into your software, the more likely you are to find yourself, one day, in this pit of despair.

依赖管理是一个语言必须要解决的问题,而且随着项目依赖的模块量以及复杂程度不断增加会显得更加重要。Go依赖管理发展历史可以归纳如下:

  • goinstall(2010.02):将依赖的代码库下载到本地,并通过import引用这些库
  • go get(2011.12):go get代替goinstall
  • godep(2013.09):godep提供了一个依赖文件,记录所有依赖具体的版本和路径,编译时将依赖下载到workspace中,然后切换到指定版本,并设置GOPATH访问(解决go get没有版本管理的缺陷)
  • gopkg.in(2014.03):通过import路径中添加版本号来标示不同版本,而实际代码存放于github中,go通过redirect获取代码。例如(import gopkg.in/yaml.v1,实际代码地址为:https://github.com/go-yaml/yaml)
  • vendor(2015.06);Go 1.5版本引入vendor(类似godep),存放于项目根目录,编译时优先使用vendor目录,之后再去GOPATH,GOROOT目录查找(解决GOPATH无法管控依赖变更和丢失的问题)
  • dep(2016.08):dep期望统一Go依赖管理,虽然提供了兼容其它依赖管理工具的功能,但是本质上还是利用GOPATH和vendor解决依赖管理
  • Go Modules(2018.08):Go 1.11发布的官方依赖管理解决方案,并最终统一了Go依赖管理(by Russ Cox)。Go Modules以semantic version(语义版本化)和Minimal Version Selection, MVS(最小版本选择)为核心,相比dep更具稳定性;同时也解决了vendor代码库依赖过于庞大,造成存储浪费的问题

通过如上历史,我们可以看出:Go依赖管理的发展历史,其实就是Go去google的历史(google内部没有强烈的版本管理需求),同时也是典型的社区驱动开发的例子

接下来,我将详细探讨Go Modules的两大核心概念:semantic version(语义化版本)和Minimal Version Selection, MVS(最小版本选择)

2

原 理

semantic version

Go使用semantic version来标识package的版本。具体来说:

  • MAJOR version when you make incompatible API changes(不兼容的修改)
  • MINOR version when you add functionality in a backwards compatible manner(特性添加,版本兼容)
  • PATCH version when you make backwards compatible bug fixes(bug修复,版本兼容)

这里,只要模块的主版本号(MAJOR)不变,次版本号(MINOR)以及修订号(PATCH)的变更都不会引起破坏性的变更(breaking change)。这就要求开发人员尽可能按照semantic version发布和管理模块(实际是否遵守以及遵守的程度不能保证,参考Hyrum's Law)

Minimal Version Selection●

A versioned Go command must decide which module versions to use in each build. I call this list of modules and versions for use in a given build the build list. For stable development, today's build list must also be tomorrow's build list. But then developers must also be allowed to change the build list: to upgrade all modules, to upgrade one module, or to downgrade one module.

The version selection problem therefore is to define the meaning of, and to give algorithms implementing, these four operations on build lists:

  1. Construct the current build list.
  2. Upgrade all modules to their latest versions.
  3. Upgrade one module to a specific newer version.
  4. Downgrade one module to a specific older version.

这里将一次构建(go build)中所依赖模块及其版本列表称为build list,对于一个稳定发展的项目,build list应该尽可能保持不变,同时也允许开发人员修改build list,比如升级或者降级依赖。而依赖管理因此也可以归纳为如下四个操作:

  • 构建项目当前build list
  • 升级所有依赖模块到它们的最新版本
  • 升级某个依赖模块到指定版本
  • 将某个依赖模块降级到指定版本

在Minimal version selection之前,Go的选择算法很简单,且提供了 2 种不同的版本选择算法,但都不正确:

第 1 种算法是 go get 的默认行为:若本地有一个版本,则使用此版本;否则下载使用最新的版本。这种模式将导致使用的版本太老:假设已经安装了B 1.1,并执行 go get 下载,那么go get 不会更新到B 1.2,这样就会导致因为B 1.1太老构建失败或有bug

第 2 种算法是 go get -u 的行为:下载并使用所有模块的最新版本。这种模式可能会因为版本太新而失败:若你运行 go get -u 来下载A依赖模块,会正确地更新到B 1.2。同时也会更新到C 1.3 和E 1.3,但这可能不是 A 想要的,因为这些版本可能未经测试,无法正常工作

这 2 种算法的构建是低保真构建(Low-Fidelity Builds):虽然都想复现模块 A 的作者所使用的构建,但这些构建都因某些不明确的原因而变得有些偏差。在详细介绍最小版本选择算法后,我们将明白为什么最小版本选择算法可以产生高保真的构建:

Minimal version selection assumes that each module declares its own dependency requirements: a list of minimum versions of other modules. Modules are assumed to follow the import compatibility rule—packages in any newer version should work as well as older ones—so a dependency requirement gives only a minimum version, never a maximum version or a list of incompatible later versions.

Then the definitions of the four operations are:

  1. To construct the build list for a given target: start the list with the target itself, and then append each requirement's own build list. If a module appears in the list multiple times, keep only the newest version.
  2. To upgrade all modules to their latest versions: construct the build list, but read each requirement as if it requested the latest module version.
  3. To upgrade one module to a specific newer version: construct the non-upgraded build list and then append the new module's build list. If a module appears in the list multiple times, keep only the newest version.
  4. To downgrade one module to a specific older version: rewind the required version of each top-level requirement until that requirement's build list no longer refers to newer versions of the downgraded module.

Minimal version selection也即最小版本选择,如果光看上述的引用可能会很迷惑(或者矛盾):明明是选择最新的版本(keep only the newest version),为什么叫最小版本选择?

我对最小版本选择算法中'最小'的理解如下:

  • 最小的修改操作
  • 最小的需求列表
  • 最小的模块版本。这里比较的对象是该模块的最新版本:如果项目需要依赖的模块版本是v1.2,而该模块实际最新的版本是v1.3,那么最小版本选择算法会选取v1.2版本而非v1.3(为了尽可能提高构建的稳定性和重复性)。也即'最小版本'表示项目所需要依赖模块的最小版本号(v1.2),而不是该模块实际的最小版本号(v1.1),也并非该模块实际的最大版本号(v1.3)

这里,我们举例子依次对Go Modules最小版本选择的算法细节进行阐述:

Algorithm 1: Construct Build List

There are two useful (and equivalent) ways to define build list construction: as a recursive process and as a graph traversal. The recursive definition of build list construction is as follows. Construct the rough build list for M by starting an empty list, adding M, and then appending the build list for each of M's requirements. Simplify the rough build list to produce the final build list, by keeping only the newest version of any listed module.

简单来说可以通过图遍历以及递归算法(图递归遍历)来构建依赖列表。从根节点出发依次遍历直接依赖B1.2以及C1.2,然后递归遍历。这样根据初始的依赖关系(指定版本:A1->B1.2,A1->C1.2),会按照如下路径选取依赖:

首先构建empty build list,然后从根节点出发递归遍历依赖模块获取rough build list,这样rough build list中可能会存在同一个依赖模块的不同版本(如D1.3与D1.4),通过选取最新版本构建final build list(最终的构建列表一个模块只取一个版本,即这些版本中的最新版),如下:

●Algorithm 2. Upgrade All Modules

一次性升级所有模块(直接&间接)可能是对构建列表最常见的修改,go get -u 就实现了这样的功能。当执行完这个命令后,所有依赖包都将升级为最新版本,如下(Algorithm 1例子基础上进行升级):

这里面添加了新的依赖模块:E1.3,G1.1,F1.1以及C1.3。新rough build list会将新引入的依赖模块和旧的rough build list模块(黄色部分)进行合并,并从中选取最大版本,最终构建final build list(上图红线标识模块)。

为了得到上述结果,需要添加一些依赖模块到A的需求列表中,因为按照正常的构建流程,依赖包不应该包括D1.4和E1.3,而是D1.3和E1.2。这里面就会涉及Algorithm R. Compute a Minimal Requirement List,该算法核心是创建一个能重复构建依赖模块的最小需求列表,也即只保留必须的依赖模块信息(比如直接依赖以及特殊依赖),并存放于go.mod文件中。比如上述例子中A1的go.mod文件会构建如下:

module A1
go 1.14
require (
	B1.2
	C1.3
	D1.4 // indirect
	E1.3 // indirect)

可以看到上述go.mod文件中,没有出现F1.1以及G1.1,这是因为F1.1存在于C1.3的go.mod文件中,而G1.1存在于F1.1的go.mod文件中,因此没有必要将这两个模块填写到A1的go.mod文件中;

而D1.4和E1.3后面添加了indirect标记,这是因为D1.4和E1.3都不会出现在B1.2,C1.3以及它们的依赖模块对应的go.mod文件中,因此必须添加到模块A1的需求列表中(go需要依据这个列表中提供的依赖以及相应版本信息重复构建这个模块,反过来,如果不将D1.4和E1.3添加到go.mod,那么最终模块A1的依赖构建结果就会是D1.3以及E1.2)

另外,这里也可以总结出现indirect标记的两种情况:

  • A1的某个依赖模块没有使用Go Modules(也即该模块没有go.mod文件),那么必须将该模块的间接依赖记录在A1的需求列表中
  • A1对某个间接依赖模块有特殊的版本要求,必须显示指明版本信息(例如上述的D1.4和E1.3),以便Go可以正确构建依赖模块

Algorithm 3. Upgrade One Module

相比一次性升级所有模块,比较好的方式是只升级其中一个模块,并尽量少地修改构建列表。例如,当我们想升级到C1.3时,我们并不想造成不必要的修改,如升级到E1.3以及D1.4。这个时候我们可以选择只升级某个模块,并执行go get命令如下(Algorithm 1例子基础上进行升级):

go get C@1.3

当我们升级某个模块时,会在构建图中将指向这个模块的箭头挪向新版本(A1->C1.2挪向A1->C1.3)并递归引入新的依赖模块。例如在升级C1.2->C1.3时,会新引入F1.1以及G1.1模块(对一个模块的升级或者降级可能会引入其他依赖模块),而新的rough build list(红线)将由旧rough build list(黄色部分)与新模块(C1.3,F1.1以及G1.1)构成,并最终选取其中最大版本合成新的final build list(A1,B1.2,C1.3,D1.4,E1.2,F1.1以及G1.1)

注意最终构建列表中模块D为D1.4而非D1.3,这是因为当升级某个模块时,只会添加箭头,引入新模块;而不会减少箭头,从而删除或者降级某些模块;

比如若从 A 至 C 1.3 的新箭头替换了从 A 至 C 1.2 的旧箭头,升级后的构建列表将会丢掉 D 1.4。也即是这种对 C 的升级将导致 D 的降级(降级为D1.3),这明显是预料之外的,且不是最小修改

一旦我们完成了构建列表的升级,就可运行前面的算法 R 来决定如何升级需求列表(go.mod)。这种情况下,我们最终将以C1.3替换C 1.2,但同时也会添加一个对D1.4的新需求,以避免D的意外降级(因为按照算法1,D1.3才会出现在最终构建列表中)。如下:

module A1
go 1.14
require (
	B1.2
	C1.3
	D1.4 // indirect)

●Algorithm 4. Downgrade One Module

在升级某个模块后,可能会发现bug并需要降级到某个旧版本。当降级某个模块时,会在构建图中将这个模块的高版本删除,同时回溯删除依赖这些高版本的模块,直到查找到不依赖这些高版本的最新模块版本为止。比如这里我们将D降级到D1.2版本,如下(Algorithm 1例子基础上进行降级):

go get D@1.2

这里将D降级为D1.2,会先删除D1.3以及D1.4模块,然后回溯删除B1.2以及C1.2模块,最终确定到B1.1以及C1.1版本(它们分别是B和C不依赖>=D1.3模块的最新版本了),如下:

在确定直接依赖是B1.1以及C1.1之后,会递归将其依赖模块引入,并添加指定版本D1.2,那么按照算法1可以很容易得出构建列表为:A1,B1.1,D1.2(D1.1 vs D1.2),E1.1以及C1.1

同时,为了保证最小修改,其它不需要降级的模块我们需要尽可能保留,比如:E1.2。这样,final build list就为:A1,B1.1,D1.2(D1.1 vs D1.2),E1.2(E1.1 vs E1.2)以及C1.1

另外,根据算法R,降级操作之后A1模块的需求列表变更如下:

module A1
go 1.14
require (
	B1.1
	C1.1
	D1.2 // indirect
	E1.2 // indirect)

这里,如果A1最开始依赖的模块是C1.3而非C1.2,那么C1.3将会在构建图中保留下来(因为C1.3并没有依赖D模块)

3

go mod&sum格式

go.mod以及go.sum一般会成对出现在项目根目录中。其中,go.mod负责记录需求列表(用于构建依赖模块);而go.sum用于记录安全性以及完整性校验,下面依次介绍这两种文件格式:

1.go.mod

  • module:代表go模块名,也即被其它模块引用的名称,位于文件第一行
  • require:最小需求列表(依赖模块及其版本信息)
  • replace:通过replace将一个模块的地址转换为其它地址,用于解决某些依赖模块地址发生改变的场景。同时import命令可以无需改变(无侵入)
  • exclude:明确排除一些依赖包中不想导入或者有问题的版本
   module tkestack.io/tke      go 1.12      replace (           // wait https://github.com/chartmuseum/storage/pull/34 to be merged           github.com/chartmuseum/storage => github.com/choujimmy/storage v0.5.1-0.20191225102245-210f7683d0a6           github.com/deislabs/oras => github.com/deislabs/oras v0.8.0           // wait https://github.com/dexidp/dex/pull/1607 to be merged           github.com/dexidp/dex => github.com/choujimmy/dex v0.0.0-20191225100859-b1cb4b898bb7           k8s.io/client-go => k8s.io/client-go v0.17.0   )      require (           github.com/AlekSi/pointer v1.1.0           github.com/Azure/go-autorest v13.3.1+incompatible // indirect           github.com/Masterminds/semver v1.4.2 // indirect           github.com/NYTimes/gziphandler v1.1.1 // indirect           github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect           github.com/aws/aws-sdk-go v1.25.7           github.com/bitly/go-simplejson v0.5.0           github.com/blang/semver v3.5.1+incompatible           github.com/casbin/casbin/v2 v2.1.2           github.com/chartmuseum/storage v0.5.0           k8s.io/client-go v12.0.0+incompatible           ...   )

2.go.sum

   <模块> <版本>[/go.mod] <哈希>
  • 带有/go.mod代表该版本模块的go.mod文件hash值
  • 不带/go.mod代表该版本模块源代码的hash值
   cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=   cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=   cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=   cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=   cloud.google.com/go v0.41.0 h1:NFvqUTDnSNYPX5oReekmB+D+90jrJIcVImxQ3qrBVgM=   cloud.google.com/go v0.41.0/go.mod h1:OauMR7DV8fzvZIl2qg6rkaIhD/vmgk4iwEw/h6ercmg=   contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA=   github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI=   github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE=   github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=   github.com/Azure/azure-sdk-for-go v31.1.0+incompatible h1:5SzgnfAvUBdBwNTN23WLfZoCt/rGhLvd7QdCAaFXgY4=   github.com/Azure/azure-sdk-for-go v31.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=   github.com/Azure/azure-sdk-for-go v35.0.0+incompatible h1:PkmdmQUmeSdQQ5258f4SyCf2Zcz0w67qztEg37cOR7U=   github.com/Azure/azure-sdk-for-go v35.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=

go.sum文件可以不存在,当go.sum文件不存在时默认会到远程校验数据库进行校验(通过GOSUMDB设置地址),当然也可以设置为不校验(GONOSUMDB)

4

总 结

  • 对单个模块的修改可能会连带影响其它模块
  • Go Modules构建列表受当前模块构建状态影响
  • 在go.mod没有明确指明直接依赖模块版本的情况下,go build默认会查找直接依赖模块的最新版本进行构建(例如这里的B1.2以及C1.3)
  • 最小构建算法中的'最小'代表:最小修改&最小需求列表&依赖模块的最小版本
  • 升级某个模块不会引起其它模块的降级或者删除
  • 降级某个模块会在构建图中将这个模块的高版本删除,同时回溯删除依赖这些高版本的模块,直到查找到不依赖这些高版本的最新模块版本为止,同时会保留其它不需要降级的模块
  • go.mod文件出现indirect标记的情况有如下两种:
    • A1的某个模块没有使用Go Modules(也即该模块没有go.mod文件),那么必须将该模块的间接依赖记录在A1的需求列表中
    • A1对某个间接依赖模块有特殊的版本要求,必须显示指明版本信息(例如上述的D1.4和E1.3),以便go可以正确构建依赖模块

5

最佳实践

  • 尽量不要手动修改go.mod文件,通过go命令来操作go.mod文件
  • 尽量遵守semantic version(语义化版本)发布和管理模块
  • 通过go build编译项目时,如果在go.mod文件中指定了直接依赖模块版本,则根据最小版本选择算法会下载对应版本;否则go build会默认自动下载直接依赖模块的最新semantic version,若没有semantic version则自动生成标签:(v0.0.0)-(提交UTC时间戳)-(commit id前12位)作为版本标识,例如:github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7
  • 利用go mod tidy进行自动整理操作。该模块会清理需求列表:删除不需要的需求项,添加需要的需求项
  • 利用go get升级或者降级某个依赖模块;go get -u升级所有依赖模块(直接以及间接)
  • 本地调试:如果本地有依赖模块还未发布,则可以利用如下方法进行调试:
    • replace:将依赖模块修改成本地依赖包地址,这样就可以在本地修改依赖包的同时进行编译调试了(需要注意go.mod文件内容发生修改,注意不要提交):replace k8s.io/client-go => ~/go/src/k8s.io/client-go v0.17.0-dirty-fix
    • vendor:默认情况下go build会忽略vendor目录;当添加-mod=vendor选项时,go build会优先查找vendor目录下的依赖模块。因此可以将本地开发的依赖包放置在vendor目录,并将vendor通过.gitignore文件设置在版本控制之外,这样既可以满足本地调试,同时也不影响版本提交
  • 当需要列举本项目所有依赖模块时(包括间接依赖)使用:go list -m all;而列举某个依赖模块的所有版本使用:go list -m -versions xxx,例如:go list -m -versions k8s.io/client-go
  • 当一些依赖存在问题时,可以通过go clean -modcache清理已下载的依赖文件
  • GO111MODULE值含义如下(建议强制开启):
    • off:强制关闭Go Modules,使用GOPATH
    • on:强制开启Go Modules(建议)
    • auto:如果当前模块在$GOPATH/src中,则不使用Go Modules;如果该模块不存在$GOPATH/src下,且拥有go.mod文件则使用Go Modules
  • 设置GOPROXY为:GOPROXY=https://goproxy.cn,direct,代表先从代理服务器https://goproxy.cn下载依赖,如果失败(such as 404)则直接从原地址(such as github)下载

6

Refs

  • Minimal Version Selection:https://research.swtch.com/vgo-mvs
  • The Principles of Versioning in Go: https://research.swtch.com/vgo-principles#sat-example
  • Golang 版本管理系列 翻译 11 篇全:https://github.com/vikyd/note/tree/master/go_and_versioning
  • kubernetes-reading-notes: https://github.com/duyanghao/kubernetes-reading-notes

没看过瘾?这里还有

阿杜系列大作

● Harbor企业级实践丨零侵入改造!

● Harbor企业级实践丨20倍性能提升so easy!

听说长得好看的人都点了赞和在看!

本文分享自微信公众号 - 腾讯云TStack(gh_035269c8aa5f)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-02-26

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 一文读懂 Kubernetes APIServer 原理

    作者杜杨浩,腾讯云高级工程师,热衷于开源、容器和Kubernetes。目前主要从事Kubernetes集群高可用&备份还原,以及边缘计算相关研发工作 前言 整...

    腾讯云原生
  • 一文读懂Innodb MVCC实现原理

    • 不可重复读:事物A同样的查询条件,查询多次,读出的数据不一样,不一样的侧重点在于 update和delete

    公众号-Java编程大本营
  • 一文读懂 select count(*) 底层原理

    “SELECT COUNT( * ) FROM TABLE” 是个再常见不过的 SQL 需求了。在 MySQL 的使用规范中,我们一般使用事务引擎 InnoDB...

    Python数据科学
  • 一文读懂SpringBoot持久层开发原理

    石的三次方
  • 一文读懂 HugePages(大内存页)的原理

    在介绍 HugePages 之前,我们先来回顾一下 Linux 下 虚拟内存 与 物理内存 之间的关系。

    用户7686797
  • 怎么让Go Modules使用私有依赖模块

    Go语言官方的依赖包管理工具Go Modules已经发布很久,从1.14版本开始更是默认自动开启了Go Modules的支持,相信很多人公司里的项目都从go v...

    KevinYan
  • 一文带你读懂图像处理工作原理

    这里,如果小矩阵的点积与大矩阵的所有3x3大小的部分完成。 点积表示每个元素乘以其各自的元素,例如。 131 *( - 1),162 * 0,232 * 1等。

    AI研习社
  • [系列] - 使用 go modules 包管理工具(一)

    我想实现一个开箱即用的 API 框架的轮子,这个轮子是基于 Gin 基础上开发的。

    新亮
  • 一文读懂CAP定理

    分布式系统(distributed system)正变得越来越重要,大型网站几乎都是分布式的。

    用户1260737
  • 概念深奥看不懂?一文读懂元学习原理

    从手机应用的内容推荐,到寻找暗物质,机器学习算法已经改变了人们的生活和工作方式。但是,传统机器学习算法很大程度上依赖标于特定的标注数据,这一定程度上限制了在某些...

    机器之心
  • 干货满满的 Go Modules 和 goproxy.cn

    大家好,我是一只普通的煎鱼,周四晚上很有幸邀请到 goproxy.cn 的作者 @盛傲飞(@aofei) 到 Go 夜读给我们进行第 61 期 《Go Modu...

    madneal
  • HDFS原理 | 一文读懂HDFS架构与设计

    HDFS(Hadoop Distributed File System)是我们熟知的Hadoop分布式文件系统,是一个高容错的系统,能提供高吞吐量的数据访问,非...

    大数据技术架构
  • 迁移到 Go Modules

    Go 项目使用多种依赖管理策略,其中对 vendor 包的管理有两个比较流行的工具 dep 和 glide,但他们在行为上有很大的差异,而且并不是总能很好地同时...

    Dabelv
  • 一文搞懂 ThreadLocal 原理

    当多线程访问共享可变数据时,涉及到线程间同步的问题,并不是所有时候,都要用到共享数据,所以就需要线程封闭出场了。

    武培轩
  • Go Modules 终极入门

    Go modules 是 Go 语言中正式官宣的项目依赖解决方案,Go modules(前身为vgo)于 Go1.11 正式发布,在 Go1.14 已经准备好,...

    madneal
  • 【初识Go】| Day9 包管理

    现在随便一个小程序的实现都可能包含超过10000个函数。然而作者一般只需要考虑其中很小的一部分和做很少的设计,因为绝大部分代码都是由他人编写的,它们通过类似包或...

    yussuy
  • 一文读懂机器学习大杀器XGBoost原理

    【磐创AI导读】:本文详细介绍了Xgboost的原理。欢迎大家点击上方蓝字关注我们的公众号:磐创AI。

    磐创AI
  • 一文读懂:无人机无线电干扰原理

    1、引言 近年来无人机(本文指民用多轴飞行器)正以空前的速度普及,由此引发的关于安全的忧虑日益增多。许多有关部门甚至个人都希望采取一些措施,阻止无人机飞临敏感区...

    机器人网
  • 一文读懂 SuperEdge 边缘容器架构与原理

    作者杜杨浩,腾讯云高级工程师,热衷于开源、容器和Kubernetes。目前主要从事镜像仓库,Kubernetes集群高可用&备份还原,以及边缘计算相关研发工作...

    腾讯云原生

扫码关注云+社区

领取腾讯云代金券