设置通过SSH秘钥与GitHub进行认证和通信

连接GitHub已有的代码仓库时,可以使用SSH秘钥(公私钥)进行认证。如何创建SSH秘钥并将其添加至GitHub,请参考GitHub的帮助文档https://docs.github.com/cn/authentication/connecting-to-github-with-ssh。

需要注意的步骤是:

  • 你需要在家目录里创建.ssh文件夹,把SSH私钥拷贝进去,公钥不用拷贝进去。公钥的内容需要粘贴到你的GitHub账户网站的设置里,请参考https://docs.github.com/cn/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account。
  • SSH私钥的文件名应该是id_rsa,因为id_rsa是git默认查找的私钥名字之一,否则就找不到私钥,会报错“[email protected]: Permission denied (publickey).”。如果你的SSH私钥的文件名不是id_rsa,那么应该改名为id_rsa。
  • 如果你使用的是Ubuntu等Linux操作系统,私钥文件不能有被其他人读写的权限:
chmod o-rwx ./id_rsa
chmod g-rwx ./id_rsa

完成设置后,就可以用家目录里的.ssh文件夹里的SSH私钥与GitHub进行认证和通信了。打开Git Bash,执行命令:

ssh -T [email protected]

如果输出以下信息:

Hi yourusername! You've successfully authenticated, but GitHub does not provide shell access.

说明通过SSH秘钥与GitHub进行认证和通信设置成功。

参考

https://docs.github.com/cn/authentication/connecting-to-github-with-ssh https://docs.github.com/en/authentication/troubleshooting-ssh/error-permission-denied-publickey

Go语言build等工具详解

Go语言自带一套完整的命令行工具,你可以通过在命令行中执go命令来查看它们:

Go is a tool for managing Go source code.
Go是一个管理Go源代码的工具。

Usage:
用法

	go <command> [arguments]
	go <命令> [参数]

The commands are:
命令如下:

	bug         start a bug report 开始报告一个bug
	build       compile packages and dependencies 编译包和依赖项
	clean       remove object files and cached files 删除目标文件和缓存文件
	doc         show documentation for package or symbol 显示包或符号的文档
	env         print Go environment information 打印Go环境信息
	fix         update packages to use new APIs 更新软件包以使用新的API
	fmt         gofmt (reformat) package sources gofmt(重新格式化)包源
	generate    generate Go files by processing source 通过处理源生成Go文件
	get         add dependencies to current module and install them 将依赖项添加到当前模块并安装它们
	install     compile and install packages and dependencies 编译和安装包和依赖项
	list        list packages or modules 列出包或模块
	mod         module maintenance 模块维护
	work        workspace maintenance 工作空间维护
	run         compile and run Go program 编译并运行Go程序
	test        test packages 测试软件包
	tool        run specified go tool 运行指定的go工具
	version     print Go version 打印Go版本号
	vet         report likely mistakes in packages 报告包中可能出现的错误

Use "go help <command>" for more information about a command.
使用“go help <命令>”获取有关命令的更多信息。

Additional help topics:
其他帮助主题:

	buildconstraint build constraints 构建约束
	buildmode       build modes 构建模式
	c               calling between Go and C Go和C之间的调用
	cache           build and test caching 构建和测试缓存
	environment     environment variables 环境变量
	filetype        file types 文件类型
	go.mod          the go.mod file go.mod文件
	gopath          GOPATH environment variable GOPATH环境变量
	gopath-get      legacy GOPATH go get 遗留的GOPATH模式的go get命令
	goproxy         module proxy protocol 模块代理协议
	importpath      import path syntax 导入路径的语法
	modules         modules, module versions, and more 模块、模块版本等
	module-get      module-aware go get 模块模式的go get命令
	module-auth     module authentication using go.sum 使用go.sum进行模块认证
	packages        package lists and patterns 软件包的清单和模式
	private         configuration for downloading non-public code 下载非公开代码的配置
	testflag        testing flags 测试标志
	testfunc        testing functions 测试函数
	vcs             controlling version control with GOVCS 使用GOVCS进行版本控制

Use "go help <topic>" for more information about that topic.
使用“go help <主题>”获取有关该主题的更多信息。

以上是Go 1.18版本的go命令的输出。

我们使用这些工具开发Go语言项目,因此有必要熟识和掌握这些工具的使用。

go help 查看指定命令或主题的帮助文档

使用go help <命令>go help <主题>查看这个命令或主题的帮助文档,例如使用go help build查看go build命令详细信息。

go bug开始报告一个bug

$ go help bug
usage: go bug
用法:go bug

Bug opens the default browser and starts a new bug report.
Bug打开默认浏览器并开始一个新的bug报告。
The report includes useful system information.
该报告包括有用的系统信息。

go build编译包和依赖项

$ go help build
usage: go build [-o output] [build flags] [packages]
用法:go build [-o output] [build flags] [packages]

Build compiles the packages named by the import paths,
along with their dependencies, but it does not install the results.
Build编译由导入路径命名的包以及它们的依赖项,但它不会安装结果。

If the arguments to build are a list of .go files from a single directory,
build treats them as a list of source files specifying a single package.
如果构建参数是来自单个目录的.go文件的列表,则构建将它们视为指定的单个包的源文件列表。

When compiling packages, build ignores files that end in '_test.go'.
编译包时,build会忽略以'_test.go'结尾的文件。

When compiling a single main package, build writes
the resulting executable to an output file named after
the first source file ('go build ed.go rx.go' writes 'ed' or 'ed.exe')
or the source code directory ('go build unix/sam' writes 'sam' or 'sam.exe').
The '.exe' suffix is added when writing a Windows executable.
编译单个main包时,build将生成的可执行文件写入以第一个源文件命名的输出文件('go build ed.go rx.go'写入'ed'或'ed.exe')或源代码目录('go build unix/sam'写入'sam'或'sam.exe')。编写Windows可执行文件时会添加“.exe”后缀。

When compiling multiple packages or a single non-main package,
build compiles the packages but discards the resulting object,
serving only as a check that the packages can be built.
当编译多个包或单个非main包时,build编译包但丢弃生成的对象,仅用作检查包是否可以构建。

The -o flag forces build to write the resulting executable or object
to the named output file or directory, instead of the default behavior described
in the last two paragraphs. If the named output is an existing directory or
ends with a slash or backslash, then any resulting executables
will be written to that directory.
-o 标志强制构建将生成的可执行文件或对象写入指定的输出文件或目录,而不是最后两段中描述的默认行为。如果命名的输出是现有目录或以斜杠或反斜杠结尾,则任何生成的可执行文件都将写入该目录。

The -i flag installs the packages that are dependencies of the target.
-i 标志安装作为目标依赖项的包。
The -i flag is deprecated. Compiled packages are cached automatically.
-i 标志已弃用。编译的包会被自动缓存。

The build flags are shared by the build, clean, get, install, list, run,
and test commands:
构建标志由build,clean,get,install,list,run和test命令共享:

	-a
force rebuilding of packages that are already up-to-date. 强制重建已经是最新版本的包。
	-n
		print the commands but do not run them.打印命令但不运行它们。
	-p n
		the number of programs, such as build commands or
		test binaries, that can be run in parallel.
		The default is GOMAXPROCS, normally the number of CPUs available.
可以并行运行的程序的数量,例如构建命令或测试二进制文件。默认值为GOMAXPROCS,通常是可用的CPU数量。
	-race
		enable data race detection.
		Supported only on linux/amd64, freebsd/amd64, darwin/amd64, darwin/arm64, windows/amd64,
		linux/ppc64le and linux/arm64 (only for 48-bit VMA).
启用数据竞争检测。
仅在linux/amd64、freebsd/amd64、darwin/amd64、darwin/arm64、windows/amd64、linux/ppc64le和linux/arm64操作系统和CPU架构上受支持(仅适用于48位VMA)。
	-msan
		enable interoperation with memory sanitizer.
		Supported only on linux/amd64, linux/arm64
		and only with Clang/LLVM as the host C compiler.
		On linux/arm64, pie build mode will be used.
启用与内存清理程序的互操作。
仅在linux/amd64、linux/arm64和Clang/LLVM作为主机C编译器时受支持。
在linux/arm64上,将使用pie构建模式。
	-asan
		enable interoperation with address sanitizer.
		Supported only on linux/arm64, linux/amd64.
启用与地址清理程序的互操作。
仅在linux/arm64、linux/amd64上受支持。
	-v
		print the names of packages as they are compiled. 在编译时打印包的名称。
	-work
		print the name of the temporary work directory and
		do not delete it when exiting.
打印临时工作目录的名称,并在退出时不要删除它。
	-x
		print the commands. 打印命令。

	-asmflags '[pattern=]arg list'
arguments to pass on each go tool asm invocation. 传递给每个go tool asm调用的参数。
	-buildmode mode
build mode to use. See 'go help buildmode' for more. 要使用的构建模式。 有关更多信息,请参阅“go help buildmode”。
	-buildvcs
		Whether to stamp binaries with version control information
		("true", "false", or "auto"). By default ("auto"), version control
		information is stamped into a binary if the main package, the main module
		containing it, and the current directory are all in the same repository.
		Use -buildvcs=false to always omit version control information, or
		-buildvcs=true to error out if version control information is available but
		cannot be included due to a missing tool or ambiguous directory structure.
是否使用版本控制信息(“true”、“false”或“auto”)标记二进制文件。默认情况下(“auto”),如果main包、包含它的主模块和当前目录都在同一个存储库中,则版本控制信息被标记为二进制文件。
使用-buildvcs=false始终忽略版本控制信息,或-buildvcs=true如果版本控制信息可用但由于缺少工具或不明确的目录结构而无法包含,则输出错误信息。
	-compiler name
		name of compiler to use, as in runtime.Compiler (gccgo or gc). 要使用的编译器的名称,即runtime.Compiler(gccgo或gc)。
	-gccgoflags '[pattern=]arg list'
arguments to pass on each gccgo compiler/linker invocation. 传递给每个gccgo编译器/链接器调用的参数。
	-gcflags '[pattern=]arg list'
arguments to pass on each go tool compile invocation. 传递给每个go工具编译调用的参数。
	-installsuffix suffix
		a suffix to use in the name of the package installation directory,
		in order to keep output separate from default builds.
		If using the -race flag, the install suffix is automatically set to race
		or, if set explicitly, has _race appended to it. Likewise for the -msan
		and -asan flags. Using a -buildmode option that requires non-default compile
		flags has a similar effect.
在包安装目录的名称中使用的后缀,以便将输出与默认构建分开。
如果使用-race标志,安装后缀会自动设置为race,或者,如果明确设置,则会附加_race。-msan和-asan标志也是如此。使用需要非默认编译标志的-buildmode选项具有类似的效果。
	-ldflags '[pattern=]arg list'
arguments to pass on each go tool link invocation. 传递给每个go工具链接调用的参数。
	-linkshared
		build code that will be linked against shared libraries previously
		created with -buildmode=shared.
构建代码将链接到以前使用-buildmode=shared创建的共享库。
	-mod mode
		module download mode to use: readonly, vendor, or mod.
		By default, if a vendor directory is present and the go version in go.mod
		is 1.14 or higher, the go command acts as if -mod=vendor were set.
		Otherwise, the go command acts as if -mod=readonly were set.
		See https://golang.org/ref/mod#build-commands for details.
要使用的模块下载模式:readonly、vendor或mod。
默认情况下,如果存在vendor目录并且go.mod中的go版本为1.14或更高版本,则go命令的行为就像设置了-mod=vendor一样。
否则,go命令的行为就像设置了-mod=readonly一样。
有关详细信息,请参阅https://golang.org/ref/mod#build-commands。
	-modcacherw
		leave newly-created directories in the module cache read-write
		instead of making them read-only.
将新创建的目录保留在模块缓存中可读写,而不是使它们只读。
	-modfile file
		in module aware mode, read (and possibly write) an alternate go.mod
		file instead of the one in the module root directory. A file named
		"go.mod" must still be present in order to determine the module root
		directory, but it is not accessed. When -modfile is specified, an
		alternate go.sum file is also used: its path is derived from the
		-modfile flag by trimming the ".mod" extension and appending ".sum".
在模块感知模式下,读取(并可能写入)另一个go.mod文件,而不是模块根目录中的那个go.mod文件。一个名为“go.mod”的文件必须仍然存在才能确定模块根目录,但它不会被访问。当指定-modfile时,还使用备用go.sum文件:它的路径是从-modfile标志通过剪去“.mod”扩展名并附加“.sum”扩展名而得出的。
	-overlay file
		read a JSON config file that provides an overlay for build operations.
		The file is a JSON struct with a single field, named 'Replace', that
		maps each disk file path (a string) to its backing file path, so that
		a build will run as if the disk file path exists with the contents
		given by the backing file paths, or as if the disk file path does not
		exist if its backing file path is empty. Support for the -overlay flag
		has some limitations: importantly, cgo files included from outside the
		include path must be in the same directory as the Go package they are
		included from, and overlays will not appear when binaries and tests are
		run through go run and go test respectively.
读取一个为构建操作提供覆盖的JSON配置文件。
该文件是一个JSON结构,具有一个名为“Replace”的字段,它将每个磁盘文件路径(一个字符串)映射到其后备文件路径,以便构建将运行,就好像磁盘文件路径存在,其路径内容由后备文件路径给出,或者如果其后备文件路径为空,则好像磁盘文件路径不存在。对-overlay标志的支持有一些限制:重要的是,从包含路径之外包含的cgo文件必须与包含它们的Go包位于同一目录中,并且通过go run和go test分别运行二进制文件和测试时不会使用覆盖文件。
	-pkgdir dir
		install and load all packages from dir instead of the usual locations.
		For example, when building with a non-standard configuration,
		use -pkgdir to keep generated packages in a separate location.
从dir而不是通常的位置安装和加载所有包。
例如,使用非标准的配置进行构建时,使用-pkgdir将生成的包保存在单独的位置。
	-tags tag,list
		a comma-separated list of build tags to consider satisfied during the
		build. For more information about build tags, see the description of
		build constraints in the documentation for the go/build package.
		(Earlier versions of Go used a space-separated list, and that form
		is deprecated but still recognized.)
在构建过程中使用的构建标签的逗号分隔列表。有关构建标签的更多信息,请参阅go/build包文档中的构建约束描述。
(早期版本的Go使用空格分隔的列表,这种形式已被弃用但仍可识别。)
	-trimpath
		remove all file system paths from the resulting executable.
		Instead of absolute file system paths, the recorded file names
		will begin either a module path@version (when using modules),
		or a plain import path (when using the standard library, or GOPATH).
从生成的可执行文件中删除所有文件系统路径。
记录的文件名不是绝对文件系统路径,而是以模块路径@版本号(使用模块时)或普通导入路径(使用标准库或GOPATH时)开头。
	-toolexec 'cmd args'
		a program to use to invoke toolchain programs like vet and asm.
		For example, instead of running asm, the go command will run
		'cmd args /path/to/asm <arguments for asm>'.
		The TOOLEXEC_IMPORTPATH environment variable will be set,
		matching 'go list -f {{.ImportPath}}' for the package being built.
一个用于调用vet和asm等工具链程序的程序。
例如,go命令不会运行asm,而是运行'cmd args /path/to/asm <arguments for asm>'。
将设置TOOLEXEC_IMPORTPATH环境变量,匹配正在构建的包的“go list -f {{.ImportPath}}”。
The -asmflags, -gccgoflags, -gcflags, and -ldflags flags accept a
space-separated list of arguments to pass to an underlying tool
during the build. To embed spaces in an element in the list, surround
it with either single or double quotes. The argument list may be
preceded by a package pattern and an equal sign, which restricts
the use of that argument list to the building of packages matching
that pattern (see 'go help packages' for a description of package
patterns). Without a pattern, the argument list applies only to the
packages named on the command line. The flags may be repeated
with different patterns in order to specify different arguments for
different sets of packages. If a package matches patterns given in
multiple flags, the latest match on the command line wins.
For example, 'go build -gcflags=-S fmt' prints the disassembly
only for package fmt, while 'go build -gcflags=all=-S fmt'
prints the disassembly for fmt and all its dependencies.
-asmflags、-gccgoflags、-gcflags和-ldflags标志接受以空格分隔的参数列表,以在构建期间传递给底层工具。要在列表中的元素中嵌入空格,请用单引号或双引号将其括起来。参数列表前面可能有一个包模式和一个等号,这限制了该参数列表的使用以构建与该模式匹配的包(有关包模式的描述,请参见“go help packages”)。如果没有模式,参数列表仅适用于命令行上给出名字的包。这些标志可以以不同的模式重复,以便为不同的包集指定不同的参数。如果一个包与多个标志中给出的模式匹配,则命令行上的最新匹配项获胜。
例如,'go build -gcflags=-S fmt'仅打印包fmt的反汇编,而'go build -gcflags=all=-S fmt'打印fmt及其所有依赖项的反汇编。

For more about specifying packages, see 'go help packages'.
For more about where packages and binaries are installed,
run 'go help gopath'.
For more about calling between Go and C/C++, run 'go help c'.
有关指定包的更多信息,请参阅“go help packages”。
有关软件包和二进制文件安装位置的更多信息,请运行“go help gopath”。
有关Go和C/C++之间调用的更多信息,请运行“go help c”。

Note: Build adheres to certain conventions such as those described
by 'go help gopath'. Not all projects can follow these conventions,
however. Installations that have their own conventions or that use
a separate software build system may choose to use lower-level
invocations such as 'go tool compile' and 'go tool link' to avoid
some of the overheads and design decisions of the build tool.
注意:构建遵循某些约定,例如“go help gopath”所描述的约定。然而,并非所有项目都遵循这些约定。有自己的约定或使用单独的软件构建系统的安装可以选择使用较低级别的调用,例如“go tool compile”和“go tool link”,以避免构建工具的一些开销和设计决策。

See also: go install, go get, go clean.
另请参阅:go install, go get, go clean。

go clean删除目标文件和缓存文件

$ go help clean
usage: go clean [clean flags] [build flags] [packages]
用法:go clean [clean flags] [build flags] [packages]

Clean removes object files from package source directories.
The go command builds most objects in a temporary directory,
so go clean is mainly concerned with object files left by other
tools or by manual invocations of go build.
Clean从包源目录中删除对象文件。
go命令在临时目录中构建大多数对象,因此go clean主要涉及其他工具或手动调用go build留下的对象文件。

If a package argument is given or the -i or -r flag is set,
clean removes the following files from each of the
source directories corresponding to the import paths:
如果给定包参数或设置了-i或-r标志,则clean会从与导入路径对应的每个源目录中删除以下文件:

	_obj/            old object directory, left from Makefiles 旧对象目录,遗留自Makefiles
	_test/           old test directory, left from Makefiles 旧的测试目录,遗留自Makefiles
	_testmain.go     old gotest file, left from Makefiles 旧的gotest文件,遗留自Makefiles
	test.out         old test log, left from Makefiles 旧的测试日志,遗留自Makefiles
	build.out        old test log, left from Makefiles 旧的测试日志,遗留自Makefiles
	*.[568ao]        object files, left from Makefiles 目标文件,遗留自Makefiles

	DIR(.exe)        from go build 来自go build
	DIR.test(.exe)   from go test -c 来自go test -c
	MAINFILE(.exe)   from go build MAINFILE.go 来自go build MAINFILE.go
	*.so             from SWIG 来自SWIG

In the list, DIR represents the final path element of the
directory, and MAINFILE is the base name of any Go source
file in the directory that is not included when building
the package.
在列表中,DIR表示目录的最终路径元素,而MAINFILE是该目录中构建包时未包含的任何Go源文件的基本名称。

The -i flag causes clean to remove the corresponding installed
archive or binary (what 'go install' would create).
-i标志让clean删除相应的已安装存档或二进制文件(这些是'go install' 会创建的)。

The -n flag causes clean to print the remove commands it would execute,
but not run them.
-n标志让clean打印它将执行的删除命令,但不运行它们。

The -r flag causes clean to be applied recursively to all the
dependencies of the packages named by the import paths.
-r标志让clean递归地应用于导入路径给出名字的包的所有依赖项。

The -x flag causes clean to print remove commands as it executes them.
-x标志让clean在执行删除命令时打印它们。

The -cache flag causes clean to remove the entire go build cache.
-cache标志让clean删除整个go build缓存。

The -testcache flag causes clean to expire all test results in the
go build cache.
-testcache标志会导致clean使go-build缓存中的所有测试结果过期。

The -modcache flag causes clean to remove the entire module
download cache, including unpacked source code of versioned
dependencies.
-modcache标志会让clean删除整个模块下载缓存,包括带版本号的依赖项的未解包的源代码。

The -fuzzcache flag causes clean to remove files stored in the Go build
cache for fuzz testing. The fuzzing engine caches files that expand
code coverage, so removing them may make fuzzing less effective until
new inputs are found that provide the same coverage. These files are
distinct from those stored in testdata directory; clean does not remove
those files.
-fuzzcache标志会导致clean删除Go构建缓存中存储的用于模糊测试的文件。模糊引擎缓存文件扩展代码覆盖率测试,因此在找到提供相同覆盖率的新输入之前,删除这些文件可能会降低模糊的有效性。这些文件不同于存储在testdata目录中的文件;clean不会删除这些文件。

For more about build flags, see 'go help build'.
有关构建标志的详细信息,请参阅“go help build”。

For more about specifying packages, see 'go help packages'.
有关指定包的详细信息,请参阅“go help packages”。

go doc显示包或符号(symbol)的文档

$ go help doc
usage: go doc [doc flags] [package|[package.]symbol[.methodOrField]]
用法:go doc [doc flags] [package|[package.]symbol[.methodOrField]]

Doc prints the documentation comments associated with the item identified by its
arguments (a package, const, func, type, var, method, or struct field)
followed by a one-line summary of each of the first-level items "under"
that item (package-level declarations for a package, methods for a type,
etc.).
Doc打印与其参数给出的条目相关的文档注释(包,常量,函数,类型,变量,方法或结构字段),然后是每个第一级条目(软件包的包级声明,类型的方法等)“下”的单行注释。

Doc accepts zero, one, or two arguments.
Doc接收0个,1个或2个参数。

Given no arguments, that is, when run as
如果没有给出参数,即执行

	go doc

it prints the package documentation for the package in the current directory.
If the package is a command (package main), the exported symbols of the package
are elided from the presentation unless the -cmd flag is provided.
它打印输出当前目录中的软件包的包文档。
如果当前目录中的软件包是命令(main包),则除非提供-cmd标志,否则包的导出符号将从演示中省略。

When run with one argument, the argument is treated as a Go-syntax-like
representation of the item to be documented. What the argument selects depends
on what is installed in GOROOT and GOPATH, as well as the form of the argument,
which is schematically one of these:
当使用一个参数运行时,该参数被视为要输出文档的条目的类似于Go语法的表示。参数选择的内容取决于GOROOT和GOPATH中安装的内容以及参数的形式,是以下形式之一:

	go doc <pkg>
	go doc <sym>[.<methodOrField>]
	go doc [<pkg>.]<sym>[.<methodOrField>]
	go doc [<pkg>.][<sym>.]<methodOrField>

The first item in this list matched by the argument is the one whose documentation
is printed. (See the examples below.) However, if the argument starts with a capital
letter it is assumed to identify a symbol or method in the current directory.
该参数匹配的此列表中的第一个条目是打印文档的内容。(请参见下面的示例。)但是,如果参数以大写字母开头,则假定该参数是当前目录中的符号或方法。

For packages, the order of scanning is determined lexically in breadth-first order.
That is, the package presented is the one that matches the search and is nearest
the root and lexically first at its level of the hierarchy. The GOROOT tree is
always scanned in its entirety before GOPATH.
对于包,扫描顺序在词法上以广度优先确定。
也就是说,呈现的包是与搜索匹配的包,并且在搜索的层次结构的级别上最接近树根,词法上的第一个匹配。GOROOT树总是在GOPATH树之前全部扫描。

If there is no package specified or matched, the package in the current
directory is selected, so "go doc Foo" shows the documentation for symbol Foo in
the current package.
如果没有指定或匹配的软件包,则选择当前目录中的软件包,因此“go doc Foo”显示了当前软件包中符号FOO的文档。

The package path must be either a qualified path or a proper suffix of a
path. The go tool's usual package mechanism does not apply: package path
elements like . and ... are not implemented by go doc.
包装路径必须是合格的路径或路径的适当后缀。GO工具的常用的包机制不适用:包装路径元素像.和...未由go doc实现。

When run with two arguments, the first is a package path (full path or suffix),
and the second is a symbol, or symbol with method or struct field:
当给出两个参数运行时,第一个是一个包路径(完整路径或后缀),第二个是一个符号或带有方法或结构字段的符号:

	go doc <pkg> <sym>[.<methodOrField>]

In all forms, when matching symbols, lower-case letters in the argument match
either case but upper-case letters match exactly. This means that there may be
multiple matches of a lower-case argument in a package if different symbols have
different cases. If this occurs, documentation for all matches is printed.
在所有形式中,当匹配符号时,参数中的小写字母匹配任何一种大小写,但大写字母进行精确匹配。这意味着,如果不同的符号具有不同的大小写,则包中可能存在多个小写参数的匹配项。如果发生这种情况,将打印所有匹配项的文档。

Examples:
示例:
	go doc
		Show documentation for current package. 显示当前包的文档。
	go doc Foo
		Show documentation for Foo in the current package.
		(Foo starts with a capital letter so it cannot match
		a package path.)
在当前包中显示Foo的文档。
(Foo以大写字母开头,因此无法匹配包路径。)
	go doc encoding/json
		Show documentation for the encoding/json package. 显示encoding/json包的文档。
	go doc json
		Shorthand for encoding/json. encoding/json的速记形式。
	go doc json.Number (or go doc json.number)
		Show documentation and method summary for json.Number. 显示json.Number的文档和方法摘要。
	go doc json.Number.Int64 (or go doc json.number.int64)
		Show documentation for json.Number's Int64 method. 显示json.Number的Int64方法的文档。
	go doc cmd/doc
		Show package docs for the doc command. 显示doc命令的包文档。
	go doc -cmd cmd/doc
		Show package docs and exported symbols within the doc command. 在doc命令中显示包文档和导出的符号。
	go doc template.new
		Show documentation for html/template's New function.
		(html/template is lexically before text/template)
		显示html/template的New函数的文档。
		(html/template在词法上先于text/template)
	go doc text/template.new # One argument 一个参数
		Show documentation for text/template's New function. 显示text/template的New函数的文档。
	go doc text/template new # Two arguments 两个参数
		Show documentation for text/template's New function. 显示text/template的New函数的文档。

	At least in the current tree, these invocations all print the
	documentation for json.Decoder's Decode method:
至少在当前树中,这些调用都打印了json.Decoder的Decode方法的文档:

	go doc json.Decoder.Decode
	go doc json.decoder.decode
	go doc json.decode
	cd go/src/encoding/json; go doc decode

Flags:
标志:
	-all
		Show all the documentation for the package. 显示包的所有文档。
	-c
		Respect case when matching symbols. 匹配时区分大小写。
	-cmd
		Treat a command (package main) like a regular package.
		Otherwise package main's exported symbols are hidden
		when showing the package's top-level documentation.
将命令(main包)视为常规包。
否则,当显示包的顶级文档时,main包的导出符号将被隐藏。
	-short
		One-line representation for each symbol. 每个符号一行显示。
	-src
		Show the full source code for the symbol. This will
		display the full Go source of its declaration and
		definition, such as a function definition (including
		the body), type declaration or enclosing const
		block. The output may therefore include unexported
		details.
显示符号的完整源代码。这将显示其声明和定义的完整Go源代码,例如函数定义(包括主体)、类型声明或封闭的const块。因此,输出可能包括没有导出的详细信息。
	-u
		Show documentation for unexported as well as exported
		symbols, methods, and fields.
		显示未导出和导出的符号、方法和字段的文档。

go env打印Go环境信息

$ go help env
usage: go env [-json] [-u] [-w] [var ...]
用法:go env [-json] [-u] [-w] [var ...]

Env prints Go environment information.
env打印输出Go环境信息。

By default env prints information as a shell script
(on Windows, a batch file). If one or more variable
names is given as arguments, env prints the value of
each named variable on its own line.
默认情况下,env作为shell脚本打印输出信息(在Windows上为批处理文件)。如果一个或多个变量名称作为参数给出,env将每个给出名字的变量的值在对应的行上打印输出。

The -json flag prints the environment in JSON format
instead of as a shell script.
-json标志以JSON格式而不是作为shell脚本打印环境。

The -u flag requires one or more arguments and unsets
the default setting for the named environment variables,
if one has been set with 'go env -w'.
-u标志需要一个或多个参数,并取消设置给出名字的环境变量的默认设置(如果已使用“go env -w”设置)。

The -w flag requires one or more arguments of the
form NAME=VALUE and changes the default settings
of the named environment variables to the given values.
-w标志需要一个或多个NAME=VALUE形式的参数,并将命名环境变量的默认设置更改为给定值。

For more about environment variables, see 'go help environment'.
有关环境变量的更多信息,请参阅“go help environment”。

go fix更新软件包以使用新的API

$ go help fix
usage: go fix [-fix list] [packages]
用法:go fix [-fix list] [packages]

Fix runs the Go fix command on the packages named by the import paths.
fix在由导入路径给出名字的包上运行Go的fix命令。

The -fix flag sets a comma-separated list of fixes to run.
The default is all known fixes.
(Its value is passed to 'go tool fix -r'.)
-fix标志设置以逗号分隔的要运行的修复列表。
默认是所有已知的修复。
(它的值被传递给'go tool fix -r'。)

For more about fix, see 'go doc cmd/fix'.
For more about specifying packages, see 'go help packages'.
有关修复的更多信息,请参阅“go doc cmd/fix”。
有关指定包的更多信息,请参阅“go help packages”。

To run fix with other options, run 'go tool fix'.
要使用其他选项运行修复,请运行“go tool fix”。

See also: go fmt, go vet.
另请参阅:go fmt,go vet。

go fmt重新格式化包源

$ go help fmt
usage: go fmt [-n] [-x] [packages]
用法:go fmt [-n] [-x] [packages]

Fmt runs the command 'gofmt -l -w' on the packages named
by the import paths. It prints the names of the files that are modified.
fmt在由导入路径给出名字的包上运行命令“gofmt -l -w”。它会打印输出被修改的文件的名称。

For more about gofmt, see 'go doc cmd/gofmt'.
For more about specifying packages, see 'go help packages'.
有关gofmt的更多信息,请参阅“go doc cmd/gofmt”。
有关指定包的更多信息,请参阅“go help packages”。

The -n flag prints commands that would be executed.
The -x flag prints commands as they are executed.
-n标志打印将要执行的命令。
-x标志在执行命令时打印命令。

The -mod flag's value sets which module download mode
to use: readonly or vendor. See 'go help modules' for more.
-mod标志的值设置要使用的模块下载模式:只读或vendor模式。有关更多信息,请参阅“go help modules”。

To run gofmt with specific options, run gofmt itself.
要使用特定选项运行gofmt,请运行gofmt本身。

See also: go fix, go vet.
另请参阅:go fix, go vet。

go generate通过处理源文件生成Go文件

$ go help generate
usage: go generate [-run regexp] [-n] [-v] [-x] [build flags] [file.go... | packages]
用法:go generate [-run regexp] [-n] [-v] [-x] [build flags] [file.go... | packages]

Generate runs commands described by directives within existing
files. Those commands can run any process but the intent is to
create or update Go source files.
generate运行现有文件中的指令描述的命令。这些命令可以运行任何进程,但目的是创建或更新Go源文件。

Go generate is never run automatically by go build, go test,
and so on. It must be run explicitly.
go generate永远不会被go build、go test等自动运行。它必须显式运行。

Go generate scans the file for directives, which are lines of
the form,
go generate扫描文件中的指令,这些指令是以下形式的行,

	//go:generate command argument... go:生成命令参数

(note: no leading spaces and no space in "//go") where command
is the generator to be run, corresponding to an executable file
that can be run locally. It must either be in the shell path
(gofmt), a fully qualified path (/usr/you/bin/mytool), or a
command alias, described below.
(注意:没有前导空格并且“//go”中也没有空格),其中command是要运行的生成器,对应于可以在本地运行的可执行文件。它必须位于shell路径(gofmt)、完全限定路径(/usr/you/bin/mytool)或命令别名中,如下所述。

Note that go generate does not parse the file, so lines that look
like directives in comments or multiline strings will be treated
as directives.
请注意,go generate不会解析文件,因此在注释或多行字符串中看起来像指令的行将被视为指令。

The arguments to the directive are space-separated tokens or
double-quoted strings passed to the generator as individual
arguments when it is run.
指令的参数是空格分隔的标记或双引号包起来的字符串,在运行时作为单个参数传递给生成器。

Quoted strings use Go syntax and are evaluated before execution; a
quoted string appears as a single argument to the generator.
引号包起来的字符串使用Go语法,并在执行前进行评估;引号包起来的字符串作为生成器的单个参数。

To convey to humans and machine tools that code is generated,
generated source should have a line that matches the following
regular expression (in Go syntax):
为了向人和机器传达生成的代码,生成的源代码应该有一行匹配以下正则表达式(在Go语法中):

	^// Code generated .* DO NOT EDIT\.$

This line must appear before the first non-comment, non-blank
text in the file.
此行必须出现在文件中第一个非注释、非空白文本之前。

Go generate sets several variables when it runs the generator:
Go generate在运行生成器时设置了几个变量:

	$GOARCH
		The execution architecture (arm, amd64, etc.) 执行环境的架构(arm、amd64 等。)
	$GOOS
		The execution operating system (linux, windows, etc.) 执行环境的操作系统(linux、windows等。)
	$GOFILE
		The base name of the file. 文件的基本名称。
	$GOLINE
		The line number of the directive in the source file. 源文件中指令所在的行号。
	$GOPACKAGE
		The name of the package of the file containing the directive. 包含指令的文件的包的名字。
	$DOLLAR
		A dollar sign. 一个美元符号。

Other than variable substitution and quoted-string evaluation, no
special processing such as "globbing" is performed on the command
line.
除了变量替换和带引号的字符串求值外,在命令行上不执行诸如“globbing”之类的特殊处理。

As a last step before running the command, any invocations of any
environment variables with alphanumeric names, such as $GOFILE or
$HOME, are expanded throughout the command line. The syntax for
variable expansion is $NAME on all operating systems. Due to the
order of evaluation, variables are expanded even inside quoted
strings. If the variable NAME is not set, $NAME expands to the
empty string.
作为运行命令之前的最后一步,对任何具有字母数字名称的环境变量(例如$GOFILE或$HOME)的任何调用都会在整个命令行中展开。变量扩展的语法在所有操作系统上都是$NAME。由于求值的顺序,变量甚至在带引号的字符串中也会被扩展。如果未设置变量NAME,则$NAME将扩展为空字符串。

A directive of the form,
一个以下形式的指令,

	//go:generate -command xxx args...

specifies, for the remainder of this source file only, that the
string xxx represents the command identified by the arguments. This
can be used to create aliases or to handle multiword generators.
For example,
仅针对此源文件的其余部分,指定的字符串xxx表示由参数标识的命令。这可用于创建别名或处理多字生成器。
例如,

	//go:generate -command foo go tool foo

specifies that the command "foo" represents the generator
"go tool foo".
指定"foo"命令代表"go tool foo"生成器。

Generate processes packages in the order given on the command line,
one at a time. If the command line lists .go files from a single directory,
they are treated as a single package. Within a package, generate processes the
source files in a package in file name order, one at a time. Within
a source file, generate runs generators in the order they appear
in the file, one at a time. The go generate tool also sets the build
tag "generate" so that files may be examined by go generate but ignored
during build.
按照命令行上给定的顺序生成进程包,一次一个。如果命令行列出了单个目录中的.go文件,它们将被视为单个包。在包中,generate按文件名顺序处理包中的源文件,一次一个。在源文件中,generate按照它们在文件中出现的顺序运行生成器,一次一个。go generate工具还设置了构建标签“generate”,以便go generate可以检查文件,但在构建过程中会忽略这些文件。

For packages with invalid code, generate processes only source files with a
valid package clause.
对于具有无效代码的包,仅生成具有有效包子句的源文件。

If any generator returns an error exit status, "go generate" skips
all further processing for that package.
如果任何生成器返回错误退出状态,“go generate”会跳过该包的所有进一步的处理。

The generator is run in the package's source directory.
生成器在包的源目录中运行。

Go generate accepts one specific flag:
go generate接受一个特定的标志:

	-run=""
		if non-empty, specifies a regular expression to select
		directives whose full original source text (excluding
		any trailing spaces and final newline) matches the
		expression.
如果非空,则指定一个正则表达式以选择其完整的原始源文本(不包括任何尾随空格和最后的换行符)与该表达式匹配的指令。

It also accepts the standard build flags including -v, -n, and -x.
The -v flag prints the names of packages and files as they are
processed.
The -n flag prints commands that would be executed.
The -x flag prints commands as they are executed.
它还接受标准构建标志,包括-v、-n和-x。
-v标志在处理包和文件时打印它们的名称。
-n标志打印将要执行的命令。
-x标志在执行命令时打印命令。

For more about build flags, see 'go help build'.
有关构建标志的更多信息,请参阅“go help build”。

For more about specifying packages, see 'go help packages'.
有关指定包的更多信息,请参阅“go help packages”。

go get将依赖项添加到当前模块并安装它们

$ go help get
usage: go get [-t] [-u] [-v] [build flags] [packages]
用法:go get [-t] [-u] [-v] [build flags] [packages]

Get resolves its command-line arguments to packages at specific module versions,
updates go.mod to require those versions, and downloads source code into the
module cache.
get将其命令行参数解析为特定模块版本的包,更新go.mod以需求这些版本,并将源代码下载到模块缓存中。

To add a dependency for a package or upgrade it to its latest version:
要为软件包添加依赖项或将其升级到最新版本:

	go get example.com/pkg

To upgrade or downgrade a package to a specific version:
要将软件包升级或降级到特定版本:

	go get example.com/[email protected]

To remove a dependency on a module and downgrade modules that require it:
删除对某个模块的依赖并降级需求它的模块:

	go get example.com/mod@none

See https://golang.org/ref/mod#go-get for details.
有关详细信息,请参阅https://golang.org/ref/mod#go-get。

In earlier versions of Go, 'go get' was used to build and install packages.
Now, 'go get' is dedicated to adjusting dependencies in go.mod. 'go install'
may be used to build and install commands instead. When a version is specified,
'go install' runs in module-aware mode and ignores the go.mod file in the
current directory. For example:
在早期版本的Go中,“go get”用于构建和安装包。现在,'go get'专门用于调整go.mod中的依赖关系。'go install'可用于构建和安装命令。当指定版本时,“go install”以模块感知模式运行,并忽略当前目录中的go.mod 文件。例如:

	go install example.com/[email protected]
	go install example.com/pkg@latest

See 'go help install' or https://golang.org/ref/mod#go-install for details.
有关详细信息,请参阅“go help install”或https://golang.org/ref/mod#go-install。

'go get' accepts the following flags.
'go get'接受以下标志。

The -t flag instructs get to consider modules needed to build tests of
packages specified on the command line.
-t标志指示get考虑构建命令行上指定的包的测试所需的模块。

The -u flag instructs get to update modules providing dependencies
of packages named on the command line to use newer minor or patch
releases when available.
-u标志指示get更新提供包的依赖项的模块,以便在可用时使用更新次版本号或补丁版本号,这些模块在命令行上给出名字。

The -u=patch flag (not -u patch) also instructs get to update dependencies,
but changes the default to select patch releases.
-u=patch标志(不是-u patch)还指示get更新依赖项,但将默认值更改为选择补丁版本号。

When the -t and -u flags are used together, get will update
test dependencies as well.
当-t和-u标志一起使用时,get也会更新测试所需的依赖项。

The -x flag prints commands as they are executed. This is useful for
debugging version control commands when a module is downloaded directly
from a repository.
-x标志在执行命令时打印命令。当直接从存储库下载模块时,这对于调试版本控制命令很有用。

For more about modules, see https://golang.org/ref/mod.
有关模块的更多信息,请参阅https://golang.org/ref/mod。

For more about specifying packages, see 'go help packages'.
有关指定包的更多信息,请参阅“go help packages”。

This text describes the behavior of get using modules to manage source
code and dependencies. If instead the go command is running in GOPATH
mode, the details of get's flags and effects change, as does 'go help get'.
See 'go help gopath-get'.
本文描述了get使用模块来管理源代码和依赖项的行为。如果go命令在GOPATH模式下运行,则get的标志和效果的详细信息会发生变化,“go help get”也是如此。请参阅“go help gopath-get”。

See also: go build, go install, go clean, go mod.
另请参阅:go build, go install, go clean, go mod。

go install编译并安装包和依赖项

$ go help install
usage: go install [build flags] [packages]
用法:go install [build flags] [packages]

Install compiles and installs the packages named by the import paths.
install编译并安装由导入路径给出名字的包。

Executables are installed in the directory named by the GOBIN environment
variable, which defaults to $GOPATH/bin or $HOME/go/bin if the GOPATH
environment variable is not set. Executables in $GOROOT
are installed in $GOROOT/bin or $GOTOOLDIR instead of $GOBIN.
可执行文件安装在由GOBIN环境变量命名的目录中,默认为$GOPATH/bin,如果未设置GOPATH环境变量,则安装在$HOME/go/bin。$GOROOT中的可执行文件安装在$GOROOT/bin或$GOTOOLDIR而不是$GOBIN中。

If the arguments have version suffixes (like @latest or @v1.0.0), "go install"
builds packages in module-aware mode, ignoring the go.mod file in the current
directory or any parent directory, if there is one. This is useful for
installing executables without affecting the dependencies of the main module.
To eliminate ambiguity about which module versions are used in the build, the
arguments must satisfy the following constraints:
如果参数有版本号后缀(例如@latest或@v1.0.0),“go install”以模块感知模式构建包,忽略当前目录或任何父目录中的go.mod文件(如果有的话)。这对于在不影响主模块依赖关系的情况下安装可执行文件很有用。为了消除构建中使用的模块版本的歧义,参数必须满足以下约束:

- Arguments must be package paths or package patterns (with "..." wildcards).
They must not be standard packages (like fmt), meta-patterns (std, cmd,
all), or relative or absolute file paths.
参数必须是包路径或包模式(带有“...”通配符)。它们不能是标准包(例如fmt)、元模式(std、cmd、all)或相对或绝对文件路径。

- All arguments must have the same version suffix. Different queries are not
allowed, even if they refer to the same version.
所有参数必须具有相同的版本号后缀。不允许不同的查询,即使它们引用相同的版本。

- All arguments must refer to packages in the same module at the same version. 所有参数必须引用相同版本的相同模块中的包。

- Package path arguments must refer to main packages. Pattern arguments
will only match main packages.
包路径参数必须引用主包。模式参数只会匹配主包。

- No module is considered the "main" module. If the module containing
packages named on the command line has a go.mod file, it must not contain
directives (replace and exclude) that would cause it to be interpreted
differently than if it were the main module. The module must not require
a higher version of itself.
没有模块被视为“主”模块。如果包含在命令行中给出名字的包的模块有一个go.mod文件,则它不能包含replace和exclude指令,因为这两个指令会导致对它的解释与当它作为主模块时对它的解释不同。该模块不得需求其自身的更高版本。

- Vendor directories are not used in any module. (Vendor directories are not
included in the module zip files downloaded by 'go install'.)
Vendor目录不会在任何模块中使用。(Vendor目录不包含在“go install”下载的模块zip文件中。)

If the arguments don't have version suffixes, "go install" may run in
module-aware mode or GOPATH mode, depending on the GO111MODULE environment
variable and the presence of a go.mod file. See 'go help modules' for details.
If module-aware mode is enabled, "go install" runs in the context of the main
module.
如果参数没有版本号后缀,“go install”可能会在模块感知模式或GOPATH模式下运行,具体取决于GO111MODULE环境变量和go.mod文件的存在。有关详细信息,请参阅“go help modules”。如果启用了模块感知模式,“go install”将在主模块的上下文中运行。

When module-aware mode is disabled, other packages are installed in the
directory $GOPATH/pkg/$GOOS_$GOARCH. When module-aware mode is enabled,
other packages are built and cached but not installed.
当禁用模块感知模式时,其他包将安装在目录$GOPATH/pkg/$GOOS_$GOARCH中。当启用模块感知模式时,会生成和缓存其他包,但不会安装。

The -i flag installs the dependencies of the named packages as well.
The -i flag is deprecated. Compiled packages are cached automatically.
-i标志还安装命名包的依赖项。
-i标志已弃用。编译的包将自动缓存。

For more about the build flags, see 'go help build'.
For more about specifying packages, see 'go help packages'.
有关构建标志的详细信息,请参阅“go help build”。
有关指定包的详细信息,请参阅“go help packages”。

See also: go build, go get, go clean.
另请参阅:go build,go get,go clean。

go list列出当前全部安装的模块或包

$ go help list
usage: go list [-f format] [-json] [-m] [list flags] [build flags] [packages]
用法:go list [-f format] [-json] [-m] [list flags] [build flags] [packages]

List lists the named packages, one per line.
The most commonly-used flags are -f and -json, which control the form
of the output printed for each package. Other list flags, documented below,
control more specific details.
list列出命名的包,每行一个。
最常用的标志是-f和-json,它们控制为每个包打印输出的格式。下面记录的其他list标志控制更具体的细节。

The default output shows the package import path:
默认输出显示包的导入路径:

    bytes
    encoding/json
    github.com/gorilla/mux
    golang.org/x/net/html

The -f flag specifies an alternate format for the list, using the
syntax of package template. The default output is equivalent
to -f '{{.ImportPath}}'. The struct being passed to the template is:
-f标志使用包模板的语法指定list的替代格式。默认输出格式等效于-f '{{.ImportPath}}'。传递给模板的结构是:

    type Package struct {
        Dir           string   // directory containing package sources 包含包源代码的目录
        ImportPath    string   // import path of package in dir 包在dir里的导入路径
        ImportComment string   // path in import comment on package statement 包语句的导入注释中的路径
        Name          string   // package name 包名
        Doc           string   // package documentation string 包文档字符串
        Target        string   // install path 安装路径
        Shlib         string   // the shared library that contains this package (only set when -linkshared) 包含此包的共享库(仅在-linkshared时设置)
        Goroot        bool     // is this package in the Go root? 此包是否在Go安装目录里?
        Standard      bool     // is this package part of the standard Go library? 此包是Go标准库的一部分吗?
        Stale         bool     // would 'go install' do anything for this package? 'go install'是否会对此包做一些事情?
        StaleReason   string   // explanation for Stale==true 对Stale==true的解释
        Root          string   // Go root or Go path dir containing this package 包含此包的Go root或Go path目录
        ConflictDir   string   // this directory shadows Dir in $GOPATH 此目录隐藏了$GOPATH中的目录
        BinaryOnly    bool     // binary-only package (no longer supported) 只是二进制包(已经不再支持)
        ForTest       string   // package is only for use in named test 只用于测试的包
        Export        string   // file containing export data (when using -export) 包含导出数据的文件(使用-export时)
        BuildID       string   // build ID of the compiled package (when using -export) 编译包的构建ID(使用-export时)
        Module        *Module  // info about package's containing module, if any (can be nil) 关于包的包含模块的信息,如果有的话(可以是nil)
        Match         []string // command-line patterns matching this package 匹配此包的命令行模式
        DepOnly       bool     // package is only a dependency, not explicitly listed 此包只是一个依赖项,没有显式地被列出

        // Source files 源文件
        GoFiles         []string   // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles) .go源文件(不包括CgoFiles、TestGoFiles、XTestGoFiles)
        CgoFiles        []string   // .go source files that import "C" 导入“C”的.go源文件
        CompiledGoFiles []string   // .go files presented to compiler (when using -compiled) 呈现给编译器的.go文件(使用-compiled时)
        IgnoredGoFiles  []string   // .go source files ignored due to build constraints 因构建约束而被忽略的.go源文件
        IgnoredOtherFiles []string // non-.go source files ignored due to build constraints 由于构建约束而被忽略的非.go源文件
        CFiles          []string   // .c source files .c源文件
        CXXFiles        []string   // .cc, .cxx and .cpp source files .cc,.cxx和.cpp源文件
        MFiles          []string   // .m source files .m源文件
        HFiles          []string   // .h, .hh, .hpp and .hxx source files .h,.hh,.hpp和.hxx源文件
        FFiles          []string   // .f, .F, .for and .f90 Fortran source files .f,.F,.for和.f90 Fortran源文件
        SFiles          []string   // .s source files .s源文件
        SwigFiles       []string   // .swig files .swig文件
        SwigCXXFiles    []string   // .swigcxx files .swigcxx文件
        SysoFiles       []string   // .syso object files to add to archive 要添加到文档的.syso目标文件
        TestGoFiles     []string   // _test.go files in package 包里的_test.go文件
        XTestGoFiles    []string   // _test.go files outside package 包外面的_test.go文件

        // Embedded files 内嵌的文件
        EmbedPatterns      []string // //go:embed patterns go:内嵌模式
        EmbedFiles         []string // files matched by EmbedPatterns 匹配EmbedPatterns的文件
        TestEmbedPatterns  []string // //go:embed patterns in TestGoFiles go:在TestGoFiles里内嵌的模式
        TestEmbedFiles     []string // files matched by TestEmbedPatterns 匹配TestEmbedPatterns的文件
        XTestEmbedPatterns []string // //go:embed patterns in XTestGoFiles go:在XTestGoFiles里内嵌的模式
        XTestEmbedFiles    []string // files matched by XTestEmbedPatterns 匹配XTestEmbedPatterns的文件

        // Cgo directives Cgo指令
        CgoCFLAGS    []string // cgo: flags for C compiler cgo:C编译器cgo的标志
        CgoCPPFLAGS  []string // cgo: flags for C preprocessor cgo:C预处理器的标志
        CgoCXXFLAGS  []string // cgo: flags for C++ compiler cgo:C++编译器的标志
        CgoFFLAGS    []string // cgo: flags for Fortran compiler cgo:Fortran编译器的标志
        CgoLDFLAGS   []string // cgo: flags for linker cgo:连接器的标志
        CgoPkgConfig []string // cgo: pkg-config names cgo:pkg-config的名字

        // Dependency information 依赖信息
        Imports      []string          // import paths used by this package 此包使用的导入路径
        ImportMap    map[string]string // map from source import to ImportPath (identity entries omitted) 从源导入映射到 ImportPath(省略标识条目)
        Deps         []string          // all (recursively) imported dependencies 所有(递归地)导入的依赖项
        TestImports  []string          // imports from TestGoFiles 从TestGoFiles的导入
        XTestImports []string          // imports from XTestGoFiles 从XTestGoFiles的导入

        // Error information 错误信息
        Incomplete bool            // this package or a dependency has an error 次包或一个依赖项有一个错误
        Error      *PackageError   // error loading package 加载包时出错
        DepsErrors []*PackageError // errors loading dependencies 加载依赖项时出错
    }

Packages stored in vendor directories report an ImportPath that includes the
path to the vendor directory (for example, "d/vendor/p" instead of "p"),
so that the ImportPath uniquely identifies a given copy of a package.
The Imports, Deps, TestImports, and XTestImports lists also contain these
expanded import paths. See golang.org/s/go15vendor for more about vendoring.
存储在vendor目录中的软件包报告一个ImportPath,其中包含vendor目录的路径(例如,“d/ivendor/p”而不是“p”),以便ImportPath唯一标识软件包的给定副本。
Imports、Deps、TestImports和XTestImports列表也包含这些扩展的导入路径。见golang.org/s/go15vendor了解更多关于vendor的信息。

The error information, if any, is
错误信息(如果有的话)是

    type PackageError struct {
        ImportStack   []string // shortest path from package named on command line to this one 在命令行给出名字的包到这个包的最短路径
        Pos           string   // position of error (if present, file:line:col) 错误位置(如果存在的话,文件名:行号:列号)
        Err           string   // the error itself 错误本身
    }

The module information is a Module struct, defined in the discussion
of list -m below.
模块信息是一个Module结构体,在下面list -m的讨论中定义。

The template function "join" calls strings.Join.
模板函数“join”调用strings.Join。

The template function "context" returns the build context, defined as:
模板函数“context”返回构建上下文,定义为:

    type Context struct {
        GOARCH        string   // target architecture 目标架构
        GOOS          string   // target operating system 目标操作系统
        GOROOT        string   // Go root Go语言安装路径
        GOPATH        string   // Go path Go语言包的查找路径
        CgoEnabled    bool     // whether cgo can be used 是否可以使用cgo
        UseAllFiles   bool     // use files regardless of +build lines, file names 无论+build行、文件名如何,都使用文件
        Compiler      string   // compiler to assume when computing target paths 计算目标路径时假定的编译器
        BuildTags     []string // build constraints to match in +build lines 用于匹配+build行的构建约束
        ToolTags      []string // toolchain-specific build constraints 特定于工具链的构建约束
        ReleaseTags   []string // releases the current release is compatible with 当前发布版本兼容的发布版本
        InstallSuffix string   // suffix to use in the name of the install dir 要在安装目录名称中使用的后缀
    }

For more information about the meaning of these fields see the documentation
for the go/build package's Context type.
有关这些字段含义的更多信息,请参阅go/build包的Context类型的文档。

The -json flag causes the package data to be printed in JSON format
instead of using the template format.
-json标志使包数据以JSON格式而不是使用模板格式打印。

The -compiled flag causes list to set CompiledGoFiles to the Go source
files presented to the compiler. Typically this means that it repeats
the files listed in GoFiles and then also adds the Go code generated
by processing CgoFiles and SwigFiles. The Imports list contains the
union of all imports from both GoFiles and CompiledGoFiles.
-compiled标志让list将CompiledGoFiles设置为呈现给编译器的Go源文件。通常这意味着它会重复GoFiles中列出的文件,然后还会添加通过处理CgoFiles和SwigFiles生成的Go代码。Imports列表包含来自GoFiles和CompiledGoFiles的所有导入的并集。

The -deps flag causes list to iterate over not just the named packages
but also all their dependencies. It visits them in a depth-first post-order
traversal, so that a package is listed only after all its dependencies.
Packages not explicitly listed on the command line will have the DepOnly
field set to true.
-deps标志导致list不仅遍历给出名字的包,还遍历它们的所有依赖项。它以深度优先的后序遍历方式访问它们,因此包仅在其所有依赖项之后列出。
将DepOnly字段设置为true的包不会在命令行上明确列出。

The -e flag changes the handling of erroneous packages, those that
cannot be found or are malformed. By default, the list command
prints an error to standard error for each erroneous package and
omits the packages from consideration during the usual printing.
With the -e flag, the list command never prints errors to standard
error and instead processes the erroneous packages with the usual
printing. Erroneous packages will have a non-empty ImportPath and
a non-nil Error field; other information may or may not be missing
(zeroed).
-e标志更改对错误包的处理方式,即那些无法找到或格式错误的包。默认情况下,list命令为每个错误的包打印一个错误到标准误,并在通常的打印过程中忽略这些包。
使用-e标志,list命令永远不会将错误打印到标准误,而是使用通常的打印来处理错误的包。错误的包将有一个非空的ImportPath和一个非nil的错误字段;其他信息可能会或可能不会丢失(归零)。

The -export flag causes list to set the Export field to the name of a
file containing up-to-date export information for the given package.
-export标志使list将Export字段设置为包含给定包的最新导出信息的文件名。

The -find flag causes list to identify the named packages but not
resolve their dependencies: the Imports and Deps lists will be empty.
-find标志让list识别给出名字的包但不解析它们的依赖关系:Imports和Deps的列表将为空。

The -test flag causes list to report not only the named packages
but also their test binaries (for packages with tests), to convey to
source code analysis tools exactly how test binaries are constructed.
The reported import path for a test binary is the import path of
the package followed by a ".test" suffix, as in "math/rand.test".
When building a test, it is sometimes necessary to rebuild certain
dependencies specially for that test (most commonly the tested
package itself). The reported import path of a package recompiled
for a particular test binary is followed by a space and the name of
the test binary in brackets, as in "math/rand [math/rand.test]"
or "regexp [sort.test]". The ForTest field is also set to the name
of the package being tested ("math/rand" or "sort" in the previous
examples).
-test标志让list不仅报告给出名字的包,还报告它们的测试二进制文件(对于带有测试的包),以准确地向源代码分析工具传达测试二进制文件的构建方式。
报告的测试二进制文件的导入路径是包的导入路径,后跟“.test”后缀,例如“math/rand.test”。
在构建测试时,有时需要专门为该测试重建某些依赖项(最常见的是测试包本身)。 为特定测试二进制文件重新编译的包的报告的导入路径后跟一个空格和方括号中的测试二进制文件的名称,例如“math/rand [math/rand.test]”或“regexp [sort.test]”。ForTest字段也设置为正在测试的包的名称(前面示例中的“math/rand”或“sort”)。

The Dir, Target, Shlib, Root, ConflictDir, and Export file paths
are all absolute paths.
Dir、Target、Shlib、Root、ConflictDir、Export的文件路径都是绝对路径。

By default, the lists GoFiles, CgoFiles, and so on hold names of files in Dir
(that is, paths relative to Dir, not absolute paths).
The generated files added when using the -compiled and -test flags
are absolute paths referring to cached copies of generated Go source files.
Although they are Go source files, the paths may not end in ".go".
默认情况下,GoFiles、CgoFiles等列表包含Dir中文件的名称(即相对于Dir的路径,而不是绝对路径)。
使用-compiled和-test标志时添加的生成文件使用绝对路径,指向的是生成的Go源文件的缓存副本。
虽然它们是Go源文件,但路径可能不会以“.go”结尾。

The -m flag causes list to list modules instead of packages.
-m标志让list列出模块而不是包。

When listing modules, the -f flag still specifies a format template
applied to a Go struct, but now a Module struct:
列出模块时,-f标志仍然指定应用于一个Go结构体的格式模板,但现在是一个模块(Module)结构:

    type Module struct {
        Path      string       // module path 模块路径
        Version   string       // module version 模块版本号
        Versions  []string     // available module versions (with -versions) 可用的模块版本(使用-versions选项)
        Replace   *Module      // replaced by this module 被这个模块替代
        Time      *time.Time   // time version was created 版本被创建的时刻
        Update    *Module      // available update, if any (with -u)  可用的更新,如果可用的话(使用-u选项)
        Main      bool         // is this the main module? 这是主模块吗?
        Indirect  bool         // is this module only an indirect dependency of main module? 这个模块只是主模块的一个间接依赖项吗?
        Dir       string       // directory holding files for this module, if any 包含这个模块的文件的目录,如果有的话
        GoMod     string       // path to go.mod file used when loading this module, if any 当加载这个模块的时候使用的go.mod文件的路径,如果有的话
        GoVersion string       // go version used in module 在模块里使用的go版本
        Retracted string       // retraction information, if any (with -retracted or -u) 撤回相关信息,如果有的话(使用-retracted或-u选项)
        Error     *ModuleError // error loading module 加载模块错误
    }

    type ModuleError struct {
        Err string // the error itself 错误本身
    }

The file GoMod refers to may be outside the module directory if the
module is in the module cache or if the -modfile flag is used.
如果模块在模块缓存中或使用了-modfile标志,则GoMod引用的文件可能位于模块目录之外。

The default output is to print the module path and then
information about the version and replacement if any.
For example, 'go list -m all' might print:
默认打印输出模块路径,然后是版本号和替换(如果有的话)相关的信息。
例如,“go-list-mall”可能会打印:

    my/main/module
    golang.org/x/text v0.3.0 => /tmp/text
    rsc.io/pdf v0.1.1

The Module struct has a String method that formats this
line of output, so that the default format is equivalent
to -f '{{.String}}'.
Module结构体有一个String方法来格式化这行输出,因此默认格式相当于-f '{{.String}}'。

Note that when a module has been replaced, its Replace field
describes the replacement module, and its Dir field is set to
the replacement's source code, if present. (That is, if Replace
is non-nil, then Dir is set to Replace.Dir, with no access to
the replaced source code.)
请注意,当模块被替换时,其Replace字段描述替换模块,并且其Dir字段设置为替换模块的源代码(如果存在)。(也就是说,如果Replace为非nil,则Dir设置为Replace.Dir,无法访问被替换的源代码。)

The -u flag adds information about available upgrades.
When the latest version of a given module is newer than
the current one, list -u sets the Module's Update field
to information about the newer module. list -u will also set
the module's Retracted field if the current version is retracted.
The Module's String method indicates an available upgrade by
formatting the newer version in brackets after the current version.
If a version is retracted, the string "(retracted)" will follow it.
For example, 'go list -m -u all' might print:
-u标志添加有关可用升级的信息。
当给定模块的最新版本比当前版本新时,list -u将模块的Update字段设置为有关较新模块的信息。如果当前版本被撤回,list -u还将设置模块的Retracted字段。
模块的String方法通过在当前版本之后的方括号中格式化新版本来指示可用的升级。
如果一个版本被撤回,字符串“(retracted)”将跟随它。
例如,'go list -m -u all'可能会打印:

    my/main/module
    golang.org/x/text v0.3.0 [v0.4.0] => /tmp/text
    rsc.io/pdf v0.1.1 (retracted) [v0.1.2]

(For tools, 'go list -m -u -json all' may be more convenient to parse.)
(对于工具,“go list -m -u -json all”的输出可能更便于解析。)

The -versions flag causes list to set the Module's Versions field
to a list of all known versions of that module, ordered according
to semantic versioning, earliest to latest. The flag also changes
the default output format to display the module path followed by the
space-separated version list.
-versions标志使list将Module的versions字段设置为该模块的所有已知版号本的列表,这些版本根据语义版本号从最早到最新进行排序。该标志还将更改默认输出格式,以显示模块路径,后跟空格分隔的版本号列表。

The -retracted flag causes list to report information about retracted
module versions. When -retracted is used with -f or -json, the Retracted
field will be set to a string explaining why the version was retracted.
The string is taken from comments on the retract directive in the
module's go.mod file. When -retracted is used with -versions, retracted
versions are listed together with unretracted versions. The -retracted
flag may be used with or without -m.
-retracted标志让list报告有关撤回的模块的版本信息。当-retracted与-f或-json一起使用时,Retracted字段将被设置为一个字符串,解释版本被撤回的原因。
该字符串取自模块的go.mod文件中retract指令的注释。当-retracted与-versions一起使用时,撤回的版本与未撤回的版本一起列出。-retracted标志可以与-m一起使用,也可以不与-m一起使用。

The arguments to list -m are interpreted as a list of modules, not packages.
The main module is the module containing the current directory.
The active modules are the main module and its dependencies.
With no arguments, list -m shows the main module.
With arguments, list -m shows the modules specified by the arguments.
Any of the active modules can be specified by its module path.
The special pattern "all" specifies all the active modules, first the main
module and then dependencies sorted by module path.
A pattern containing "..." specifies the active modules whose
module paths match the pattern.
A query of the form path@version specifies the result of that query,
which is not limited to active modules.
See 'go help modules' for more about module queries.
list -m的参数被解释为模块的列表,而不是包的列表。
主(main)模块是包含当前目录的模块。
活动(active)模块是主模块及其依赖项。
没有给出参数时,list -m显示主模块。
给出参数时,list -m显示参数指定的模块。
任何活动模块都可以通过其模块路径指定。
特殊模式“all”指定所有活动模块,首先是主模块,然后是按模块路径排序的依赖项。
包含“...”的模式指定其模块路径与该模式匹配的活动模块。
path@version形式的查询指定该查询的结果,它不限于活动模块。
有关模块查询的更多信息,请参阅“go help modules”。

The template function "module" takes a single string argument
that must be a module path or query and returns the specified
module as a Module struct. If an error occurs, the result will
be a Module struct with a non-nil Error field.
模板函数“module”接受一个字符串参数,该参数必须是模块路径或查询,并将指定的模块作为Module结构体实例返回。如果发生错误,结果将是一个带有非nil值的Error字段的Module结构体实例。

For more about build flags, see 'go help build'.
有关构建标志的详细信息,请参阅“go help build”。

For more about specifying packages, see 'go help packages'.
有关指定包的详细信息,请参阅“go help packages”。

For more about modules, see https://golang.org/ref/mod.
有关模块的更多信息,请参阅https://golang.org/ref/mod.

go mod模块维护

$ go help mod
Go mod provides access to operations on modules.
Go mod提供对模块操作的访问。

Note that support for modules is built into all the go commands,
not just 'go mod'. For example, day-to-day adding, removing, upgrading,
and downgrading of dependencies should be done using 'go get'.
See 'go help modules' for an overview of module functionality.
请注意,所有go命令都内置了对模块的支持,而不仅仅是“go mod”。例如,日常添加、删除、升级和降级依赖项应该使用“go get”来完成。
有关模块功能的概述,请参阅“go help modules”。

Usage:
用法:

	go mod <command> [arguments]

The commands are:
命令有:

	download    download modules to local cache 下载模块到本地缓存
	edit        edit go.mod from tools or scripts 从工具或脚本编辑go.mod
	graph       print module requirement graph 打印输出模块依赖图
	init        initialize new module in current directory 在当前目录下初始化一个新模块
	tidy        add missing and remove unused modules 添加缺少的模块并删除未被使用的模块
	vendor      make vendored copy of dependencies 拷贝依赖项到vendor目录
	verify      verify dependencies have expected content 验证依赖项是否具有预期的内容
	why         explain why packages or modules are needed 解释为什么需要这些包或模块

Use "go help mod <command>" for more information about a command.
对于其中某一个命令,使用"go help mod <command>"查看它的详细信息。

对于其中某一个命令,查看go.mod英文文文档或go.mod中文文档查看它的更详细的信息。

go work工作空间维护

$ go help work
Work provides access to operations on workspaces.
work提供对工作空间的操作的访问。

Note that support for workspaces is built into many other commands, not
just 'go work'.
请注意,对工作空间的支持内置于许多其他命令中,而不只是'go work'。

See 'go help modules' for information about Go's module system of which
workspaces are a part.
有关Go的模块系统的信息,请参阅“go help modules”,其中工作空间是其中的一部分。

See https://go.dev/ref/mod#workspaces for an in-depth reference on
workspaces.
有关工作空间的深入参考,请参阅https://go.dev/ref/mod#workspaces。

See https://go.dev/doc/tutorial/workspaces for an introductory
tutorial on workspaces.
有关工作空间的介绍性教程,请参阅https://go.dev/doc/tutorial/workspaces。

A workspace is specified by a go.work file that specifies a set of
module directories with the "use" directive. These modules are used as
root modules by the go command for builds and related operations.  A
workspace that does not specify modules to be used cannot be used to do
builds from local modules.
工作空间由go.work文件指定,该文件使用“use”指令指定一组模块目录。这些模块被go命令用作构建和相关操作的根模块。未指定要使用的本地模块的工作空间不能使用本地模块进行构建。

go.work files are line-oriented. Each line holds a single directive,
made up of a keyword followed by arguments. For example:
go.work文件是面向行的。每行包含一个指令,由关键字和参数组成。例如:

	go 1.18

	use ../foo/bar
	use ./baz

	replace example.com/foo v1.2.3 => example.com/bar v1.4.5

The leading keyword can be factored out of adjacent lines to create a block,
like in Go imports.
前导关键字可以从相邻行中规约出来以创建一个块,就像在Go import中一样。

	use (
	  ../foo/bar
	  ./baz
	)

The use directive specifies a module to be included in the workspace's
set of main modules. The argument to the use directive is the directory
containing the module's go.mod file.
use指令指定要包含在工作空间的主模块集合中的模块。use指令的参数是包含该模块的go.mod文件的目录的路径。

The go directive specifies the version of Go the file was written at. It
is possible there may be future changes in the semantics of workspaces
that could be controlled by this version, but for now the version
specified has no effect.
go指令指定编写文件时使用的Go版本。将来此版本控制的工作空间的语义可能发生变化,但目前指定的版本没有影响。

The replace directive has the same syntax as the replace directive in a
go.mod file and takes precedence over replaces in go.mod files.  It is
primarily intended to override conflicting replaces in different workspace
modules.
replace指令与go.mod文件中的replace指令具有相同的语法,并且优先于go.mod文件中的替换。它主要用于重写不同工作空间模块中的冲突的替换。

To determine whether the go command is operating in workspace mode, use
the "go env GOWORK" command. This will specify the workspace file being
used.
要确定go命令是否在工作空间模式下运行,请使用“go env GOWORK”命令。这将指定正在使用的工作空间文件。

Usage:
用法:

	go work <command> [arguments]

The commands are:
命令有:

	edit        edit go.work from tools or scripts 从工具或脚本编辑go.work
	init        initialize workspace file 初始化工作空间文件
	sync        sync workspace build list to modules 同步工作空间构建列表到模块
	use         add modules to workspace file 添加模块到工作空间文件

Use "go help work <command>" for more information about a command.
使用“go help work <command>”获取有关命令的更多信息。

syscall包

本文翻译自《The syscall package》。

作者:Rob Pike 日期:2014

状态:此提案[Go 1.4版本采用] (https://go.dev/doc/go1.4#major_library_changes)。

问题

现在的syscall包有几个问题:

1.膨胀。它包含许多系统调用和常量的定义,用于大量且不断增长的体系结构和操作系统。

2.测试。很少有接口有明确的测试,跨平台测试是不可能的。

3.管理。许多更改列表到来,以支持范围广泛的包和系统。这些变化的优点很难判断,所以基本上任何事情都会发生。该软件包是标准存储库中维护最差、测试最差、文档最差的软件包,并且还在继续恶化。

4.文档。称为syscall的单个包对于每个系统都不同,但godoc仅显示其本机环境的变体。此外,无论如何,文档都非常缺乏。大多数函数根本没有文档注释。

5.兼容性。尽管有最好的意图,但该软件包不符合Go 1兼容性保证,因为操作系统的变化方式超出了我们的控制范围。最近对FreeBSD的更改就是一个例子。

这个提议是试图改善这些问题。

提议

该提案有几个组成部分。没有特别的顺序:

1.Go 1的兼容性规则意味着我们不能直接解决问题,比如通过使包内部化。因此,我们建议从Go 1.3开始冻结该包,这意味着取消从那时起进行的一些更改。

2.支持Go未来版本所需的系统调用接口的任何更改都将通过为Go 1.4提议的内部包机制完成。

3.syscall包将不会在未来的版本中更新,甚至不会跟上它引用的操作系统的变化。例如,如果内核常量的值在未来的NetBSD版本中发生变化,则不会更新syscall包以反映这一点。

4.将创建一个新的子存储库go.sys

5.在go.sys内部,将有三个独立于syscall的包,分别称为plan9unixwindows,并且当前syscall包的内容将被适当地分解并安装在这些包中。(这种划分表达了系统之间的基本接口差异,允许一些源代码级别的可移植性,但在包中仍然需要构建标签来分离架构和变体(darwinlinux))。这些是我们希望所有外部Go包在需要支持系统调用时迁移到的包。因为它们是不同的,所以它们更容易管理,更容易使用godoc进行检查,并且可能更容易保持良好的文档记录。这种布局还使如何编写跨平台代码更加清晰:通过将系统相关元素分离为单独导入的组件。

6.go.sys存储库将随着操作系统的发展而更新。

7.标准syscall包的文档将引导用户访问新的存储库。尽管syscall包将继续存在并且可行,但所有新的公共开发都将在go.sys中进行。

8.核心存储库将不依赖于go.sys包,尽管某些子存储库(例如go.net)可能会依赖。

9.与任何标准存储库一样,go.sys存储库将由Go团队管理。将其从主存储库中分离出来使得自动化一些维护变得更加实际可行,例如通过对系统包含文件的详尽处理来自动创建包。

10.自1.3以来在syscall包中发生的任何非必要的提示更改都将迁移到go.sys子存储库。

请注意,由于兼容性保证,我们无法在任何有意义的范围内清理现有的syscall包。但是,我们可以冻结并实际上弃用它。

时间线

我们建议在Go 1.4的2014年9月1日截止日期之前完成这些更改。

Go 1和Go程序的未来

本文翻译自《Go 1 and the Future of Go Programs》。

目录

介绍

期望

子存储库

操作系统

工具

介绍

Go版本1的发布,简称Go 1,是该语言发展的一个重要里程碑。Go 1是一个稳定的平台,用于Go语言编写的程序和项目的生长。

Go 1定义了两件事:第一,语言的规范;其次,一组核心API的规范,即Go库的“标准包”。Go 1版本的发行,包括以两个编译器套件(gc和gccgo)形式的实现,以及它们的核心库。

其目的是,编写遵循Go 1规范的程序将在该规范的整个生命周期内继续正确编译和运行,不变。在某个不确定的时间点,可能会出现Go 2规范,但在此之前,即使将来出现Go 1的“单点”版本(Go 1.1、Go 1.2等),今天工作的Go程序也应该继续工作。

兼容性是在源代码级别保证。版本之间不保证编译后的二进制包的兼容性。单点版本发布后,需要重新编译Go源代码以链接到新版本。

API可能会增长,获取新的包和功能,但不会破坏现有的Go 1代码。

期望

尽管我们预计绝大多数程序会随着时间的推移保持这种兼容性,但无法保证未来的任何更改都不会破坏任何程序。本文档试图设定对未来Go 1软件兼容性的期望。今天编译和运行的程序在未来的单点版本发布后可能会以多种方式失败。虽然它们都不太可能,但值得记录。

  • 安全。规范或实现中的安全问题可能会暴露出来,其解决方法需要破坏兼容性。我们保留解决此类安全问题的权利。
  • 未定义的行为。Go规范试图明确该语言的大多数属性,但某些方面未定义。依赖于这种未指定行为的程序可能会在未来的版本中中断运行。
  • 规范错误。如果有必要解决规范中的不一致或不完整问题,解决该问题可能会影响现有程序的意义或合法性。我们保留解决此类问题的权利,包括更新实现。除安全问题外,不会对规范进行不兼容的更改。
  • Bug。如果编译器或库存在违反规范的Bug,则如果修复了Bug,则依赖于该Bug行为的程序可能会中断运行。我们保留修复此类Bug的权利。
  • 结构体字面量。为了在以后的版本中添加功能,可能需要在API中向导出的结构体添加字段。使用无键结构体字面量(例如pkg.T{3, “x”})来创建这些类型的值的代码在此类更改后将无法编译。但是,使用键控结构体字面量(pkg.T{A: 3, B: “x”}) 的代码将在此类更改后继续编译通过。我们将以允许键控结构体字面量保持兼容的方式更新此类数据结构,尽管非键控结构体字面量可能无法编译通过。(还有更复杂的情况涉及嵌套的数据结构或接口,但它们具有相同的解决方法。)因此,我们建议其类型在单独的包中定义的复合文字应使用键控表示法。
  • 方法。与结构体字段一样,可能需要向类型添加方法。在某些情况下,例如当该类型与另一种类型一起嵌入到结构中时,添加新方法可能会通过与其他嵌入类型的现有方法产生冲突来破坏结构。我们无法防范这种罕见的情况,也不保证出现这种情况时的兼容性。
  • 点导入。如果程序使用import . "path"导入标准包,未来版本中导入包中定义的其他名称可能与程序中定义的其他名称冲突。在测试之外,我们不建议使import . "path",使用它可能会导致程序在未来的版本中无法编译通过。
  • 使用unsafe包。导入并使用unsafe包可能依赖Go实现的内部特性。我们保留对可能破坏此类程序的实现进行更改的权利。

当然,对于所有这些可能性,如果它们出现,我们将尽可能在不影响现有代码的情况下更新规范、编译器或库。

这些相同的考虑适用于连续的点版本发布。例如,在Go 1.2下运行的代码应该与Go 1.2.1、Go 1.3、Go 1.4等版本兼容,尽管不一定与Go 1.1兼容,因为它可能使用仅在Go 1.2中添加的新特性。

在版本之间添加的特性,在源存储库中可用,但不是打了编号的二进制发布版本的一部分,说明它正在积极开发中。在使用此类功能的软件发布之前,我们不承诺其兼容性。

最后,虽然这不是正确性问题,但程序的性能可能会受到其所依赖的编译器或库的实现变化的影响。不能保证给定程序在版本之间的性能。

尽管这些期望适用于Go 1本身,但我们希望对基于Go 1的外部软件的开发做出类似的考虑。

子存储库

主go树(main go tree)的子存储库中的代码,例如golang.org/x/net,可能会在更宽松的兼容性要求下开发。但是,子存储库将被适当地打标签以识别与Go 1点版本兼容的版本。

操作系统

无法保证与外部各方更改的操作系统接口的长期兼容性。因此,syscall包不在此处所做保证的范围内。从Go 1.4版本开始,syscall包被冻结。系统调用接口的任何演变都必须在其他地方得到支持,例如在go.sys子存储库中。有关详细信息和背景,请参阅此文档

工具

最后,Go工具链(编译器、链接器、构建工具等)正在积极开发中,可能会改变行为。这意味着,例如,依赖于工具位置和属性的脚本可能会被单点发布版本破坏。

除了这些注意事项,我们相信Go 1将成为Go及其生态系统发展的坚实基础。

Context和结构体

本文翻译自《Contexts and structs》。

Jean de Klerk, Matt T. Proud

24 February 2021

介绍

在许多Go API,尤其是现代API中,函数和方法的第一个参数通常是context.Context。上下文提供了一种跨API边界和进程之间传输截止时间(deadline)、调用者取消(caller cancellation)和请求范围内的其他值的方法。当库直接或传递地与远程服务器(例如数据库、API等)交互时,通常会使用它。

Context(上下文)的文档指出:Context不应该存储在结构体类型中,而是传递给每个需要它的函数。

本文扩展了该建议的原因和示例,描述了为什么传递Context而不是将其存储在另一种类型中很重要。它还强调了在结构体类型中存储Context可能有意义的一个罕见情况,以及如何安全地这样做。

首选作为参数传递的context

为了理解不将context存储在结构体中的建议,让我们考虑首选的把context作为参数的方法:

// Worker获取work并将其添加到远程工作编排服务器。
type Worker struct { /* … */ }

type Work struct { /* … */ }

func New() *Worker {
  return &Worker{}
}

func (w *Worker) Fetch(ctx context.Context) (*Work, error) {
  _ = ctx // 每次调用ctx用于取消、截止时间和元数据。
}

func (w *Worker) Process(ctx context.Context, work *Work) error {
  _ = ctx // 每次调用ctx用于取消、截止时间和元数据。
}

在这里,(*Worker).Fetch(*Worker).Process方法都直接接受context。通过这种作为参数传递的设计,用户可以设置每次调用的截止时间、取消和元数据。而且,很清楚如何使用传递给每个方法的context.Context:没有悬念传递给一个方法的context.Context将被任何其他方法使用。这是因为context被限定为尽可能小的操作,这极大地增加了这个包中context的实用性和清晰度。

在结构体中存储context会导致混乱

让我们用不受欢迎的在结构体中使用context(context-in-struct)方法再次检查上面的Worker示例。它的问题在于,当你将context存储在结构体中时,你会模糊调用者的生命周期,或者更糟的是以不可预知的方式将两个作用域混合在一起:

type Worker struct {
  ctx context.Context
}

func New(ctx context.Context) *Worker {
  return &Worker{ctx: ctx}
}

func (w *Worker) Fetch() (*Work, error) {
  _ = w.ctx // 共享的w.ctx用于取消、截止时间和元数据。
}

func (w *Worker) Process(work *Work) error {
  _ = w.ctx // 共享的w.ctx用于取消、截止时间和元数据。
}

(*Worker).Fetch(*Worker).Process方法都使用存储在Worker中的context。这可以防止FetchProcess的调用者(它们本身可能有不同的context)指定截止时间、请求取消以及在每个调用的基础上附加元数据。例如:用户无法为(*Worker).Fetch提供截止时间,或仅取消(*Worker).Process调用。调用者的生命周期与共享的context混合在一起,并且context的作用域局限于创建的Worker的生命周期。

与通过参数传递(pass-as-argument)的方法相比,API也更容易让用户感到困惑。用户可能会问自己:

  • 由于New需要一个context.Context,构造函数做的工作是否需要取消或截止时间?
  • 传递给Newcontext.Context是否适用于(*Worker).Fetch(*Worker).Process?两者都不?这一个但不是另一个?

API需要大量文档来明确告诉用户context.Context的用途。用户可能还必须阅读源代码,而不是能够依赖API传达的信息。

最后,这么设计一个生产级服务可能非常危险,它的每个请求都没有context,因此不能充分执行取消。如果无法设置每次调用的最后时间,你的进程可能会积压并耗尽其资源(例如内存)!

规则的例外:保持向后兼容性

引入context.Context的Go 1.7发布时,大量API必须以向后兼容的方式添加context支持。例如,net/httpClient方法,像GetDo,是context的绝佳候选者。使用这些方法发送的每个外部请求都将受益于context.Context附带的截止时间、取消和元数据支持。

有两种方法可以向后兼容的方式添加对context.Context的支持:在结构体中包含context,我们稍后会看到,以及复制函数,复制的函数接受context.Context并将Context作为其函数名后缀。复制方法应优先于在结构体中使用context,并在《保持模块兼容》中进一步讨论。但是,在某些情况下它是不切实际的:例如,如果你的API公开了大量函数,那么复制它们可能是不可行的。

net/http包选择了在结构体中使用context的方法,它提供了一个有用的案例研究。让我们看看net/httpDo。在引入context.Context之前,Do定义如下:

// Do发送一个HTTP请求并返回一个HTTP响应 [...]
func (c *Client) Do(req *Request) (*Response, error)

在Go 1.7之后,如果不是因为它会破坏向后兼容性,Do可能看起来像下面这样:

// Do发送一个HTTP请求并返回一个HTTP响应 [...]
func (c *Client) Do(ctx context.Context, req *Request) (*Response, error)

但是,保持向后兼容性并遵守Go 1的兼容性承诺对于标准库至关重要。因此,维护者选择在http.Request结构里添加context.Context以允许支持context.Context而不会破坏向后兼容性:

// Request代表服务器接收到的HTTP请求或将由客户端发送的HTTP请求。
// ...
type Request struct {
  ctx context.Context

  // ...
}

// NewRequestWithContext在给定method、URL和可选body的情况下返回一个新请求。
// [...]
// 给定的ctx用于Request的生命周期。
func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error) {
  // 为简洁起见,本文进行了简化。
  return &Request{
    ctx: ctx,
    // ...
  }
}

// Do发送一个HTTP请求并返回一个HTTP响应 [...]
func (c *Client) Do(req *Request) (*Response, error)

当改造你的API以支持context时,将context.Context添加到结构体中可能是有意义的,如上所述。但是,请记住首先考虑复制你的函数,这允许在不牺牲实用性和理解性的情况下以向后兼容的方式改进context.Context。例如:

// Call在内部使用context.Background;要指定context,请使用CallContext。
func (c *Client) Call() error {
  return c.CallContext(context.Background())
}

func (c *Client) CallContext(ctx context.Context) error {
  // ...
}

总结

context可以轻松地将重要的跨库和跨API信息传播到调用堆栈。但是,它必须一致且清晰地使用,以保持可理解、易于调试和有效。

当作为方法中的第一个参数而不是存储在结构类型中传递时,用户可以充分利用其可扩展性,以便通过调用堆栈构建强大的取消、截止时间和元数据信息树。而且,最重要的是,当它作为参数传入时,可以清楚地理解它的作用域,从而在堆栈上下都有清晰的理解和可调试性。

在设计带有context的API时,请记住以下建议:将context.Context作为参数传入;不要将它存储在结构体中。

Go:优雅成长的代码

本文翻译自《Go: code that grows with grace》,是个PPT。

Andrew Gerrand

Google Sydney

1 Go:优雅成长的代码

Andrew Gerrand

Google Sydney

2 视频

本次演讲的视频于2012年11月在瑞典马尔默的Øredev录制。

在Vimeo上观看演讲

3 Go

你可能听说过Go。

这是我最喜欢的语言。我想你也会喜欢的。

4 什么是Go?

一个开源(BSD 许可)项目:

  • 语言规范,
  • 小型运行时(垃圾收集器、调度器等),
  • 两个编译器(gc和gccgo),
  • “包括电池”标准库,
  • 工具(构建、获取、测试、文档、配置文件、格式),
  • 文档。

截至2012年9月,我们有300多名贡献者。

5 Go是关于组合的

Go是面向对象的,但不是以通常的方式实现。

  • 没有类(可以在任何类型上声明方法)
  • 没有子类继承
  • 隐式满足接口(结构类型)

结果:通过小接口连接起来的多个简单部件。

6 Go是关于并发的

Go提供了类似CSP的并发原语。

  • 轻量级线程(协程(goroutines))
  • 类型化线程安全通信和同步(通道(channels))

结果:可理解的并发代码。

7 Go是关于地鼠的

8 核心价值

Go是关于组合、并发和地鼠的。

记在脑子里。

9 Hello, go

package main

import "fmt"

func main() {
    fmt.Println("Hello, go")
}

10 Hello, net

package main

import (
    "fmt"
    "log"
    "net"
)

const listenAddr = "localhost:4000"

func main() {
    l, err := net.Listen("tcp", listenAddr)
    if err != nil {
        log.Fatal(err)
    }
    for {
        c, err := l.Accept()
        if err != nil {
            log.Fatal(err)
        }
        fmt.Fprintln(c, "Hello!")
        c.Close()
    }
}

11 接口

嘿内托!我们只是使用Fprintln写入网络连接。

这是因为Fprintln写入io.Writer,而net.Conn实现了io.Writer接口。

        fmt.Fprintln(c, "Hello!")

func Fprintln(w io.Writer, a ...interface{}) (n int, err error)

type Writer interface {
    Write(p []byte) (n int, err error)
}

type Conn interface {
    Read(b []byte) (n int, err error)
    Write(b []byte) (n int, err error)
    Close() error
    // ... 省略了一些额外函数 ...
}

12 回声服务器

package main

import (
    "io"
    "log"
    "net"
)

const listenAddr = "localhost:4000"

func main() {
    l, err := net.Listen("tcp", listenAddr)
    if err != nil {
        log.Fatal(err)
    }
    for {
        c, err := l.Accept()
        if err != nil {
            log.Fatal(err)
        }
        io.Copy(c, c)
    }
}

13 深入了解io.Copy

        io.Copy(c, c)

// 将副本从src复制到dst,直到在src上达到EOF或发生错误。它返回复制的字节数和复制时遇到的第一个错误(如果有)。
func Copy(dst Writer, src Reader) (written int64, err error)

type Conn interface {
    Read(b []byte) (n int, err error)
    Write(b []byte) (n int, err error)
    Close() error
    // ... 省略了一些额外函数 ...
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type Reader interface {
    Read(p []byte) (n int, err error)
}

14 协程(Goroutine)

Goroutine是由Go运行时管理的轻量级线程。要在新的goroutine中运行函数,只需在函数调用之前加上“go”关键字。

package main

import (
    "fmt"
    "time"
)

func main() {
    go say("let's go!", 3)
    go say("ho!", 2)
    go say("hey!", 1)
    time.Sleep(4 * time.Second)
}

func say(text string, secs int) {
    time.Sleep(time.Duration(secs) * time.Second)
    fmt.Println(text)
}

15 并发版本的回声服务器

package main

import (
    "io"
    "log"
    "net"
)

const listenAddr = "localhost:4000"

func main() {
    l, err := net.Listen("tcp", listenAddr)
    if err != nil {
        log.Fatal(err)
    }
    for {
        c, err := l.Accept()
        if err != nil {
            log.Fatal(err)
        }
        go io.Copy(c, c)
    }
}

16 “聊天轮盘”

在本次演讲中,我们将看一个基于流行的“聊天轮盘”网站的简单程序。

简而言之:

  • 用户连接,
  • 另一个用户连接,
  • 每一个用户输入的所有内容都会发送给另一个用户。

17 设计

聊天程序类似于回声程序。使用echo,我们将连接的传入数据复制回到同一连接。

对于聊天,我们必须将传入数据从一个用户的连接复制到另一个用户的连接。

复制数据很容易。就像在现实生活中一样,困难的部分是将一个用户与另一个用户匹配。

18 设计图

19 通道(Channel)

Goroutines通过通道进行通信。通道是一个类型化的管道,可以是同步的(无缓冲的)或异步的(有缓冲的)。

package main

import "fmt"

func main() {
    ch := make(chan int)
    go fibs(ch)
    for i := 0; i < 20; i++ {
        fmt.Println(<-ch)
    }
}

func fibs(ch chan int) {
    i, j := 0, 1
    for {
        ch <- j
        i, j = j, i+j
    }
}

20 选择(select)

一个select语句就像一个开关,但它选择通道的操作(并选择其中一个)。

package main

import (
    "fmt"
    "time"
)

func main() {
    ticker := time.NewTicker(time.Millisecond * 250)
    boom := time.After(time.Second * 1)
    for {
        select {
        case <-ticker.C:
            fmt.Println("tick")
        case <-boom:
            fmt.Println("boom!")
            return
        }
    }
}

21 修改回声程序以创建聊天程序

在Accept循环中,我们替换了对io.Copy的调用:

    for {
        c, err := l.Accept()
        if err != nil {
            log.Fatal(err)
        }
        go io.Copy(c, c)
    }

通过调用新函数,match:

    for {
        c, err := l.Accept()
        if err != nil {
            log.Fatal(err)
        }
        go match(c)
    }

22 匹配器

match函数尝试在通道上同时发送和接收连接。

  • 如果发送成功,则连接已移交给另一个goroutine,因此函数退出并且goroutine关闭。
  • 如果接收成功,则表示已从另一个goroutine接收到连接。然后当前的goroutine已有两个连接,因此它在它们之间启动一个聊天会话。
var partner = make(chan io.ReadWriteCloser)

func match(c io.ReadWriteCloser) {
    fmt.Fprint(c, "Waiting for a partner...")
    select {
    case partner <- c:
        // 现在由另一个goroutine处理
    case p := <-partner:
        chat(p, c)
    }
}

23 交谈

聊天功能向每个连接发送问候,然后将数据从一个连接复制到另一个,反之亦然。

请注意,它启动了另一个goroutine,以便复制操作可能同时发生。

func chat(a, b io.ReadWriteCloser) {
    fmt.Fprintln(a, "Found one! Say hi.")
    fmt.Fprintln(b, "Found one! Say hi.")
    go io.Copy(a, b)
    io.Copy(b, a)
}

24 演示(Demo)

25 错误处理

谈话结束后进行清理很重要。为此,我们将每个io.Copy调用的错误值发送到通道,记录任何非零值错误,并关闭两个连接。

func chat(a, b io.ReadWriteCloser) {
    fmt.Fprintln(a, "Found one! Say hi.")
    fmt.Fprintln(b, "Found one! Say hi.")
    errc := make(chan error, 1)
    go cp(a, b, errc)
    go cp(b, a, errc)
    if err := <-errc; err != nil {
        log.Println(err)
    }
    a.Close()
    b.Close()
}

func cp(w io.Writer, r io.Reader, errc chan<- error) {
    _, err := io.Copy(w, r)
    errc <- err
}

26 演示

27 把它带到网上

“可爱的程序,”你说,“但是谁想通过原始TCP连接来聊天呢?”

好观点。让我们通过将其转变为Web应用程序来对其进行现代化改造。

我们将使用websocket代替TCP套接字。

我们将使用Go的标准net/http包提供用户界面,并且websocket支持由go.net子存储库中的websocket包提供。

28 Hello, web

package main

import (
    "fmt"
    "log"
    "net/http"
)

const listenAddr = "localhost:4000"

func main() {
    http.HandleFunc("/", handler)
    err := http.ListenAndServe(listenAddr, nil)
    if err != nil {
        log.Fatal(err)
    }
}

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello, web")
}

29 Hello, WebSocket

var sock = new WebSocket("ws://localhost:4000/");
sock.onmessage = function(m) { console.log("Received:", m.data); }
sock.send("Hello!\n")

package main

import (
    "fmt"
    "golang.org/x/net/websocket"
    "net/http"
)

func main() {
    http.Handle("/", websocket.Handler(handler))
    http.ListenAndServe("localhost:4000", nil)
}

func handler(c *websocket.Conn) {
    var s string
    fmt.Fscan(c, &s)
    fmt.Println("Received:", s)
    fmt.Fprint(c, "How do you do?")
}

30 使用http和websocket包

package main

import (
    "fmt"
    "io"
    "log"
    "net/http"

    "golang.org/x/net/websocket"
)

const listenAddr = "localhost:4000"

func main() {
    http.HandleFunc("/", rootHandler)
    http.Handle("/socket", websocket.Handler(socketHandler))
    err := http.ListenAndServe(listenAddr, nil)
    if err != nil {
        log.Fatal(err)
    }
}

31 提供HTML和JavaScript

import "html/template"
func rootHandler(w http.ResponseWriter, r *http.Request) {
    rootTemplate.Execute(w, listenAddr)
}

var rootTemplate = template.Must(template.New("root").Parse(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script>
    websocket = new WebSocket("ws://{{.}}/socket");
    websocket.onmessage = onMessage;
    websocket.onclose = onClose;
</html>
`))

32 添加套接字(socket)类型

我们不能只使用websocket.Conn而不是net.Conn,因为websocket.Conn由其处理函数保持打开状态。在这里,我们使用一个通道来保持处理程序运行,直到调用套接字的Close方法。

type socket struct {
    conn *websocket.Conn
    done chan bool
}

func (s socket) Read(b []byte) (int, error)  { return s.conn.Read(b) }
func (s socket) Write(b []byte) (int, error) { return s.conn.Write(b) }

func (s socket) Close() error {
    s.done <- true
    return nil
}

func socketHandler(ws *websocket.Conn) {
    s := socket{conn: ws, done: make(chan bool)}
    go match(s)
    <-s.done
}

33 结构嵌入

Go支持一种具有“结构嵌入”特性的“混合”功能。被嵌入结构的类型(下例中的B)可以直接调用嵌入结构(下例中的A)的方法(下例中的Hello)。

type A struct{}

func (A) Hello() {
    fmt.Println("Hello!")
}

type B struct {
    A
}

// func (b B) Hello() { b.A.Hello() } // (隐式的!)

func main() {
    var b B
    b.Hello()
}

34 嵌入websocket连接

通过将*websocket.Conn嵌入io.ReadWriter,我们可以删除套接字显式的Read和Write方法。

type socket struct {
    io.ReadWriter
    done          chan bool
}

func (s socket) Close() error {
    s.done <- true
    return nil
}

func socketHandler(ws *websocket.Conn) {
    s := socket{ws, make(chan bool)}
    go match(s)
    <-s.done
}

35 演示

36 缓解孤独

如果你连接了,但那里没有人怎么办?

如果我们能合成一个聊天伙伴不是很好吗?

我们开始做吧。

37 使用马尔可夫链生成文本

Source
"I am not a number! I am a free man!"

Prefix           Suffix 
"" ""            "I"
"" "I"           "am"
"I" "am"         "a"
"I" "am"         "not"
"a" "free"       "man!"
"am" "a"         "free"
"am" "not"       "a"
"a" "number!"    "I"
"number!" "I"    "am"
"not" "a"        "number!"

Generated sentences beginning with the prefix "I am"
"I am a free man!"
"I am not a number! I am a free man!"
"I am not a number! I am not a number! I am a free man!"
"I am not a number! I am not a number! I am not a number! I am a free man!"

38 使用马尔可夫链生成文本

幸运的是,Go doc包含一个马尔可夫链实现:

golang.org/doc/codewalk/markov

我们将使用经过修改以确保并发使用的版本。

// Chain包含前缀到后缀列表的映射map ("chain")。
// 前缀是由空格连接的prefixLen长度的字符串串。
// 后缀是一个单词。一个前缀可以有多个后缀。
type Chain struct {

// Write将字节解析为存储在Chain中的前缀和后缀。
func (c *Chain) Write(b []byte) (int, error) {

// Generate返回从Chain生成的最多n个单词的字符串。
func (c *Chain) Generate(n int) string {

39 给链(chan)喂食

我们将使用进入系统的所有文本来构建马尔可夫链。

为此,我们将套接字的ReadWriter拆分为Reader和Writer,并将所有传入数据提供给Chain实例。

type socket struct {
    io.Reader
    io.Writer
    done chan bool
}

var chain = NewChain(2) // 2个单词前缀

func socketHandler(ws *websocket.Conn) {
    r, w := io.Pipe()
    go func() {
        _, err := io.Copy(io.MultiWriter(w, chain), ws)
        w.CloseWithError(err)
    }()
    s := socket{r, ws, make(chan bool)}
    go match(s)
    <-s.done
}

40 马尔可夫机器人

// Bot返回一个io.ReadWriteCloser,它使用生成的句子响应给每个传入的写入。
func Bot() io.ReadWriteCloser {
    r, out := io.Pipe() // 用于传出数据
    return bot{r, out}
}

type bot struct {
    io.ReadCloser
    out io.Writer
}

func (b bot) Write(buf []byte) (int, error) {
    go b.speak()
    return len(buf), nil
}

func (b bot) speak() {
    time.Sleep(time.Second)
    msg := chain.Generate(10) // 最多10个单词
    b.out.Write([]byte(msg))
}

41 集成马尔可夫机器人

如果真正的伙伴不加入,那么加入一个机器人。

为此,我们在5秒后触发的select中添加一个case,开始用户的套接字和机器人之间的聊天。

func match(c io.ReadWriteCloser) {
    fmt.Fprint(c, "Waiting for a partner...")
    select {
    case partner <- c:
        // 现在由另一个goroutine处理
    case p := <-partner:
        chat(p, c)
    case <-time.After(5 * time.Second):
        chat(Bot(), c)
    }
}

42 演示

43 还有一件事情

44 同时使用TCP和HTTP

func main() {
    go netListen()
    http.HandleFunc("/", rootHandler)
    http.Handle("/socket", websocket.Handler(socketHandler))
    err := http.ListenAndServe(listenAddr, nil)
    if err != nil {
        log.Fatal(err)
    }
}

func netListen() {
    l, err := net.Listen("tcp", "localhost:4001")
    if err != nil {
        log.Fatal(err)
    }
    for {
        c, err := l.Accept()
        if err != nil {
            log.Fatal(err)
        }
        go match(c)
    }
}

45 演示

46 讨论

47 进一步阅读

关于Go:

golang.org

本次演讲的幻灯片:

go.dev/talks/2012/chat.slide

Rob Pike的“Go并发模式”:

golang.org/s/concurrency-patterns

48 致谢

Andrew Gerrand

Google Sydney

http://andrewgerrand.com/

@enneff

http://golang.org/

好的程序员有三种美德

Perl语言的发明人Larry Wall说,好的程序员有三种美德: 懒惰、急躁和傲慢(Laziness, Impatience and hubris)。

懒惰: 是这样一种品质,它使得你花大力气去避免消耗过多的精力。它敦促你写出节省体力的程序,同时别人也能利用它们。为此你会写出完善的文档,以免别人问你太多问题。

急躁: 是这样一种愤怒—-当你发现计算机懒洋洋地不给出结果。于是你写出更优秀的代码,能尽快真正的解决问题。至少看上去是这样。

傲慢: 极度的自信,使你有信心写出(或维护)别人挑不出毛病的程序。

参考

https://www.zhihu.com/question/435258463/answer/2648273894

对大公司和小公司的批判和认知

美团的二号位王慧文把大公司和小公司的批判和认知太到位,太戳心了。

给大家参考。

大公司的难处在于:

每个人都有职级有晋升的需求,而晋升的答辨委员会通常是被大的成熟的业务的老板们把持的,新业务线的员工在公司通常地位不高,而在发展速度快的业务线晋升也快,发展慢的业务线很难晋升,因为评审委员对新业务不太懂,如果有成果就会高抬贵手,没有成果就不给过。此外大公司内部有业务线之间的人员流动,所以大公司里大家都在追涨杀跌,一个业务势头好大家都过去了,势头不好大家都走了,如果一个业务长期没有进展,里面剩下的都是能力不足的人,即使机会来了也做不成

小公司的难处在于:

搞一段时间没有进展,团队里优秀的人会不断被人挖走。 第一个人被挖走的时候你觉得这哥们叛变革命了,多走几个人你就觉得革命叛变了自己,你就会质疑自己是方向选错了,还是行业选错了,还是做法有问题,还是自己能力不够,还是资源不够,还是投资人不行,会陷入自我否定。 此外帮人一起创业总有一个领头的,公司里领头的通常就是 CEO, CEO 平时要见投资人见媒体要招人,慢慢精力就不在业务上了,而 CTO 是实际管事的,业务发展方向是 CEO 定的,千了一段时间如果没有进展,实际干活的 CTO 就会受到很多职位的诱惑,并且会对业务的发展产生怀疑,如果 CEO 说没搞错大家接着干,CTO 会觉得 CEO 很难沟通,听不进团队意见反馈,可能自己不受认可和尊重,可能就离职了,如果 CEO 让 CTO 负责改版,CTO 改版通常不靠谱,这次改版可能把 CEO 原来的想法颠覆掉了,如果改版不成功,试个 2 次这个创业团队就会面临家里的压力,如果没有进展可能创业队就解散了,所以大部分创业团队会在第二年年底解散。

参考

https://www.zhihu.com/question/31116099/answer/1572832069

高级Go并发模式

本文翻译自《Advanced Go Concurrency Patterns》。

Sameer Ajmani

May 2013

1 高级Go并发模式

Sameer Ajmani

Google

2 视频

该演讲于2013年5月在Google I/O上发表。

在YouTube上观看演讲

3 做好准备

4 Go支持并发

在语言和运行时(runtime)。不是一个库。

这会改变你构建程序的方式。

5 协程(Goroutine)和通道(Channel)

Goroutine是在同一地址空间中独立执行的函数。

go f()
go g(1, 2)

Channel是允许goroutine同步和交换信息的类型值。

c := make(chan int)
go func() { c <- 3 }()
n := <-c

有关基础知识的更多信息,请观看Go并发模式 (Pike, 2012)

6 示例:乒乓球(ping-pong)

type Ball struct{ hits int }

func main() {
    table := make(chan *Ball)
    go player("ping", table)
    go player("pong", table)

    table <- new(Ball) // 开始游戏:投球
    time.Sleep(1 * time.Second)
    <-table // 结束游戏:抓住这个球
}

func player(name string, table chan *Ball) {
    for {
        ball := <-table
        ball.hits++
        fmt.Println(name, ball.hits)
        time.Sleep(100 * time.Millisecond)
        table <- ball
    }
}

7 死锁检测

type Ball struct{ hits int }

func main() {
    table := make(chan *Ball)
    go player("ping", table)
    go player("pong", table)

    // table <- new(Ball) // 开始游戏:投球
    time.Sleep(1 * time.Second)
    <-table // 结束游戏:抓住这个球
}

func player(name string, table chan *Ball) {
    for {
        ball := <-table
        ball.hits++
        fmt.Println(name, ball.hits)
        time.Sleep(100 * time.Millisecond)
        table <- ball
    }
}

8 使用panic输出堆栈信息

type Ball struct{ hits int }

func main() {
    table := make(chan *Ball)
    go player("ping", table)
    go player("pong", table)

    table <- new(Ball) // 开始游戏:投球
    time.Sleep(1 * time.Second)
    <-table // 结束游戏:抓住这个球

    panic("show me the stacks")
}

func player(name string, table chan *Ball) {
    for {
        ball := <-table
        ball.hits++
        fmt.Println(name, ball.hits)
        time.Sleep(100 * time.Millisecond)
        table <- ball
    }
}

9 走(go)很容易,但如何停下来?

长时间运行的程序需要清理。

让我们看看如何编写处理通信、周期性事件和取消的程序。

核心是Go的select语句:就像一个switch,但决定是基于通信能力做出的。

select {
case xc <- x:
    // 把x发送到xc通道
case y := <-yc:
    // 从yc通道接收y
}

10 示例:feed阅读器

我最喜欢的feed阅读器消失了。我需要一个新的。

为什么不写一个?

我们从哪里开始?

11 查找RSS客户端

godoc.org中搜索“rss”会出现几个命中,其中一个提供:

// Fetch获取uri的Items并返回应该尝试下一次获取的时刻。失败时,Fetch返回错误。
func Fetch(uri string) (items []Item, next time.Time, err error)

type Item struct{
    Title, Channel, GUID string // RSS字段的子集
}

但我想要一个流:

<-chan Item

我想要多个订阅。

12 这就是我们所拥有的

type Fetcher interface {
    Fetch() (items []Item, next time.Time, err error)
}

func Fetch(domain string) Fetcher {...} // 从域名获取条目

13 这就是我们想要的

type Subscription interface {
    Updates() <-chan Item // 条目流(stream of Items)
    Close() error         // 关闭流
}

func Subscribe(fetcher Fetcher) Subscription {...} // 把获取到的数据转换为一个流

func Merge(subs ...Subscription) Subscription {...} // 合并多个流

14 示例

func main() {
    // 订阅一些feed,并创建一个合并的更新流。
    merged := Merge(
        Subscribe(Fetch("blog.golang.org")),
        Subscribe(Fetch("googleblog.blogspot.com")),
        Subscribe(Fetch("googledevelopers.blogspot.com")))

    // 在一段时间后关闭所有订阅(subscription)。
    time.AfterFunc(3*time.Second, func() {
        fmt.Println("closed:", merged.Close())
    })

    // 输出流中数据。
    for it := range merged.Updates() {
        fmt.Println(it.Channel, it.Title)
    }

    panic("show me the stacks")
}

15 订阅(Subscribe)

Subscribe创建一个新的Subscription,它重复获取条目,直到调用Close。

func Subscribe(fetcher Fetcher) Subscription {
    s := &sub{
        fetcher: fetcher,
        updates: make(chan Item), // for Updates
    }
    go s.loop()
    return s
}

// sub实现了Subscription接口。
type sub struct {
    fetcher Fetcher   // 获取条目
    updates chan Item // 把条目分发给用户
}

// loop使用s.fetcher方法获取条目并使用s.updates方法发送它们。当调用s.Close方法时,退出loop。
func (s *sub) loop() {...}

16 实现Subscription

要实现Subscription接口,请定义Updates方法和Close方法。

func (s *sub) Updates() <-chan Item {
    return s.updates
}
func (s *sub) Close() error {
    // TODO: 让loop退出
    // TODO: 发现任何错误
    return err
}

17 loop有什么作用?

  • 定期调用Fetch
  • 在Updates通道上发送获取的条目
  • 调用Close时退出,并报告错误

18 幼稚的实现

    for {
        if s.closed {
            close(s.updates)
            return
        }
        items, next, err := s.fetcher.Fetch()
        if err != nil {
            s.err = err                 
            time.Sleep(10 * time.Second)
            continue
        }
        for _, item := range items {
            s.updates <- item
        }
        if now := time.Now(); next.After(now) {
            time.Sleep(next.Sub(now))
        }
     }

func (s *naiveSub) Close() error {
    s.closed = true
    return s.err   
}

19 Bug 1:对s.closed/s.err的非同步访问

    for {
        if s.closed {
            close(s.updates)
            return
        }
        items, next, err := s.fetcher.Fetch()
        if err != nil {
            s.err = err
            time.Sleep(10 * time.Second)
            continue
        }
        for _, item := range items {
            s.updates <- item
        }
        if now := time.Now(); next.After(now) {
            time.Sleep(next.Sub(now))
        }
    }

func (s *naiveSub) Close() error {
    s.closed = true
    return s.err
}

20 竞态(Race)检测器

go run -race naivemain.go
    for {
        if s.closed {
            close(s.updates)
            return
        }
        items, next, err := s.fetcher.Fetch()
        if err != nil {
            s.err = err

func (s *naiveSub) Close() error {
    s.closed = true
    return s.err
}

21 Bug 2:time.Sleep可能会保持循环运行

    for {
        if s.closed {
            close(s.updates)
            return
        }
        items, next, err := s.fetcher.Fetch()
        if err != nil {
            s.err = err                 
            time.Sleep(10 * time.Second)
            continue
        }
        for _, item := range items {
            s.updates <- item
        }
        if now := time.Now(); next.After(now) {
            time.Sleep(next.Sub(now))
        }
    }

22 Bug 3:循环可能会在s.updates上永远阻塞

    for {
        if s.closed {
            close(s.updates)
            return
        }
        items, next, err := s.fetcher.Fetch()
        if err != nil {
            s.err = err                 
            time.Sleep(10 * time.Second)
            continue
        }
        for _, item := range items {
            s.updates <- item
        }
        if now := time.Now(); next.After(now) {
            time.Sleep(next.Sub(now))
        }
    }

23 解决方案

将循环体更改为具有三种情况的select:

  • Close被调用时
  • 调用Fetch时
  • 在s.updates上发送条目

24 结构:for-select循环

循环在它自己的goroutine中运行。

select让循环避免在任何一种状态下无限期地阻塞。

func (s *sub) loop() {
    ... declare mutable state ...
    for {
        ... set up channels for cases ...
        select {
        case <-c1:
            ... read/write state ...
        case c2 <- x:
            ... read/write state ...
        case y := <-c3:
            ... read/write state ...
        }
    }
}

这些case通过循环中的本地状态进行交互。

25 case 1:Close

Close通过s.closure与循环通信。

type sub struct {
    closing chan chan error
}

服务(loop)在其通道(s.closure)上监听请求。

客户端(Close)在s.closure上发送请求:退出并回复错误

在这种情况下,请求中唯一的内容就是回复通道。

26 Case 1: Close

Close要求循环退出并等待响应。

func (s *sub) Close() error {
    errc := make(chan error)
    s.closing <- errc
    return <-errc
}

循环通过回复Fetch错误来处理Close并退出。

    var err error // 当Fetch失败时设置错误
    for {
        select {
        case errc := <-s.closing:
            errc <- err
            close(s.updates) // 告诉接收者我们做完了
            return
        }
    }

27 Case 2: Fetch

在延迟一段时间后安排下一次Fetch。

    var pending []Item // 被fetch追加;被send消费
    var next time.Time // 初始化值是January 1, year 0
    var err error
    for {
        var fetchDelay time.Duration // 初始化值是0(没有延迟)
        if now := time.Now(); next.After(now) {
            fetchDelay = next.Sub(now)
        }
        startFetch := time.After(fetchDelay)

        select {
        case <-startFetch:
            var fetched []Item
            fetched, next, err = s.fetcher.Fetch()
            if err != nil {
                next = time.Now().Add(10 * time.Second)
                break
            }
            pending = append(pending, fetched...)
        }
    }

28 Case 3: Send

一次发送一个获取的条目。

var pending []Item // 被fetch追加;被send消费
for {
    select {
    case s.updates <- pending[0]:
        pending = pending[1:]
    }
}

哎呀。这崩溃了。

29 select和nil通道

在nil通道上发送和接收会被阻塞。

select从不选择阻塞的case。

func main() {
    a, b := make(chan string), make(chan string)
    go func() { a <- "a" }()
    go func() { b <- "b" }()
    if rand.Intn(2) == 0 {
        a = nil
        fmt.Println("nil a")
    } else {
        b = nil
        fmt.Println("nil b")
    }
    select {
    case s := <-a:
        fmt.Println("got", s)
    case s := <-b:
        fmt.Println("got", s)
    }
}

30 Case 3: Send (修复的)

仅在pending非空时启用send。

    var pending []Item // 被fetch追加;被send消费
    for {
        var first Item
        var updates chan Item
        if len(pending) > 0 {
            first = pending[0]
            updates = s.updates // 启用send case
        }

        select {
        case updates <- first:
            pending = pending[1:]
        }
     }

31 select

将三个case放在一起:

   select {
        case errc := <-s.closing:
            errc <- err
            close(s.updates)
            return
        case <-startFetch:
            var fetched []Item
            fetched, next, err = s.fetcher.Fetch()
            if err != nil {
                next = time.Now().Add(10 * time.Second)
                break
            }
            pending = append(pending, fetched...)
        case updates <- first:
            pending = pending[1:]
      }

这些case通过err、next和pending交互。

没有锁,没有条件变量,没有回调函数。

32 已修复的Bug

  • Bug 1:对s.closed和s.err的非同步(unsynchronized)访问
  • Bug 2:time.Sleep可能会保持循环运行
  • Bug 3:循环可能会永远阻塞在发送s.updates上
        select {
        case errc := <-s.closing:
            errc <- err
            close(s.updates)
            return
        case <-startFetch:
            var fetched []Item
            fetched, next, err = s.fetcher.Fetch()
            if err != nil {
                next = time.Now().Add(10 * time.Second)
                break
            }
            pending = append(pending, fetched...)
        case updates <- first:
            pending = pending[1:]
        }

33 我们可以进一步改进循环

34 问题:Fetch可能会返回重复项

    var pending []Item
    var next time.Time
    var err error

        case <-startFetch:
            var fetched []Item
            fetched, next, err = s.fetcher.Fetch()
            if err != nil {
                next = time.Now().Add(10 * time.Second)
                break
            }
            pending = append(pending, fetched...)

35 修复:在添加到pending之前过滤条目

    var pending []Item
    var next time.Time
    var err error
    var seen = make(map[string]bool) // item.GUIDs集合

        case <-startFetch:
            var fetched []Item
            fetched, next, err = s.fetcher.Fetch()
            if err != nil {
                next = time.Now().Add(10 * time.Second)
                break
            }
            for _, item := range fetched {
                if !seen[item.GUID] {
                    pending = append(pending, item)
                    seen[item.GUID] = true
                }
            }

36 问题:pending队列无限制地增长

        case <-startFetch:
            var fetched []Item
            fetched, next, err = s.fetcher.Fetch()
            if err != nil {
                next = time.Now().Add(10 * time.Second)
                break
            }
            for _, item := range fetched {
                if !seen[item.GUID] {
                    pending = append(pending, item)
                    seen[item.GUID] = true
                }
            }

37 问题:pending队列无限制地增长

const maxPending = 10

        var fetchDelay time.Duration
        if now := time.Now(); next.After(now) {
            fetchDelay = next.Sub(now)
        }
        var startFetch <-chan time.Time
        if len(pending) < maxPending {
            startFetch = time.After(fetchDelay) // 启用fetch case
        }

可以改为从pending的头部删除较旧的条目。

38 问题:Fetch上的循环被阻塞

        case <-startFetch:
            var fetched []Item
            fetched, next, err = s.fetcher.Fetch()
            if err != nil {
                next = time.Now().Add(10 * time.Second)
                break
            }
            for _, item := range fetched {
                if !seen[item.GUID] {
                    pending = append(pending, item)
                    seen[item.GUID] = true         
                }
            }

39 修复:异步运行Fetch

为fetchDone添加一个新的select case。

type fetchResult struct{ fetched []Item; next time.Time; err error }

    var fetchDone chan fetchResult // 如果不是nil,则表示Fetch正在运行

        var startFetch <-chan time.Time
        if fetchDone == nil && len(pending) < maxPending {
            startFetch = time.After(fetchDelay) // 启用fetch case
        }

        select {
        case <-startFetch:
            fetchDone = make(chan fetchResult, 1)
            go func() {
                fetched, next, err := s.fetcher.Fetch()
                fetchDone <- fetchResult{fetched, next, err}
            }()
        case result := <-fetchDone:
            fetchDone = nil
            // 使用result.fetched, result.next, result.err

40 实现订阅

响应式。干净。易于阅读和更改。

三种技术:

  • for-select循环
  • 服务通道,回复通道(chan chan err)
  • select case里的nil通道

更多详细信息在线,包括Merge。

41 总结

并发编程可能很棘手。

Go让它变得更容易:

  • 通道传送数据、定时器事件、取消信号
  • goroutines序列化对本地可变状态的访问
  • 堆栈跟踪和死锁检测器
  • 竞态检测器

42 链接

Go并发模式(2012)

go.dev/talks/2012/concurrency.slide

并发不是并行

golang.org/s/concurrency-is-not-parallelism

通过通信共享内存

golang.org/doc/codewalk/sharemem

Go Tour (在你的浏览器里学习Go)

tour.golang.org

43 致谢

Sameer Ajmani

Google

http://profiles.google.com/ajmani

@Sajma

http://golang.org