<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Tony Bai &#187; linker</title>
	<atom:link href="http://tonybai.com/tag/linker/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Sat, 23 May 2026 23:26:24 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>探索Go gcflags的使用模式与完整参数选项列表</title>
		<link>https://tonybai.com/2025/01/22/gcflags-options-list-and-usage/</link>
		<comments>https://tonybai.com/2025/01/22/gcflags-options-list-and-usage/#comments</comments>
		<pubDate>Wed, 22 Jan 2025 10:37:43 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[cmd]]></category>
		<category><![CDATA[Compiler]]></category>
		<category><![CDATA[gcflags]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[gobuild]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[gomodule]]></category>
		<category><![CDATA[gotoolchain]]></category>
		<category><![CDATA[ldflags]]></category>
		<category><![CDATA[linker]]></category>
		<category><![CDATA[Package]]></category>
		<category><![CDATA[package-pattern]]></category>
		<category><![CDATA[std]]></category>
		<category><![CDATA[优化]]></category>
		<category><![CDATA[内联]]></category>
		<category><![CDATA[包]]></category>
		<category><![CDATA[包模式]]></category>
		<category><![CDATA[工具链]]></category>
		<category><![CDATA[标准库]]></category>
		<category><![CDATA[模块]]></category>
		<category><![CDATA[汇编]]></category>
		<category><![CDATA[编译]]></category>
		<category><![CDATA[链接]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=4466</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/01/22/gcflags-options-list-and-usage Go build是Go开发中不可或缺的构建工具，其中-gcflags参数为开发者提供了向编译器传递额外选项的能力。然而，关于-gcflags的完整参数选项和使用模式，官方文档多有局限，很多开发者对此了解不深。本文将系统性地解析-gcflags的完整参数来源以及其结合包模式（package pattern）的使用方法，供大家参考。 注：本文主要以-gcflags为例，其实go build的-ldflags参数与-gcflags在使用方法上如出一辙，唯一不同的是ldflags是将参数传递给go链接器。 gcflags是Go构建工具的一个标志，用于向Go编译器 (go tool compile) 传递额外的编译参数。通过它，开发者可以调整编译行为，例如禁用优化、生成调试信息或输出反汇编代码等。 Go build文档中关于-gcflags的说明很短小精悍： $go help build ... ... -gcflags '[pattern=]arg list' arguments to pass on each go tool compile invocation. -ldflags '[pattern=]arg list' arguments to pass on each go tool link invocation. ... ... The -asmflags, -gccgoflags, -gcflags, and -ldflags flags accept a [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/gcflags-options-list-and-usage-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/01/22/gcflags-options-list-and-usage">本文永久链接</a> &#8211; https://tonybai.com/2025/01/22/gcflags-options-list-and-usage</p>
<p>Go build是Go开发中不可或缺的构建工具，其中-gcflags参数为开发者提供了向编译器传递额外选项的能力。然而，关于-gcflags的完整参数选项和使用模式，官方文档多有局限，很多开发者对此了解不深。本文将系统性地解析-gcflags的完整参数来源以及其结合包模式（package pattern）的使用方法，供大家参考。</p>
<blockquote>
<p>注：本文主要以-gcflags为例，其实go build的-ldflags参数与-gcflags在使用方法上如出一辙，唯一不同的是ldflags是将参数传递给go链接器。</p>
</blockquote>
<p>gcflags是Go构建工具的一个标志，用于向Go编译器 (go tool compile) 传递额外的编译参数。通过它，开发者可以调整编译行为，例如禁用优化、生成调试信息或输出反汇编代码等。</p>
<p>Go build文档中关于-gcflags的说明很短小精悍：</p>
<pre><code>$go help build
... ...
    -gcflags '[pattern=]arg list'
        arguments to pass on each go tool compile invocation.
    -ldflags '[pattern=]arg list'
        arguments to pass on each go tool link invocation.
... ...

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.

... ...
</code></pre>
<p>多数Go初学者初次看到上述关于gcflags的说明，都无法知道到底有哪些arg可用以及究竟如何使用gcflags，而<a href="https://pkg.go.dev/cmd/go">Go cmd文档</a>中关于gcflags的内容也仅限于上述这些。</p>
<p>我将大家遇到的主要问题总结为下面两条：</p>
<ul>
<li>gcflags的完整参数选项列表在哪里可以找到？</li>
<li>gcflags的使用模式，尤其是其中的package pattern应该如何正确使用？</li>
</ul>
<p>如果你能正确回答上述两个问题，那你就基本掌握了gcflags的使用，<strong>大可不必继续往下看了</strong>。</p>
<p>否则，我们就一起分别看一下这两个问题该如何解答。</p>
<p>在哪里能<strong>查找到gcflags可用的全部参数选项呢</strong>？go help build不行，<a href="https://pkg.go.dev/cmd/go">go command的web文档</a>中没有！甚至<a href="https://pkg.go.dev/cmd/compile">Go tool compile的web文档</a>中列举的gcflag的参数列表也是不全的(或者说是文档没有及时同步最新的参数列表变化)，也许我们应该提一个issue给Go团队^_^。</p>
<p>远在天边近在眼前！下面命令可以让-gcflag可用的参数选项完整列表尽收眼底：</p>
<pre><code>$go tool compile -h
usage: compile [options] file.go...
  -%    debug non-static initializers
  -+    compiling runtime
  -B    disable bounds checking
  -C    disable printing of columns in error messages
  -D path
        set relative path for local imports
  -E    debug symbol export
  -I directory
        add directory to import search path
  -K    debug missing line numbers
  -L    also show actual source file names in error messages for positions affected by //line directives
  -N    disable optimizations
  -S    print assembly listing
  -V    print version and exit
  -W    debug parse tree after type checking
  -asan
        build code compatible with C/C++ address sanitizer
  -asmhdr file
        write assembly header to file
... ...
</code></pre>
<p>同样，如果你要查看-ldflags的完整参数选项列表，你可以使用下面命令：</p>
<pre><code>$go tool link -h
usage: link [options] main.o
  -B note
        add an ELF NT_GNU_BUILD_ID note when using ELF; use "gobuildid" to generate it from the Go build ID
  -E entry
        set entry symbol name
  -H type
        set header type
  -I linker
        use linker as ELF dynamic linker
  -L directory
        add specified directory to library path
  -R quantum
        set address rounding quantum (default -1)
  -T int
        set the start address of text symbols (default -1)
  -V    print version and exit
  -X definition
        add string value definition of the form importpath.name=value
  -a    no-op (deprecated)
  -asan
        enable ASan interface
... ...
</code></pre>
<p>到这里，我们得到了第一个问题的答案。</p>
<p>接下来，我们<strong>再来看第二个问题</strong>：-gcflags的使用模式。</p>
<p>根据go help build的输出，我们知道-gcflags的使用形式如下：</p>
<pre><code>-gcflags '[pattern=]arg list'
</code></pre>
<p>其中：</p>
<ul>
<li>[pattern=]（可选）：包模式(package pattern)，用于作用范围控制，即限定参数仅应用于特定的包。如果省略此部分，则参数仅适用于命令行中指定的包。</li>
<li>arg list：参数选项列表，多个参数以空格分隔。</li>
</ul>
<p>对包模式有很好地理解并非是使用好gcflags的必要条件。但在一些复杂项目中，我们可能会通过包模式精确控制调试和优化，在这种情况下，对包模式有深入理解还是大有裨益的。</p>
<p>包模式是一种通过匹配规则指定目标包的方式，常见的包模式有几下几种：</p>
<ul>
<li>./&#8230;：匹配当前目录及其所有子目录中的包。</li>
<li>/DIR/&#8230;：匹配/DIR及其子目录中的包。</li>
<li>cmd/&#8230;：匹配Go仓库中cmd目录下的所有命令包。</li>
<li>github.com/user/repo/&#8230;：匹配该github仓库中的所有包。</li>
<li>all：GOPATH模式下，匹配的是所有GOPATH路径中的包，Go module模式下，all匹配主模块及其所有依赖的包（包括测试依赖）。</li>
<li>std：仅匹配标准库包。</li>
<li>cmd：匹配Go仓库中的Go命令及其内部包(internal)。</li>
</ul>
<p>基于上述关于gcflags使用形式以及包模式的说明，我们举几个示例来直观理解一下gcflags的用法：</p>
<ul>
<li>对单个包设置参数</li>
</ul>
<pre><code>$go build -gcflags=-S fmt
</code></pre>
<p>上述命令中的参数-S仅作用于fmt包，显示其反汇编代码。</p>
<ul>
<li>对特定模式(比如all/std等)的包设置参数</li>
</ul>
<pre><code>$go build -gcflags='all=-N -l'
</code></pre>
<p>在Go module模式下，参数-N和-l应用于当前主模块所有包及其依赖，禁用优化和内联。</p>
<ul>
<li>对不同包模式设置不同参数</li>
</ul>
<pre><code>$go build -gcflags='fmt=-S' -gcflags='net/http=-N'
</code></pre>
<p>Go build命令行中可以<strong>多次使用-gcflags</strong>，上述命令中的第一个gcflags对fmt包启用反汇编输出(-S)。第二个gcflags对net/http包禁用优化(-N)。</p>
<ul>
<li>模式的优先级</li>
</ul>
<pre><code>$go build -gcflags='all=-N' -gcflags='fmt=-S'
</code></pre>
<p>像上面命令中，两个gcflags都匹配了fmt包，或者说两个gcflags的作用范围都包含了fmt包，这种情况下哪些参数会对fmt包生效呢？Go规定：当一个包匹配多个模式时，以最后一个匹配的参数为准。在这个例子中，fmt包将只应用-S参数，而其他包应用-N参数。</p>
<p>到这里，我们完成了对两个关于gcflags问题的回答!</p>
<p>最后小结一下：</p>
<ul>
<li>gcflags(以及-ldflags)是Go构建工具中的重要选项，能极大提升调试和优化效率。</li>
<li>gcflags的完整的参数选项需通过底层工具获取，即go tool compile -h和go tool link -h。</li>
<li>对包模式的灵活使用能够精确控制gcflags参数的作用范围，为复杂项目提供了更大的自由度。</li>
</ul>
<p>通过本篇文章，希望你能掌握查看gcflags完整参数列表的方法以及gcflags的使用模式，并在构建和调试Go项目时能更加得心应手。</p>
<hr />
<p><a href="https://public.zsxq.com/groups/51284458844544">Gopher部落知识星球</a>在2025年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。并且，2025年将在星球首发“Go陷阱与缺陷”和“Go原理课”专栏！此外，我们还会加强星友之间的交流和互动。欢迎大家踊跃提问，分享心得，讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾<br />
。让我相聚在Gopher部落，享受coding的快乐! 欢迎大家踊跃加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/go-programming-from-beginner-to-master-qr.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /></p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻) &#8211; https://gopherdaily.tonybai.com</p>
<p>我的联系方式：</p>
<ul>
<li>微博(暂不可用)：https://weibo.com/bigwhite20xx</li>
<li>微博2：https://weibo.com/u/6484441286</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>Gopher Daily归档 &#8211; https://github.com/bigwhite/gopherdaily</li>
<li>Gopher Daily Feed订阅 &#8211; https://gopherdaily.tonybai.com/feed</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/01/22/gcflags-options-list-and-usage/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Go编译的几个细节，连专家也要停下来想想</title>
		<link>https://tonybai.com/2024/11/11/some-details-about-go-compilation/</link>
		<comments>https://tonybai.com/2024/11/11/some-details-about-go-compilation/#comments</comments>
		<pubDate>Sun, 10 Nov 2024 22:13:45 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[archive]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[CFLAGS]]></category>
		<category><![CDATA[Cgo]]></category>
		<category><![CDATA[CGO_ENABLED]]></category>
		<category><![CDATA[Compiler]]></category>
		<category><![CDATA[crypto]]></category>
		<category><![CDATA[DWARF]]></category>
		<category><![CDATA[expvar]]></category>
		<category><![CDATA[fmt]]></category>
		<category><![CDATA[GCC]]></category>
		<category><![CDATA[gcflags]]></category>
		<category><![CDATA[getaddrinfo]]></category>
		<category><![CDATA[getnameinfo]]></category>
		<category><![CDATA[Glibc]]></category>
		<category><![CDATA[glibc-static]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go-sqlite3]]></category>
		<category><![CDATA[go1.23]]></category>
		<category><![CDATA[gobuild]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[golist]]></category>
		<category><![CDATA[gomodule]]></category>
		<category><![CDATA[helloworld]]></category>
		<category><![CDATA[import]]></category>
		<category><![CDATA[init]]></category>
		<category><![CDATA[inittask]]></category>
		<category><![CDATA[Inline]]></category>
		<category><![CDATA[ldd]]></category>
		<category><![CDATA[ldflags]]></category>
		<category><![CDATA[LD_LIBRARY_PATH]]></category>
		<category><![CDATA[libresolv.so]]></category>
		<category><![CDATA[linker]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Make]]></category>
		<category><![CDATA[Makefile]]></category>
		<category><![CDATA[NameResolution]]></category>
		<category><![CDATA[nm]]></category>
		<category><![CDATA[Package]]></category>
		<category><![CDATA[sqlite-devel]]></category>
		<category><![CDATA[sqlite3]]></category>
		<category><![CDATA[TinyGo]]></category>
		<category><![CDATA[yum]]></category>
		<category><![CDATA[优化]]></category>
		<category><![CDATA[内联]]></category>
		<category><![CDATA[动态链接]]></category>
		<category><![CDATA[包]]></category>
		<category><![CDATA[死码消除]]></category>
		<category><![CDATA[编译器]]></category>
		<category><![CDATA[静态链接]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=4383</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2024/11/11/some-details-about-go-compilation 在Go开发中，编译相关的问题看似简单，但实则蕴含许多细节。有时，即使是Go专家也需要停下来，花时间思考答案或亲自验证。本文将通过几个具体问题，和大家一起探讨Go编译过程中的一些你可能之前未曾关注的细节。 注：本文示例使用的环境为Go 1.23.0、Linux Kernel 3.10.0和CentOS 7.9。 1. Go编译默认采用静态链接还是动态链接？ 我们来看第一个问题：Go编译默认采用静态链接还是动态链接呢？ 很多人脱口而出：动态链接，因为CGO_ENABLED默认值为1，即开启Cgo。也有些人会说：“其实Go编译器默认是静态链接的，只有在使用C语言库时才会动态链接”。那么到底哪个是正确的呢？ 我们来看一个具体的示例。但在这之前，我们要承认一个事实，那就是CGO_ENABLED默认值为1，你可以通过下面命令来验证这一点： $go env&#124;grep CGO_ENABLED CGO_ENABLED='1' 验证Go默认究竟是哪种链接，我们写一个hello, world的Go程序即可： // go-compilation/main.go package main import "fmt" func main() { fmt.Println("hello, world") } 构建该程序： $go build -o helloworld-default main.go 之后，我们查看一下生成的可执行文件helloworld-default的文件属性： $file helloworld-default helloworld-default: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped $ldd [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/some-details-about-go-compilation-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2024/11/11/some-details-about-go-compilation">本文永久链接</a> &#8211; https://tonybai.com/2024/11/11/some-details-about-go-compilation</p>
<p>在Go开发中，编译相关的问题看似简单，但实则蕴含许多细节。有时，即使是Go专家也需要停下来，花时间思考答案或亲自验证。本文将通过几个具体问题，和大家一起探讨Go编译过程中的一些你可能之前未曾关注的细节。</p>
<blockquote>
<p>注：本文示例使用的环境为<a href="https://tonybai.com/2024/08/19/some-changes-in-go-1-23/">Go 1.23.0</a>、Linux Kernel 3.10.0和CentOS 7.9。</p>
</blockquote>
<h2>1. Go编译默认采用静态链接还是动态链接？</h2>
<p>我们来看第一个问题：Go编译默认采用静态链接还是动态链接呢？</p>
<p>很多人脱口而出：<a href="https://tonybai.com/2011/07/07/also-talk-about-shared-library-2/">动态链接</a>，因为CGO_ENABLED默认值为1，即开启Cgo。也有些人会说：“其实Go编译器默认是静态链接的，只有在使用C语言库时才会动态链接”。那么到底哪个是正确的呢？</p>
<p>我们来看一个具体的示例。但在这之前，我们要承认一个事实，那就是CGO_ENABLED默认值为1，你可以通过下面命令来验证这一点：</p>
<pre><code>$go env|grep CGO_ENABLED
CGO_ENABLED='1'
</code></pre>
<p>验证Go默认究竟是哪种链接，我们写一个hello, world的Go程序即可：</p>
<pre><code>// go-compilation/main.go

package main

import "fmt"

func main() {
    fmt.Println("hello, world")
}
</code></pre>
<p>构建该程序：</p>
<pre><code>$go build -o helloworld-default main.go
</code></pre>
<p>之后，我们查看一下生成的可执行文件helloworld-default的文件属性：</p>
<pre><code>$file helloworld-default
helloworld-default: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
$ldd helloworld-default
   不是动态可执行文件
</code></pre>
<p>我们看到，虽然CGO_ENABLED=1，但默认情况下，Go构建出的helloworld程序是静态链接的(statically linked)。</p>
<p>那么默认情况下，Go编译器是否都会采用静态链接的方式来构建Go程序呢？我们给上面的main.go添加一行代码：</p>
<pre><code>// go-compilation/main-with-os-user.go

package main

import (
    "fmt"
    _ "os/user"
)

func main() {
    fmt.Println("hello, world")
}
</code></pre>
<p>和之前的hello, world不同的是，这段代码多了一行<strong>包的空导入</strong>，导入的是os/user这个包。</p>
<p>编译这段代码，我们得到helloworld-with-os-user可执行文件。</p>
<pre><code>$go build -o helloworld-with-os-user main-with-os-user.go
</code></pre>
<p>使用file和ldd检视文件helloworld-with-os-user：</p>
<pre><code>$file helloworld-with-os-user
helloworld-with-os-user: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), not stripped

$ldd helloworld-with-os-user
    linux-vdso.so.1 =&gt;  (0x00007ffcb8fd4000)
    libpthread.so.0 =&gt; /lib64/libpthread.so.0 (0x00007fb5d6fce000)
    libc.so.6 =&gt; /lib64/libc.so.6 (0x00007fb5d6c00000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fb5d71ea000)
</code></pre>
<p>我们看到：<strong>一行新代码居然让helloworld从静态链接变为了动态链接</strong>，同时这也是如何编译出一个hello world版的动态链接Go程序的答案。</p>
<p>通过nm命令我们还可以查看Go程序依赖了哪些C库的符号：</p>
<pre><code>$nm -a helloworld-with-os-user |grep " U "
                 U abort
                 U __errno_location
                 U fprintf
                 U fputc
                 U free
                 U fwrite
                 U malloc
                 U mmap
                 U munmap
                 U nanosleep
                 U pthread_attr_destroy
                 U pthread_attr_getstack
                 U pthread_attr_getstacksize
                 U pthread_attr_init
                 U pthread_cond_broadcast
                 U pthread_cond_wait
                 U pthread_create
                 U pthread_detach
                 U pthread_getattr_np
                 U pthread_key_create
                 U pthread_mutex_lock
                 U pthread_mutex_unlock
                 U pthread_self
                 U pthread_setspecific
                 U pthread_sigmask
                 U setenv
                 U sigaction
                 U sigaddset
                 U sigemptyset
                 U sigfillset
                 U sigismember
                 U stderr
                 U strerror
                 U unsetenv
                 U vfprintf
</code></pre>
<p>由此，我们可以得到一个结论，在默认情况下(CGO_ENABLED=1)，Go会尽力使用静态链接的方式，但在某些情况下，会采用动态链接。那么究竟在哪些情况下会默认生成动态链接的程序呢？我们继续往下看。</p>
<h2>2. 在何种情况下默认会生成动态链接的Go程序？</h2>
<p>在以下几种情况下，Go编译器会默认(CGO_ENABLED=1)生成动态链接的可执行文件，我们逐一来看一下。</p>
<h3>2.1 一些使用C实现的标准库包</h3>
<p>根据上述示例，我们可以看到，在某些情况下，即使只依赖标准库，Go 仍会在CGO_ENABLED=1的情况下采用动态链接。这是因为代码依赖的标准库包使用了C版本的实现。虽然这种情况并不常见，但<a href="https://pkg.go.dev/os/user">os/user包</a>和<a href="https://pkg.go.dev/net">net包</a>是两个典型的例子。</p>
<p>os/user包的示例在前面我们已经见识过了。user包允许开发者通过名称或ID查找用户账户。对于大多数Unix系统(包括linux)，该包内部有两种版本的实现，用于解析用户和组ID到名称，并列出附加组ID。一种是用纯Go编写，解析/etc/passwd和/etc/group文件。另一种是基于cgo的，依赖于标准C库（libc）中的例程，如getpwuid_r、getgrnam_r和getgrouplist。当cgo可用(CGO_ENABLED=1)，并且特定平台的libc实现了所需的例程时，将使用基于cgo的（libc支持的）代码，即采用动态链接方式。</p>
<p>同样，net包在名称解析(Name Resolution，即域名或主机名对应IP查找)上针对大多数Unix系统也有两个版本的实现：一个是纯Go版本，另一个是基于C的版本。C版本会在cgo可用且特定平台实现了相关C函数(比如getaddrinfo和getnameinfo等)时使用。</p>
<p>下面是一个简单的使用net包并采用动态链接的示例：</p>
<pre><code>// go-compilation/main-with-net.go

package main

import (
    "fmt"
    _ "net"
)

func main() {
    fmt.Println("hello, world")
}
</code></pre>
<p>编译后，我们查看一下文件属性：</p>
<pre><code>$go build -o helloworld-with-net main-with-net.go 

$file helloworld-with-net
helloworld-with-net: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), not stripped

$ldd helloworld-with-net
    linux-vdso.so.1 =&gt;  (0x00007ffd75dfd000)
    libresolv.so.2 =&gt; /lib64/libresolv.so.2 (0x00007fdda2cf9000)
    libpthread.so.0 =&gt; /lib64/libpthread.so.0 (0x00007fdda2add000)
    libc.so.6 =&gt; /lib64/libc.so.6 (0x00007fdda270f000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fdda2f13000)
</code></pre>
<p>我们看到C版本实现依赖了libresolv.so这个用于名称解析的C库。</p>
<p>由此可得，当Go在默认cgo开启时，一旦依赖了标准库中拥有C版本实现的包，比如os/user、net等，Go编译器会采用动态链接的方式编译Go可执行程序。</p>
<h3>2.2 显式使用cgo调用外部C程序</h3>
<p>如果使用cgo与外部C代码交互，那么生成的可执行文件必然会包含动态链接。下面我们来看一个调用cgo的简单示例。</p>
<p>首先，建立一个简单的C lib：</p>
<pre><code>// go-compilation/my-c-lib

$tree my-c-lib
my-c-lib
├── Makefile
├── mylib.c
└── mylib.h

// go-compilation/my-c-lib/Makefile

.PHONY:  all static

all:
        gcc -c -fPIC -o mylib.o mylib.c
        gcc -shared -o libmylib.so mylib.o
static:
        gcc -c -fPIC -o mylib.o mylib.c
        ar rcs libmylib.a mylib.o

// go-compilation/my-c-lib/mylib.h

#ifndef MYLIB_H
#define MYLIB_H

void hello();
int add(int a, int b);

#endif // MYLIB_H

// go-compilation/my-c-lib/mylib.c

#include &lt;stdio.h&gt;

void hello() {
    printf("Hello from C!\n");
}

int add(int a, int b) {
    return a + b;
}
</code></pre>
<p>执行make all构建出动态链接库libmylib.so！接下来，我们编写一个Go程序通过cgo调用libmylib.so中：</p>
<pre><code>// go-compilation/main-with-call-myclib.go 

package main

/*
#cgo CFLAGS: -I ./my-c-lib
#cgo LDFLAGS: -L ./my-c-lib -lmylib
#include "mylib.h"
*/
import "C"
import "fmt"

func main() {
    // 调用 C 函数
    C.hello()

    // 调用 C 中的加法函数
    result := C.add(3, 4)
    fmt.Printf("Result of addition: %d\n", result)
}
</code></pre>
<p>编译该源码：</p>
<pre><code>$go build -o helloworld-with-call-myclib main-with-call-myclib.go
</code></pre>
<p>通过ldd可以看到，可执行文件helloworld-with-call-myclib是动态链接的，并依赖libmylib.so：</p>
<pre><code>$ldd helloworld-with-call-myclib
    linux-vdso.so.1 =&gt;  (0x00007ffcc39d8000)
    libmylib.so =&gt; not found
    libpthread.so.0 =&gt; /lib64/libpthread.so.0 (0x00007f7166df5000)
    libc.so.6 =&gt; /lib64/libc.so.6 (0x00007f7166a27000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f7167011000)
</code></pre>
<p>设置LD_LIBRARY_PATH(为了让程序找到libmylib.so)并运行可执行文件helloworld-with-call-myclib：</p>
<pre><code>$ LD_LIBRARY_PATH=./my-c-lib:$LD_LIBRARY_PATH ./helloworld-with-call-myclib
Hello from C!
Result of addition: 7
</code></pre>
<h3>2.3 使用了依赖cgo的第三方包</h3>
<p>在日常开发中，我们经常依赖一些第三方包，有些时候这些第三方包依赖cgo，比如<a href="https://github.com/mattn/go-sqlite3">mattn/go-sqlite3</a>。下面就是一个依赖go-sqlite3包的示例：</p>
<pre><code>// go-compilation/go-sqlite3/main.go
package main

import (
    "database/sql"
    "fmt"
    "log"

    _ "github.com/mattn/go-sqlite3"
)

func main() {
    // 打开数据库（如果不存在，则创建）
    db, err := sql.Open("sqlite3", "./test.db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // 创建表
    sqlStmt := `CREATE TABLE IF NOT EXISTS user (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT);`
    _, err = db.Exec(sqlStmt)
    if err != nil {
        log.Fatalf("%q: %s\n", err, sqlStmt)
    }

    // 插入数据
    _, err = db.Exec(`INSERT INTO user (name) VALUES (?)`, "Alice")
    if err != nil {
        log.Fatal(err)
    }

    // 查询数据
    rows, err := db.Query(`SELECT id, name FROM user;`)
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()

    for rows.Next() {
        var id int
        var name string
        err = rows.Scan(&amp;id, &amp;name)
        if err != nil {
            log.Fatal(err)
        }
        fmt.Printf("%d: %s\n", id, name)
    }

    // 检查查询中的错误
    if err = rows.Err(); err != nil {
        log.Fatal(err)
    }
}
</code></pre>
<p>编译和运行该源码：</p>
<pre><code>$go build demo
$ldd demo
    linux-vdso.so.1 =&gt;  (0x00007ffe23d8e000)
    libdl.so.2 =&gt; /lib64/libdl.so.2 (0x00007faf0ddef000)
    libpthread.so.0 =&gt; /lib64/libpthread.so.0 (0x00007faf0dbd3000)
    libc.so.6 =&gt; /lib64/libc.so.6 (0x00007faf0d805000)
    /lib64/ld-linux-x86-64.so.2 (0x00007faf0dff3000)
$./demo
1: Alice
</code></pre>
<p>到这里，有些读者可能会问一个问题：如果需要在上述依赖场景中生成静态链接的Go程序，该怎么做呢？接下来，我们就来看看这个问题的解决细节。</p>
<h2>3. 如何在上述情况下实现静态链接？</h2>
<p>到这里是不是有些烧脑了啊！我们针对上一节的三种情况，分别对应来看一下静态编译的方案。</p>
<h3>3.1 仅依赖标准包</h3>
<p>在前面我们说过，之所以在使用os/user、net包时会在默认情况下采用动态链接，是因为Go使用了这两个包对应功能的C版实现，如果要做静态编译，让Go编译器选择它们的纯Go版实现即可。那我们仅需要关闭CGO即可，以依赖标准库os/user为例：</p>
<pre><code>$CGO_ENABLED=0 go build -o helloworld-with-os-user-static main-with-os-user.go
$file helloworld-with-os-user-static
helloworld-with-os-user-static: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
$ldd helloworld-with-os-user-static
    不是动态可执行文件
</code></pre>
<h3>3.2 使用cgo调用外部c程序（静态链接）</h3>
<p>对于依赖cgo调用外部c的程序，我们要使用静态链接就必须要求外部c库提供静态库，因此，我们需要my-c-lib提供一份libmylib.a，这通过下面命令可以实现(或执行make static)：</p>
<pre><code>$gcc -c -fPIC -o mylib.o mylib.c
$ar rcs libmylib.a mylib.o
</code></pre>
<p>有了libmylib.a后，我们还要让Go程序静态链接该.a文件，于是我们需要修改一下Go源码中cgo链接的flag，加上静态链接的选项：</p>
<pre><code>// go-compilation/main-with-call-myclib-static.go
... ...
#cgo LDFLAGS: -static -L my-c-lib -lmylib
... ...
</code></pre>
<p>编译链接并查看一下文件属性：</p>
<pre><code>$go build -o helloworld-with-call-myclib-static main-with-call-myclib-static.go

$file helloworld-with-call-myclib-static
helloworld-with-call-myclib-static: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=b3da3ed817d0d04230460069b048cab5f5bfc3b9, not stripped
</code></pre>
<p>我们得到了预期的结果！</p>
<h3>3.3 依赖使用cgo的外部go包（静态链接）</h3>
<p>最麻烦的是这类情况，要想实现静态链接，我们需要找出外部go依赖的所有c库的.a文件(静态共享库)。以我们的go-sqlite3示例为例，go-sqlite3是sqlite库的go binding，它依赖sqlite库，同时所有第三方c库都依赖libc，我们还要准备一份libc的.a文件，下面我们就先安装这些：</p>
<pre><code>$yum install -y gcc glibc-static sqlite-devel
... ...

已安装:
  sqlite-devel.x86_64 0:3.7.17-8.el7_7.1                                                                                          

更新完毕:
  glibc-static.x86_64 0:2.17-326.el7_9.3
</code></pre>
<p>接下来，我们就来以静态链接的方式在go-compilation/go-sqlite3-static下编译一下：</p>
<pre><code>$go build -tags 'sqlite_omit_load_extension' -ldflags '-linkmode external -extldflags "-static"' demo

$file ./demo
./demo: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=c779f5c3eaa945d916de059b56d94c23974ce61c, not stripped
</code></pre>
<p>这里命令行中的-tags &#8216;sqlite_omit_load_extension&#8217;用于禁用SQLite3的动态加载功能，确保更好的静态链接兼容性。而-ldflags &#8216;-linkmode external -extldflags “-static”&#8216;的含义是使用外部链接器(比如gcc linker)，并强制静态链接所有库。</p>
<p>我们再看完略烧脑的几个细节后，再来看一个略轻松的话题。</p>
<h2>4. Go编译出的可执行文件过大，能优化吗？</h2>
<p>Go编译出的二进制文件一般较大，一个简单的“Hello World”程序通常在2MB左右：</p>
<pre><code>$ls -lh helloworld-default
-rwxr-xr-x 1 root root 2.1M 11月  3 10:39 helloworld-default
</code></pre>
<p>这一方面是因为Go将整个runtime都编译到可执行文件中了，另一方面也是因为Go静态编译所致。那么在默认情况下，Go二进制文件的大小还有优化空间么？方法不多，有两种可以尝试：</p>
<ul>
<li>去除符号表和调试信息</li>
</ul>
<p>在编译时使用-ldflags=”-s -w”标志可以去除符号表和调试符号，其中-s用于去掉符号表和调试信息，-w用于去掉DWARF调试信息，这样能显著减小文件体积。以helloworld为例，可执行文件的size减少了近四成：</p>
<pre><code>$go build -ldflags="-s -w" -o helloworld-default-nosym main.go
$ls -l
-rwxr-xr-x 1 root root 2124504 11月  3 10:39 helloworld-default
-rwxr-xr-x 1 root root 1384600 11月  3 13:34 helloworld-default-nosym
</code></pre>
<ul>
<li>使用tinygo</li>
</ul>
<p><a href="https://github.com/tinygo-org/tinygo/">TinyGo</a>是一个Go语言的编译器，它专为资源受限的环境而设计，例如微控制器、WebAssembly和其他嵌入式设备。TinyGo的目标是提供一个轻量级的、能在小型设备上运行的Go运行时，同时尽可能支持Go语言的特性。tinygo的一大优点就是生成的二进制文件通常比标准Go编译器生成的文件小得多：</p>
<pre><code>$tinygo build -o helloworld-tinygo main.go
$ls -l
总用量 2728
-rwxr-xr-x  1 root root 2128909 11月  5 05:43 helloworld-default*
-rwxr-xr-x  1 root root  647600 11月  5 05:45 helloworld-tinygo*
</code></pre>
<p>我们看到：tinygo生成的可执行文件的size仅是原来的30%。</p>
<blockquote>
<p>注：虽然TinyGo在特定场景（如IoT和嵌入式开发）中非常有用，但在常规服务器环境中，由于生态系统兼容性、性能、调试支持等方面的限制，可能并不是最佳选择。对于需要高并发、复杂功能和良好调试支持的应用，标准Go仍然是更合适的选择。</p>
<p>注：这里使用的tinygo为0.34.0版本。</p>
</blockquote>
<h2>5. 未使用的符号是否会被编译到Go二进制文件中？</h2>
<p>到这里，相信读者心中也都会萦绕一些问题：到底哪些符号被编译到最终的Go二进制文件中了呢？未使用的符号是否会被编译到Go二进制文件中吗？在这一小节中，我们就来探索一下。</p>
<p>出于对Go的了解，我们已经知道无论是GOPATH时代，还是Go module时代，Go的编译单元始终是包(package)，一个包（无论包中包含多少个Go源文件）都会作为一个编译单元被编译为一个目标文件(.a)，然后Go链接器会将多个目标文件链接在一起生成可执行文件，因此如果一个包被依赖，那么它就会进入到Go二进制文件中，它内部的符号也会进入到Go二进制文件中。</p>
<p>那么问题来了！是否被依赖包中的所有符号都会被放到最终的可执行文件中呢？我们以最简单的helloworld-default为例，它依赖fmt包，并调用了fmt包的Println函数，我们看看Println这个符号是否会出现在最终的可执行文件中：</p>
<pre><code>$nm -a helloworld-default | grep "Println"
000000000048eba0 T fmt.(*pp).doPrintln
</code></pre>
<p>居然没有！我们初步怀疑是inline优化在作祟。接下来，关闭优化再来试试：</p>
<pre><code>$go build -o helloworld-default-noinline -gcflags='-l -N' main.go

$nm -a helloworld-default-noinline | grep "Println"
000000000048ec00 T fmt.(*pp).doPrintln
0000000000489ee0 T fmt.Println
</code></pre>
<p>看来的确如此！不过当使用”fmt.”去过滤helloworld-default-noinline的所有符号时，我们发现fmt包的一些常见的符号并未包含在其中，比如Printf、Fprintf、Scanf等。</p>
<p>这是因为Go编译器的一个重要特性：死码消除(dead code elimination)，即编译器会将未使用的代码和数据从最终的二进制文件中剔除。</p>
<p>我们再来继续探讨一个衍生问题：如果Go源码使用空导入方式导入了一个包，那么这个包是否会被编译到Go二进制文件中呢？其实道理是一样的，如果用到了里面的符号，就会存在，否则不会。</p>
<p>以空导入os/user为例，即便在CGO_ENABLED=0的情况下，因为没有使用os/user中的任何符号，在最终的二进制文件中也不会包含user包：</p>
<pre><code>$CGO_ENABLED=0 go build -o helloworld-with-os-user-noinline -gcflags='-l -N' main-with-os-user.go
[root@iZ2ze18rmx2avqb5xgb4omZ helloworld]# nm -a helloworld-with-os-user-noinline |grep user
0000000000551ac0 B runtime.userArenaState
</code></pre>
<p>但是如果是带有init函数的包，且init函数中调用了同包其他符号的情况呢？我们以expvar包为例看一下：</p>
<pre><code>// go-compilation/main-with-expvar.go

package main

import (
    _ "expvar"
    "fmt"
)

func main() {
    fmt.Println("hello, world")
}
</code></pre>
<p>编译并查看一下其中的符号：</p>
<pre><code>$go build -o helloworld-with-expvar-noinline -gcflags='-l -N' main-with-expvar.go
$nm -a helloworld-with-expvar-noinline|grep expvar
0000000000556480 T expvar.appendJSONQuote
00000000005562e0 T expvar.cmdline
00000000005561c0 T expvar.expvarHandler
00000000005568e0 T expvar.(*Func).String
0000000000555ee0 T expvar.Func.String
00000000005563a0 T expvar.init.0
00000000006e0560 D expvar..inittask
0000000000704550 d expvar..interfaceSwitch.0
... ...
</code></pre>
<p>除此之外，如果一个包即便没有init函数，但有需要初始化的全局变量，比如crypto包的hashes：</p>
<pre><code>// $GOROOT/src/crypto/crypto.go
var hashes = make([]func() hash.Hash, maxHash)
</code></pre>
<p>crypto包的相关如何也会进入最终的可执行文件中，大家自己动手不妨试试。下面是我得到的一些输出：</p>
<pre><code>$go build -o helloworld-with-crypto-noinline -gcflags='-l -N' main-with-crypto.go
$nm -a helloworld-with-crypto-noinline|grep crypto
00000000005517b0 B crypto.hashes
000000000048ee60 T crypto.init
0000000000547280 D crypto..inittask
</code></pre>
<p>有人会问：os/user包也有一些全局变量啊，为什么这些符号没有被包含在可执行文件中呢？比如：</p>
<pre><code>// $GOROOT/src/os/user/user.go
var (
    userImplemented      = true
    groupImplemented     = true
    groupListImplemented = true
)
</code></pre>
<p>这就要涉及Go包初始化的逻辑了。我们看到crypto包包含在可执行文件中的符号中有crypto.init和crypto..inittask这两个符号，显然这不是crypto包代码中的符号，而是Go编译器为crypto包自动生成的init函数和inittask结构。</p>
<p>Go编译器会为每个包生成一个init函数，即使包中没有显式定义init函数，同时<a href="https://go.dev/src/cmd/compile/internal/pkginit/init.go">每个包都会有一个inittask结构</a>，用于运行时的包初始化系统。当然这么说也不足够精确，如果一个包没有init函数、需要初始化的全局变量或其他需要运行时初始化的内容，则编译器不会为其生成init函数和inittask。比如上面的os/user包。</p>
<p>os/user包确实有上述全局变量的定义，但是这些变量是在编译期就可以确定值的常量布尔值，而且未被包外引用或在包内用于影响控制流。Go编译器足够智能，能够判断出这些初始化是”无副作用的”，不需要在运行时进行初始化。只有真正需要运行时初始化的包才会生成init和inittask。这也解释了为什么空导入os/user包时没有相关的init和inittask符号，而crypto、expvar包有的init.0和inittask符号。</p>
<h2>6. 如何快速判断Go项目是否依赖cgo？</h2>
<p>在使用开源Go项目时，我们经常会遇到项目文档中没有明确说明是否依赖Cgo的情况。这种情况下，如果我们需要在特定环境（比如CGO_ENABLED=0）下使用该项目，就需要事先判断项目是否依赖Cgo，有些时候还要快速地给出判断。</p>
<p>那究竟是否可以做到这种快速判断呢？我们先来看看一些常见的作法。</p>
<p>第一类作法是源码层面的静态分析。最直接的方式是检查源码中是否存在import “C”语句，这种引入方式是CGO使用的显著标志。</p>
<pre><code>// 在项目根目录中执行
$grep -rn 'import "C"' .
</code></pre>
<p>这个命令会递归搜索当前目录下所有文件，显示包含import “C”的行号和文件路径，帮助快速定位CGO的使用位置。</p>
<p>此外，CGO项目通常包含特殊的编译指令，这些指令以注释形式出现在源码中，比如前面见识过的#cgo CFLAGS、#cgo LDFLAGS等，通过对这些编译指令的检测，同样可以来判断项目是否依赖CGO。</p>
<p>不过第一类作法并不能查找出Go项目的依赖包是否依赖cgo。而找出直接依赖或间接依赖是否依赖cgo，我们需要工具帮忙，比如使用Go工具链提供的命令分析项目依赖：</p>
<pre><code>$go list -deps -f '{{.ImportPath}}: {{.CgoFiles}}' ./...  | grep -v '\[\]'
</code></pre>
<p>其中ImportPath是依赖包的导入路径，而CgoFiles则是依赖中包含import “C”的Go源文件。我们以go-sqlite3那个依赖cgo的示例来验证一下：</p>
<pre><code>// cd go-compilation/go-sqlite3

$go list -deps -f '{{.ImportPath}}: {{.CgoFiles}}' ./...  | grep -v '\[\]'
runtime/cgo: [cgo.go]
github.com/mattn/go-sqlite3: [backup.go callback.go error.go sqlite3.go sqlite3_context.go sqlite3_load_extension.go sqlite3_opt_serialize.go sqlite3_opt_userauth_omit.go sqlite3_other.go sqlite3_type.go]
</code></pre>
<p>用空导入os/user的示例再来看一下：</p>
<pre><code>$go list -deps -f '{{.ImportPath}}: {{.CgoFiles}}'  main-with-os-user.go | grep -v '\[\]'
runtime/cgo: [cgo.go]
os/user: [cgo_lookup_cgo.go getgrouplist_unix.go]
</code></pre>
<p>我们知道os/user有纯go和C版本两个实现，因此上述判断只能说“对了一半”，当我关闭CGO_ENABLED时，Go编译器不会使用基于cgo的C版实现。</p>
<p>那是否在禁用cgo的前提下对源码进行一次编译便能验证项目是否对cgo有依赖呢？这样做显然谈不上是一种“快速”的方法，那是否有效呢？我们来对上面的go-sqlite3项目做一个测试，我们在关闭CGO_ENABLED时，编译一下该示例：</p>
<pre><code>// cd go-compilation/go-sqlite3
$ CGO_ENABLED=0 go build demo
</code></pre>
<p>我们看到，Go编译器并未报错！似乎该项目不需要cgo!  但真的是这样吗？我们运行一下编译后的demo可执行文件：</p>
<pre><code>$ ./demo
2024/11/03 22:10:36 "Binary was compiled with 'CGO_ENABLED=0', go-sqlite3 requires cgo to work. This is a stub": CREATE TABLE IF NOT EXISTS user (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT);
</code></pre>
<p>我们看到成功编译出来的程序居然出现运行时错误，提示需要cgo！</p>
<p>到这里，没有一种方法可以快速、精确的给出项目是否依赖cgo的判断。也许判断Go项目是否依赖CGO并没有捷径，需要从源码分析、依赖检查和构建测试等多个维度进行。</p>
<h2>7. 小结</h2>
<p>在本文中，我们深入探讨了Go语言编译过程中的几个重要细节，尤其是在静态链接和动态链接的选择上。通过具体示例，我们了解到：</p>
<ul>
<li>
<p>默认链接方式：尽管CGO_ENABLED默认值为1，Go编译器在大多数情况下会采用静态链接，只有在依赖特定的C库或标准库包时，才会切换到动态链接。</p>
</li>
<li>
<p>动态链接的条件：我们讨论了几种情况下Go会默认生成动态链接的可执行文件，包括依赖使用C实现的标准库包、显式使用cgo调用外部C程序，以及使用依赖cgo的第三方包。</p>
</li>
<li>
<p>实现静态链接：对于需要动态链接的场景，我们也提供了将其转为静态链接的解决方案，包括关闭CGO、使用静态库，以及处理依赖cgo的外部包的静态链接问题。</p>
</li>
<li>
<p>二进制文件优化：我们还介绍了如何通过去除符号表和使用TinyGo等方法来优化生成的Go二进制文件的大小，以满足不同场景下的需求。</p>
</li>
<li>
<p>符号编译与死码消除：最后，我们探讨了未使用的符号是否会被编译到最终的二进制文件中，并解释了Go编译器的死码消除机制。</p>
</li>
</ul>
<p>通过这些细节探讨，我希望能够帮助大家更好地理解Go编译的复杂性，并在实际开发中做出更明智的选择，亦能在面对Go编译相关问题时，提供有效的解决方案。</p>
<p>本文涉及的源码可以在<a href="https://github.com/bigwhite/experiments/tree/master/go-compilation">这里</a>下载。</p>
<hr />
<p><a href="https://public.zsxq.com/groups/51284458844544">Gopher部落知识星球</a>在2024年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。同时，我们也会加强代码质量和最佳实践的分享，包括如何编写简洁、可读、可测试的Go代码。此外，我们还会加强星友之间的交流和互动。欢迎大家踊跃提问，分享心得，讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾。让我相聚在Gopher部落，享受coding的快乐! 欢迎大家踊跃加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/go-programming-from-beginner-to-master-qr.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /></p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻) &#8211; https://gopherdaily.tonybai.com</p>
<p>我的联系方式：</p>
<ul>
<li>微博(暂不可用)：https://weibo.com/bigwhite20xx</li>
<li>微博2：https://weibo.com/u/6484441286</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>Gopher Daily归档 &#8211; https://github.com/bigwhite/gopherdaily</li>
<li>Gopher Daily Feed订阅 &#8211; https://gopherdaily.tonybai.com/feed</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2024, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2024/11/11/some-details-about-go-compilation/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go是否支持增量构建？我来告诉你！</title>
		<link>https://tonybai.com/2022/03/21/go-native-support-incremental-build/</link>
		<comments>https://tonybai.com/2022/03/21/go-native-support-incremental-build/#comments</comments>
		<pubDate>Mon, 21 Mar 2022 13:40:24 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[build-cache]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[CMake]]></category>
		<category><![CDATA[C语言]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go-build]]></category>
		<category><![CDATA[go.mod]]></category>
		<category><![CDATA[GOCACHE]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Go语言第一课]]></category>
		<category><![CDATA[Go语言精进之路]]></category>
		<category><![CDATA[import]]></category>
		<category><![CDATA[incremental-build]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[JVM]]></category>
		<category><![CDATA[link]]></category>
		<category><![CDATA[linker]]></category>
		<category><![CDATA[Make]]></category>
		<category><![CDATA[Package]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[包]]></category>
		<category><![CDATA[增量构建]]></category>
		<category><![CDATA[目标文件]]></category>
		<category><![CDATA[链接]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=3472</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2022/03/21/go-native-support-incremental-build Go语言以编译速度快闻名于码农界。这缘于Go在设计之初就选择抛弃其祖辈C语言的头文件包含机制，选择了以包(package)作为基本编译单元。Go语言的这种以包为基本构建单元的构建模型使得依赖分析变得十分简单，避免了C语言那种通过头文件分析依赖的巨大开销。在我的《Go语言精进之路》一书中，我也给出了Go编译速度快的三点具体原因，包括： Go要求每个源文件在开头处显式地列出所有依赖的包导入，这样Go编译器不必读取和处理整个文件就可以确定其依赖的包列表； Go要求包之间不能存在循环依赖，这样一个包的依赖关系便形成了一张有向无环图。由于无环，包可以被单独编译，也可以并行编译； 已编译的Go包对应的目标文件(file_name.o或package_name.a)中不仅记录了该包本身的导出符号信息，还记录了其所依赖包的导出符号信息。这样，Go编译器在编译某包P时，针对P依赖的每个包导入(比如：导入包Q)，只需读取一个目标文件即可（比如：Q包编译成的目标文件，该目标文件中已经包含了Q包的依赖包的导出信息），而无需再读取其他文件中的信息了。 不过近期有读者问到：Go是否支持增量构建(incremental build)？这是一个好问题，书中并未提到这方面内容。但语言编译器编译速度再快，如果没有增量构建，构建大型代码工程的时间也不会短。那么Go是否支持增量构建呢？在这篇文章中，我就来告诉你答案。 1. 什么是增量构建？ 提到构建(build)，我们通常所指的是静态编译型语言，比如：C、Go、Java等。Python等动态语言不需要构建，直接用解释器run即可。每种静态编译型编程语言通常都有自己的编译单元，比如Go的编译单元为一个package，c/c++的编译单元是一个c/c++源文件，java则以class为编译单元等。静态语言的构建就是将编译单元的源码编译为对应的中间目标文件(.o/.a/.class)，然后将这些目标文件通过链接器链接在一起形成最终可执行文件的过程。不过Java除外，java在编译过程没有链接环节，jvm加载class文件时会有一个链接过程。 那么问题来了：每次项目构建，项目中的所有源文件都要被重新编译一遍而形成新的中间目标文件吗？如果我只改动了一个源文件中的几行代码，项目中的其他源文件也要跟着重新编译一遍么？我们显然不希望这样浪费算力、浪费开发者时间的事情发生！ 为了避免这样的事情发生，“增量构建”被提了出来。简单来说就是每次构建仅重新编译变动了的编译单元以及对这些变动的编译单元有依赖的编译单元的源码！ 上图展示了一个项目的编译单元的依赖关系。当开发人员修改了编译单元C的源码后，如果该项目支持增量编译，那么再次构建这个项目时，仅变动的编译单元C的源码以及直接依赖C的B、间接依赖C的A会被重新编译，而D、E两个编译单元不会被重新编译，其中间目标文件会被链接器重用。 对增量编译的支持，有两种策略：一种是编程语言的编译器自身就支持，比如Rust。另外一种则是语言自身编译器不支持，需要通过第三方项目构建管理工具协助实现，最典型的就是C/C++与Make/CMake的组合。 那么Go语言的编译器go compiler(gc)是否本身就支持增量编译呢？是否需要通过外部项目构建管理工具协助呢？我们继续往下看。 2. 通过示例看Go是否支持增量构建 Go语言提供了统一的go工具链，在这个工具链中用于构建的命令只有一个，那就是go build。下面我们就通过一系列实例来验证一下Go是否原生支持增量构建。 该示例的项目结构如下： demo1/ ├── go.mod ├── main.go ├── pkg1/ │   └── pkg1.go └── pkg2/ └── pkg2.go a) 首次构建 在这个项目中，顶层的module为demo1，main包依赖pkg1包与pkg2包。我们先通过go build命令对该项目做首次构建，我们通过命令行参数-x -v输出构建的详细日志，以便于我们分析： $go build -x -v ### 笔者注：创建临时目录用于此次构建 WORK=/var/folders/cz/sbj5kg2d3m3c6j650z0qfm800000gn/T/go-build1907281507 cd /Users/tonybai/test/go git status --porcelain cd [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/go-native-support-incremental-build-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2022/03/21/go-native-support-incremental-build">本文永久链接</a> &#8211; https://tonybai.com/2022/03/21/go-native-support-incremental-build</p>
<p>Go语言<strong>以编译速度快闻名于码农界</strong>。这缘于Go在设计之初就选择抛弃其祖辈C语言的头文件包含机制，选择了以包(package)作为基本编译单元。Go语言的这种以包为基本构建单元的构建模型使得依赖分析变得十分简单，避免了C语言那种通过头文件分析依赖的巨大开销。在我的<a href="https://mp.weixin.qq.com/s/h-lsWUbKRPnzFLINub5uYw">《Go语言精进之路》</a>一书中，我也给出了Go编译速度快的三点具体原因，包括：</p>
<ul>
<li>Go要求每个源文件在开头处显式地列出所有依赖的包导入，这样Go编译器不必读取和处理整个文件就可以确定其依赖的包列表；</li>
<li>Go要求包之间不能存在循环依赖，这样一个包的依赖关系便形成了一张有向无环图。由于无环，包可以被单独编译，也可以并行编译；</li>
<li>已编译的Go包对应的目标文件(file_name.o或package_name.a)中不仅记录了该包本身的导出符号信息，还记录了其所依赖包的导出符号信息。这样，Go编译器在编译某包P时，针对P依赖的每个包导入(比如：导入包Q)，只需读取一个目标文件即可（比如：Q包编译成的目标文件，该目标文件中已经包含了Q包的依赖包的导出信息），而无需再读取其他文件中的信息了。 </li>
</ul>
<p>不过近期有读者问到：<strong>Go是否支持增量构建(incremental build)</strong>？这是一个好问题，书中并未提到这方面内容。但语言编译器编译速度再快，如果没有增量构建，构建大型代码工程的时间也不会短。那么Go是否支持增量构建呢？在这篇文章中，我就来告诉你答案。</p>
<h3>1. 什么是增量构建？</h3>
<p>提到构建(build)，我们通常所指的是静态编译型语言，比如：C、Go、Java等。Python等动态语言不需要构建，直接用解释器run即可。每种静态编译型编程语言通常都有自己的编译单元，比如Go的编译单元为一个package，c/c++的编译单元是一个c/c++源文件，java则以class为编译单元等。<strong>静态语言的构建就是将编译单元的源码编译为对应的中间目标文件(.o/.a/.class)，然后将这些目标文件通过链接器链接在一起形成最终可执行文件的过程</strong>。不过Java除外，java在编译过程没有链接环节，jvm加载class文件时会有一个链接过程。</p>
<p>那么问题来了：每次项目构建，项目中的所有源文件都要被重新编译一遍而形成新的中间目标文件吗？如果我只改动了一个源文件中的几行代码，项目中的其他源文件也要跟着重新编译一遍么？我们显然不希望这样浪费算力、浪费开发者时间的事情发生！</p>
<p>为了避免这样的事情发生，“增量构建”被提了出来。简单来说就是<strong>每次构建仅重新编译变动了的编译单元以及对这些变动的编译单元有依赖的编译单元的源码</strong>！</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-native-support-incremental-build-2.png" alt="" /></p>
<p>上图展示了一个项目的编译单元的依赖关系。当开发人员修改了编译单元C的源码后，如果该项目支持增量编译，那么再次构建这个项目时，仅变动的编译单元C的源码以及直接依赖C的B、间接依赖C的A会被重新编译，而D、E两个编译单元不会被重新编译，其中间目标文件会被链接器重用。</p>
<p>对增量编译的支持，有两种策略：一种是编程语言的编译器自身就支持，比如Rust。另外一种则是语言自身编译器不支持，需要通过第三方项目构建管理工具协助实现，最典型的就是C/C++与Make/CMake的组合。</p>
<p>那么Go语言的编译器go compiler(gc)是否本身就支持增量编译呢？是否需要通过外部项目构建管理工具协助呢？我们继续往下看。</p>
<h3>2. 通过示例看Go是否支持增量构建</h3>
<p>Go语言提供了统一的go工具链，在这个工具链中用于构建的命令只有一个，那就是go build。下面我们就通过一系列实例来验证一下Go是否原生支持增量构建。</p>
<p>该示例的项目结构如下：</p>
<pre><code>demo1/
├── go.mod
├── main.go
├── pkg1/
│   └── pkg1.go
└── pkg2/
    └── pkg2.go
</code></pre>
<h4>a) 首次构建</h4>
<p>在这个项目中，顶层的module为demo1，main包依赖pkg1包与pkg2包。我们先通过go build命令对该项目做首次构建，我们通过命令行参数-x -v输出构建的详细日志，以便于我们分析：</p>
<pre><code>$go build -x -v 

### 笔者注：创建临时目录用于此次构建

WORK=/var/folders/cz/sbj5kg2d3m3c6j650z0qfm800000gn/T/go-build1907281507
cd /Users/tonybai/test/go
git status --porcelain
cd /Users/tonybai/test/go
git show -s --no-show-signature --format=%H:%ct
demo1/pkg2
demo1/pkg1
mkdir -p $WORK/b003/
mkdir -p $WORK/b002/
cat &gt;$WORK/b003/importcfg &lt;&lt; 'EOF' # internal
# import config
EOF

### 笔者注：编译demo1/pkg1和demo1/pkg2包

cd /Users/tonybai/test/go/incremental-build/demo1
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/compile -o $WORK/b003/_pkg_.a -trimpath "$WORK/b003=&gt;" -p demo1/pkg2 -lang=go1.18 -complete -buildid 4ixic55Fpug9OyS7vsew/4ixic55Fpug9OyS7vsew -goversion go1.18rc1 -c=4 -nolocalimports -importcfg $WORK/b003/importcfg -pack ./pkg2/pkg2.go
cat &gt;$WORK/b002/importcfg &lt;&lt; 'EOF' # internal
# import config
EOF
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/compile -o $WORK/b002/_pkg_.a -trimpath "$WORK/b002=&gt;" -p demo1/pkg1 -lang=go1.18 -complete -buildid jgyT36iBuu6-dYIzK5SD/jgyT36iBuu6-dYIzK5SD -goversion go1.18rc1 -c=4 -nolocalimports -importcfg $WORK/b002/importcfg -pack ./pkg1/pkg1.go
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b003/_pkg_.a # internal
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b002/_pkg_.a # internal

### 笔者注：将编译demo1/pkg1和demo1/pkg2包得到的目标文件缓存到gocache中

cp $WORK/b003/_pkg_.a /Users/tonybai/Library/Caches/go-build/fe/fef7890aa0cf3bb97e872d2b49cd834a5fad87cd5d8bf052dca65e4cecb541d2-d # internal
cp $WORK/b002/_pkg_.a /Users/tonybai/Library/Caches/go-build/24/24519941f74b316c8e83f2d2462b62370692c5f56b04ec3df97e3124ff8b4633-d # internal

runtime
mkdir -p $WORK/b004/
cat &gt;$WORK/b004/go_asm.h &lt;&lt; 'EOF' # internal
EOF
cd /Users/tonybai/.bin/go1.18rc1/src/runtime
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/asm -p runtime -trimpath "$WORK/b004=&gt;" -I $WORK/b004/ -I /Users/tonybai/.bin/go1.18rc1/pkg/include -D GOOS_darwin -D GOARCH_amd64 -compiling-runtime -D GOAMD64_v1 -gensymabis -o $WORK/b004/symabis ./asm.s ./asm_amd64.s ./duff_amd64.s ./memclr_amd64.s ./memmove_amd64.s ./preempt_amd64.s ./rt0_darwin_amd64.s ./sys_darwin_amd64.s
cat &gt;$WORK/b004/importcfg &lt;&lt; 'EOF' # internal
# import config
packagefile internal/abi=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/abi.a
packagefile internal/bytealg=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/bytealg.a
packagefile internal/cpu=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/cpu.a
packagefile internal/goarch=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goarch.a
packagefile internal/goexperiment=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goexperiment.a
packagefile internal/goos=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goos.a
packagefile runtime/internal/atomic=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/atomic.a
packagefile runtime/internal/math=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/math.a
packagefile runtime/internal/sys=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/sys.a
EOF

### 笔者注：由于笔者在执行build前使用go clean -cache将所有cache清空，因此这里go build会重新编译Go运行时库并缓存到gocache中

cd /Users/tonybai/test/go/incremental-build/demo1
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/compile -o $WORK/b004/_pkg_.a -trimpath "$WORK/b004=&gt;" -p runtime -std -+ -buildid cjuCOFTfsWmpOEnkAPsP/cjuCOFTfsWmpOEnkAPsP -goversion go1.18rc1 -symabis $WORK/b004/symabis -c=4 -nolocalimports -importcfg $WORK/b004/importcfg -pack -asmhdr $WORK/b004/go_asm.h /Users/tonybai/.bin/go1.18rc1/src/runtime/alg.go /Users/tonybai/.bin/go1.18rc1/src/runtime/asan0.go /Users/tonybai/.bin/go1.18rc1/src/runtime/atomic_pointer.go /Users/tonybai/.bin/go1.18rc1/src/runtime/cgo.go /Users/tonybai/.bin/go1.18rc1/src/runtime/cgocall.go /Users/tonybai/.bin/go1.18rc1/src/runtime/cgocallback.go /Users/tonybai/.bin/go1.18rc1/src/runtime/cgocheck.go /Users/tonybai/.bin/go1.18rc1/src/runtime/chan.go /Users/tonybai/.bin/go1.18rc1/src/runtime/checkptr.go /Users/tonybai/.bin/go1.18rc1/src/runtime/compiler.go /Users/tonybai/.bin/go1.18rc1/src/runtime/complex.go /Users/tonybai/.bin/go1.18rc1/src/runtime/cpuflags.go /Users/tonybai/.bin/go1.18rc1/src/runtime/cpuflags_amd64.go /Users/tonybai/.bin/go1.18rc1/src/runtime/cpuprof.go /Users/tonybai/.bin/go1.18rc1/src/runtime/cputicks.go /Users/tonybai/.bin/go1.18rc1/src/runtime/debug.go /Users/tonybai/.bin/go1.18rc1/src/runtime/debugcall.go /Users/tonybai/.bin/go1.18rc1/src/runtime/debuglog.go /Users/tonybai/.bin/go1.18rc1/src/runtime/debuglog_off.go /Users/tonybai/.bin/go1.18rc1/src/runtime/defs_darwin_amd64.go /Users/tonybai/.bin/go1.18rc1/src/runtime/env_posix.go /Users/tonybai/.bin/go1.18rc1/src/runtime/error.go /Users/tonybai/.bin/go1.18rc1/src/runtime/extern.go /Users/tonybai/.bin/go1.18rc1/src/runtime/fastlog2.go /Users/tonybai/.bin/go1.18rc1/src/runtime/fastlog2table.go /Users/tonybai/.bin/go1.18rc1/src/runtime/float.go /Users/tonybai/.bin/go1.18rc1/src/runtime/hash64.go /Users/tonybai/.bin/go1.18rc1/src/runtime/heapdump.go /Users/tonybai/.bin/go1.18rc1/src/runtime/histogram.go /Users/tonybai/.bin/go1.18rc1/src/runtime/iface.go /Users/tonybai/.bin/go1.18rc1/src/runtime/lfstack.go /Users/tonybai/.bin/go1.18rc1/src/runtime/lfstack_64bit.go /Users/tonybai/.bin/go1.18rc1/src/runtime/lock_sema.go /Users/tonybai/.bin/go1.18rc1/src/runtime/lockrank.go /Users/tonybai/.bin/go1.18rc1/src/runtime/lockrank_off.go /Users/tonybai/.bin/go1.18rc1/src/runtime/malloc.go /Users/tonybai/.bin/go1.18rc1/src/runtime/map.go /Users/tonybai/.bin/go1.18rc1/src/runtime/map_fast32.go /Users/tonybai/.bin/go1.18rc1/src/runtime/map_fast64.go /Users/tonybai/.bin/go1.18rc1/src/runtime/map_faststr.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mbarrier.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mbitmap.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mcache.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mcentral.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mcheckmark.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mem_darwin.go /Users/tonybai/.bin/go1.18rc1/src/runtime/metrics.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mfinal.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mfixalloc.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mgc.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mgcmark.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mgcpacer.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mgcscavenge.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mgcstack.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mgcsweep.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mgcwork.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mheap.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mpagealloc.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mpagealloc_64bit.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mpagecache.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mpallocbits.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mprof.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mranges.go /Users/tonybai/.bin/go1.18rc1/src/runtime/msan0.go /Users/tonybai/.bin/go1.18rc1/src/runtime/msize.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mspanset.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mstats.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mwbbuf.go /Users/tonybai/.bin/go1.18rc1/src/runtime/nbpipe_pipe.go /Users/tonybai/.bin/go1.18rc1/src/runtime/netpoll.go /Users/tonybai/.bin/go1.18rc1/src/runtime/netpoll_kqueue.go /Users/tonybai/.bin/go1.18rc1/src/runtime/os_darwin.go /Users/tonybai/.bin/go1.18rc1/src/runtime/os_nonopenbsd.go /Users/tonybai/.bin/go1.18rc1/src/runtime/panic.go /Users/tonybai/.bin/go1.18rc1/src/runtime/plugin.go /Users/tonybai/.bin/go1.18rc1/src/runtime/preempt.go /Users/tonybai/.bin/go1.18rc1/src/runtime/preempt_nonwindows.go /Users/tonybai/.bin/go1.18rc1/src/runtime/print.go /Users/tonybai/.bin/go1.18rc1/src/runtime/proc.go /Users/tonybai/.bin/go1.18rc1/src/runtime/profbuf.go /Users/tonybai/.bin/go1.18rc1/src/runtime/proflabel.go /Users/tonybai/.bin/go1.18rc1/src/runtime/race0.go /Users/tonybai/.bin/go1.18rc1/src/runtime/rdebug.go /Users/tonybai/.bin/go1.18rc1/src/runtime/relax_stub.go /Users/tonybai/.bin/go1.18rc1/src/runtime/runtime.go /Users/tonybai/.bin/go1.18rc1/src/runtime/runtime1.go /Users/tonybai/.bin/go1.18rc1/src/runtime/runtime2.go /Users/tonybai/.bin/go1.18rc1/src/runtime/rwmutex.go /Users/tonybai/.bin/go1.18rc1/src/runtime/select.go /Users/tonybai/.bin/go1.18rc1/src/runtime/sema.go /Users/tonybai/.bin/go1.18rc1/src/runtime/signal_amd64.go /Users/tonybai/.bin/go1.18rc1/src/runtime/signal_darwin.go /Users/tonybai/.bin/go1.18rc1/src/runtime/signal_darwin_amd64.go /Users/tonybai/.bin/go1.18rc1/src/runtime/signal_unix.go /Users/tonybai/.bin/go1.18rc1/src/runtime/sigqueue.go /Users/tonybai/.bin/go1.18rc1/src/runtime/sizeclasses.go /Users/tonybai/.bin/go1.18rc1/src/runtime/slice.go /Users/tonybai/.bin/go1.18rc1/src/runtime/softfloat64.go /Users/tonybai/.bin/go1.18rc1/src/runtime/stack.go /Users/tonybai/.bin/go1.18rc1/src/runtime/string.go /Users/tonybai/.bin/go1.18rc1/src/runtime/stubs.go /Users/tonybai/.bin/go1.18rc1/src/runtime/stubs_amd64.go /Users/tonybai/.bin/go1.18rc1/src/runtime/stubs_nonlinux.go /Users/tonybai/.bin/go1.18rc1/src/runtime/symtab.go /Users/tonybai/.bin/go1.18rc1/src/runtime/sys_darwin.go /Users/tonybai/.bin/go1.18rc1/src/runtime/sys_libc.go /Users/tonybai/.bin/go1.18rc1/src/runtime/sys_nonppc64x.go /Users/tonybai/.bin/go1.18rc1/src/runtime/sys_x86.go /Users/tonybai/.bin/go1.18rc1/src/runtime/time.go /Users/tonybai/.bin/go1.18rc1/src/runtime/time_nofake.go /Users/tonybai/.bin/go1.18rc1/src/runtime/timestub.go /Users/tonybai/.bin/go1.18rc1/src/runtime/tls_stub.go /Users/tonybai/.bin/go1.18rc1/src/runtime/trace.go /Users/tonybai/.bin/go1.18rc1/src/runtime/traceback.go /Users/tonybai/.bin/go1.18rc1/src/runtime/type.go /Users/tonybai/.bin/go1.18rc1/src/runtime/typekind.go /Users/tonybai/.bin/go1.18rc1/src/runtime/utf8.go /Users/tonybai/.bin/go1.18rc1/src/runtime/vdso_in_none.go /Users/tonybai/.bin/go1.18rc1/src/runtime/write_err.go
cd /Users/tonybai/.bin/go1.18rc1/src/runtime
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/asm -p runtime -trimpath "$WORK/b004=&gt;" -I $WORK/b004/ -I /Users/tonybai/.bin/go1.18rc1/pkg/include -D GOOS_darwin -D GOARCH_amd64 -compiling-runtime -D GOAMD64_v1 -o $WORK/b004/asm.o ./asm.s
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/asm -p runtime -trimpath "$WORK/b004=&gt;" -I $WORK/b004/ -I /Users/tonybai/.bin/go1.18rc1/pkg/include -D GOOS_darwin -D GOARCH_amd64 -compiling-runtime -D GOAMD64_v1 -o $WORK/b004/asm_amd64.o ./asm_amd64.s
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/asm -p runtime -trimpath "$WORK/b004=&gt;" -I $WORK/b004/ -I /Users/tonybai/.bin/go1.18rc1/pkg/include -D GOOS_darwin -D GOARCH_amd64 -compiling-runtime -D GOAMD64_v1 -o $WORK/b004/duff_amd64.o ./duff_amd64.s
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/asm -p runtime -trimpath "$WORK/b004=&gt;" -I $WORK/b004/ -I /Users/tonybai/.bin/go1.18rc1/pkg/include -D GOOS_darwin -D GOARCH_amd64 -compiling-runtime -D GOAMD64_v1 -o $WORK/b004/memclr_amd64.o ./memclr_amd64.s
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/asm -p runtime -trimpath "$WORK/b004=&gt;" -I $WORK/b004/ -I /Users/tonybai/.bin/go1.18rc1/pkg/include -D GOOS_darwin -D GOARCH_amd64 -compiling-runtime -D GOAMD64_v1 -o $WORK/b004/memmove_amd64.o ./memmove_amd64.s
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/asm -p runtime -trimpath "$WORK/b004=&gt;" -I $WORK/b004/ -I /Users/tonybai/.bin/go1.18rc1/pkg/include -D GOOS_darwin -D GOARCH_amd64 -compiling-runtime -D GOAMD64_v1 -o $WORK/b004/preempt_amd64.o ./preempt_amd64.s
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/asm -p runtime -trimpath "$WORK/b004=&gt;" -I $WORK/b004/ -I /Users/tonybai/.bin/go1.18rc1/pkg/include -D GOOS_darwin -D GOARCH_amd64 -compiling-runtime -D GOAMD64_v1 -o $WORK/b004/rt0_darwin_amd64.o ./rt0_darwin_amd64.s
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/asm -p runtime -trimpath "$WORK/b004=&gt;" -I $WORK/b004/ -I /Users/tonybai/.bin/go1.18rc1/pkg/include -D GOOS_darwin -D GOARCH_amd64 -compiling-runtime -D GOAMD64_v1 -o $WORK/b004/sys_darwin_amd64.o ./sys_darwin_amd64.s
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/pack r $WORK/b004/_pkg_.a $WORK/b004/asm.o $WORK/b004/asm_amd64.o $WORK/b004/duff_amd64.o $WORK/b004/memclr_amd64.o $WORK/b004/memmove_amd64.o $WORK/b004/preempt_amd64.o $WORK/b004/rt0_darwin_amd64.o $WORK/b004/sys_darwin_amd64.o # internal
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b004/_pkg_.a # internal
cp $WORK/b004/_pkg_.a /Users/tonybai/Library/Caches/go-build/0e/0e28018e12d646c32443e88953b839c7ba0be3198e6a61afc8a74c0b3e76696a-d # internal
demo1
mkdir -p $WORK/b001/
cat &gt;$WORK/b001/importcfg &lt;&lt; 'EOF' # internal
# import config
packagefile demo1/pkg1=$WORK/b002/_pkg_.a
packagefile demo1/pkg2=$WORK/b003/_pkg_.a
packagefile runtime=$WORK/b004/_pkg_.a
EOF

### 笔者注：编译main包并缓存

cd /Users/tonybai/test/go/incremental-build/demo1
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=&gt;" -p main -lang=go1.18 -complete -buildid ZhPqHmBh6WQ6HFsDI1Yh/ZhPqHmBh6WQ6HFsDI1Yh -goversion go1.18rc1 -c=4 -nolocalimports -importcfg $WORK/b001/importcfg -pack ./main.go
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b001/_pkg_.a # internal
cp $WORK/b001/_pkg_.a /Users/tonybai/Library/Caches/go-build/e8/e86257379cbdd59856f799594b63f3bb33ae89011955fee50e6fe90d3809ce5a-d # internal
cat &gt;$WORK/b001/importcfg.link &lt;&lt; 'EOF' # internal
packagefile demo1=$WORK/b001/_pkg_.a
packagefile demo1/pkg1=$WORK/b002/_pkg_.a
packagefile demo1/pkg2=$WORK/b003/_pkg_.a
packagefile runtime=$WORK/b004/_pkg_.a
packagefile internal/abi=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/abi.a
packagefile internal/bytealg=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/bytealg.a
packagefile internal/cpu=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/cpu.a
packagefile internal/goarch=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goarch.a
packagefile internal/goexperiment=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goexperiment.a
packagefile internal/goos=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goos.a
packagefile runtime/internal/atomic=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/atomic.a
packagefile runtime/internal/math=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/math.a
packagefile runtime/internal/sys=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/sys.a
modinfo "0w\xaf\f\x92t\b\x02A\xe1\xc1\a\xe6\xd6\x18\xe6path\tdemo1\nmod\tdemo1\t(devel)\t\nbuild\t-compiler=gc\nbuild\tCGO_ENABLED=1\nbuild\tCGO_CFLAGS=\nbuild\tCGO_CPPFLAGS=\nbuild\tCGO_CXXFLAGS=\nbuild\tCGO_LDFLAGS=\nbuild\tGOARCH=amd64\nbuild\tGOOS=darwin\nbuild\tGOAMD64=v1\nbuild\tvcs=git\nbuild\tvcs.revision=6534186d4b5b80c6c056237191fc703fa99cd19e\nbuild\tvcs.time=2022-03-12T13:52:57Z\nbuild\tvcs.modified=true\n\xf92C1\x86\x18 r\x00\x82B\x10A\x16\xd8\xf2"
EOF
mkdir -p $WORK/b001/exe/
cd .

### 笔者注：执行链接过程

/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=mzN3WRwHiNhsESy6r89L/ZhPqHmBh6WQ6HFsDI1Yh/Nvx0U2gM2zWzj7FTESXk/mzN3WRwHiNhsESy6r89L -extld=clang $WORK/b001/_pkg_.a
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b001/exe/a.out # internal

### 笔者注：将构建出来的可执行文件放到正确位置并改名
mv $WORK/b001/exe/a.out demo1
rm -r $WORK/b001/
</code></pre>
<h4>b) 删除可执行文件后，再次构建</h4>
<p>接下来我们删除之前构建出来的可执行文件demo1，然后再执行一次go build：</p>
<pre><code>$go build -x -v
WORK=/var/folders/cz/sbj5kg2d3m3c6j650z0qfm800000gn/T/go-build3889005616
cd /Users/tonybai/test/go
git status --porcelain
cd /Users/tonybai/test/go
git show -s --no-show-signature --format=%H:%ct
mkdir -p $WORK/b001/
cat &gt;$WORK/b001/importcfg.link &lt;&lt; 'EOF' # internal

### 笔者注：这次构建直接使用了上一次缓存的各个包的缓存结果 

packagefile demo1=/Users/tonybai/Library/Caches/go-build/e8/e86257379cbdd59856f799594b63f3bb33ae89011955fee50e6fe90d3809ce5a-d
packagefile demo1/pkg1=/Users/tonybai/Library/Caches/go-build/24/24519941f74b316c8e83f2d2462b62370692c5f56b04ec3df97e3124ff8b4633-d
packagefile demo1/pkg2=/Users/tonybai/Library/Caches/go-build/fe/fef7890aa0cf3bb97e872d2b49cd834a5fad87cd5d8bf052dca65e4cecb541d2-d
packagefile runtime=/Users/tonybai/Library/Caches/go-build/0e/0e28018e12d646c32443e88953b839c7ba0be3198e6a61afc8a74c0b3e76696a-d

packagefile internal/abi=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/abi.a
packagefile internal/bytealg=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/bytealg.a
packagefile internal/cpu=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/cpu.a
packagefile internal/goarch=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goarch.a
packagefile internal/goexperiment=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goexperiment.a
packagefile internal/goos=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goos.a
packagefile runtime/internal/atomic=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/atomic.a
packagefile runtime/internal/math=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/math.a
packagefile runtime/internal/sys=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/sys.a
modinfo "0w\xaf\f\x92t\b\x02A\xe1\xc1\a\xe6\xd6\x18\xe6path\tdemo1\nmod\tdemo1\t(devel)\t\nbuild\t-compiler=gc\nbuild\tCGO_ENABLED=1\nbuild\tCGO_CFLAGS=\nbuild\tCGO_CPPFLAGS=\nbuild\tCGO_CXXFLAGS=\nbuild\tCGO_LDFLAGS=\nbuild\tGOARCH=amd64\nbuild\tGOOS=darwin\nbuild\tGOAMD64=v1\nbuild\tvcs=git\nbuild\tvcs.revision=6534186d4b5b80c6c056237191fc703fa99cd19e\nbuild\tvcs.time=2022-03-12T13:52:57Z\nbuild\tvcs.modified=true\n\xf92C1\x86\x18 r\x00\x82B\x10A\x16\xd8\xf2"
EOF
mkdir -p $WORK/b001/exe/
cd .
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=mzN3WRwHiNhsESy6r89L/ZhPqHmBh6WQ6HFsDI1Yh/Nvx0U2gM2zWzj7FTESXk/mzN3WRwHiNhsESy6r89L -extld=clang /Users/tonybai/Library/Caches/go-build/e8/e86257379cbdd59856f799594b63f3bb33ae89011955fee50e6fe90d3809ce5a-d
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b001/exe/a.out # internal
mv $WORK/b001/exe/a.out demo1
rm -r $WORK/b001/
</code></pre>
<p>通过go build命令输出的日志我们看到：go build并没有重新编译各个包中的源文件，而是直接使用上一次构建缓存在cache中的demo1、demo1/pkg1和demo1/pkg2进行链接并输出最终可执行文件。初步判断，Go编译器是可以识别出项目中的源文件是否发生了改变并决定是否对其重新编译的。</p>
<h4>c) 新增pkg3</h4>
<p>我们为demo1新增pkg3，并在main.go中调用pkg3包中的函数，相当于建立了一个对pkg3的依赖，然后我们再来build一下该项目：</p>
<pre><code>$go build -x -v
WORK=/var/folders/cz/sbj5kg2d3m3c6j650z0qfm800000gn/T/go-build3890553968
cd /Users/tonybai/test/go
git status --porcelain
cd /Users/tonybai/test/go
git show -s --no-show-signature --format=%H:%ct
demo1/pkg3
mkdir -p $WORK/b004/
cat &gt;$WORK/b004/importcfg &lt;&lt; 'EOF' # internal
# import config
EOF

### 笔者注：构建pkg3

cd /Users/tonybai/test/go/incremental-build/demo1
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/compile -o $WORK/b004/_pkg_.a -trimpath "$WORK/b004=&gt;" -p demo1/pkg3 -lang=go1.18 -complete -buildid yVeHBkrjxeJ1Ib-jc5Fu/yVeHBkrjxeJ1Ib-jc5Fu -goversion go1.18rc1 -c=4 -nolocalimports -importcfg $WORK/b004/importcfg -pack ./pkg3/pkg3.go
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b004/_pkg_.a # internal
cp $WORK/b004/_pkg_.a /Users/tonybai/Library/Caches/go-build/2c/2c02674d62c50d4f2b8439c9314ef51b3e211d45d4114fa495fdd0e20c43440d-d # internal
demo1
mkdir -p $WORK/b001/
cat &gt;$WORK/b001/importcfg &lt;&lt; 'EOF' # internal
# import config

### 笔者注：直接重用demo1/pkg1和demo1/pkg2在cache中的目标文件

packagefile demo1/pkg1=/Users/tonybai/Library/Caches/go-build/24/24519941f74b316c8e83f2d2462b62370692c5f56b04ec3df97e3124ff8b4633-d
packagefile demo1/pkg2=/Users/tonybai/Library/Caches/go-build/fe/fef7890aa0cf3bb97e872d2b49cd834a5fad87cd5d8bf052dca65e4cecb541d2-d
packagefile demo1/pkg3=$WORK/b004/_pkg_.a
packagefile runtime=/Users/tonybai/Library/Caches/go-build/0e/0e28018e12d646c32443e88953b839c7ba0be3198e6a61afc8a74c0b3e76696a-d
EOF

### 笔者注：重新编译main.go

/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=&gt;" -p main -lang=go1.18 -complete -buildid Jii_iiylmm9d82X_Mzem/Jii_iiylmm9d82X_Mzem -goversion go1.18rc1 -c=4 -nolocalimports -importcfg $WORK/b001/importcfg -pack ./main.go
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b001/_pkg_.a # internal
cp $WORK/b001/_pkg_.a /Users/tonybai/Library/Caches/go-build/52/52b5b7e233ac17201702c26f1da97c5a23e42e68f74040d576905323a016f66e-d # internal
cat &gt;$WORK/b001/importcfg.link &lt;&lt; 'EOF' # internal
packagefile demo1=$WORK/b001/_pkg_.a
packagefile demo1/pkg1=/Users/tonybai/Library/Caches/go-build/24/24519941f74b316c8e83f2d2462b62370692c5f56b04ec3df97e3124ff8b4633-d
packagefile demo1/pkg2=/Users/tonybai/Library/Caches/go-build/fe/fef7890aa0cf3bb97e872d2b49cd834a5fad87cd5d8bf052dca65e4cecb541d2-d
packagefile demo1/pkg3=$WORK/b004/_pkg_.a
packagefile runtime=/Users/tonybai/Library/Caches/go-build/0e/0e28018e12d646c32443e88953b839c7ba0be3198e6a61afc8a74c0b3e76696a-d
packagefile internal/abi=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/abi.a
packagefile internal/bytealg=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/bytealg.a
packagefile internal/cpu=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/cpu.a
packagefile internal/goarch=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goarch.a
packagefile internal/goexperiment=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goexperiment.a
packagefile internal/goos=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goos.a
packagefile runtime/internal/atomic=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/atomic.a
packagefile runtime/internal/math=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/math.a
packagefile runtime/internal/sys=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/sys.a
modinfo "0w\xaf\f\x92t\b\x02A\xe1\xc1\a\xe6\xd6\x18\xe6path\tdemo1\nmod\tdemo1\t(devel)\t\nbuild\t-compiler=gc\nbuild\tCGO_ENABLED=1\nbuild\tCGO_CFLAGS=\nbuild\tCGO_CPPFLAGS=\nbuild\tCGO_CXXFLAGS=\nbuild\tCGO_LDFLAGS=\nbuild\tGOARCH=amd64\nbuild\tGOOS=darwin\nbuild\tGOAMD64=v1\nbuild\tvcs=git\nbuild\tvcs.revision=6534186d4b5b80c6c056237191fc703fa99cd19e\nbuild\tvcs.time=2022-03-12T13:52:57Z\nbuild\tvcs.modified=true\n\xf92C1\x86\x18 r\x00\x82B\x10A\x16\xd8\xf2"
EOF
mkdir -p $WORK/b001/exe/
cd .
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=GM4wTB4eDZmuIuIaQgup/Jii_iiylmm9d82X_Mzem/5aVh7LKgEkk3c4g5_WBq/GM4wTB4eDZmuIuIaQgup -extld=clang $WORK/b001/_pkg_.a
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b001/exe/a.out # internal
mv $WORK/b001/exe/a.out demo1
rm -r $WORK/b001/
</code></pre>
<p>我们看到Go只是编译了新增的pkg3以及依赖pkg3的main.go，pkg1和pkg2包并未被重新编译，而是直接使用了缓存在gocache中的中间目标文件。</p>
<h4>d) 重新编译单个变更的源文件还是重新编译整个包？</h4>
<p>如果一个go package包含多个源文件，当某一个源文件发生内容变化时，go编译器是只会编译该源文件还是整个包呢？我们来验证一下。</p>
<p>我们为pkg3添加另外一个源文件pkg3_1.go，然后做一次构建。之后再修改pkg3_1.go，再做构建：</p>
<pre><code>$go build -x -v
WORK=/var/folders/cz/sbj5kg2d3m3c6j650z0qfm800000gn/T/go-build213842995
cd /Users/tonybai/test/go
git status --porcelain
cd /Users/tonybai/test/go
git show -s --no-show-signature --format=%H:%ct
demo1/pkg3
mkdir -p $WORK/b004/
cat &gt;$WORK/b004/importcfg &lt;&lt; 'EOF' # internal
# import config
EOF
cd /Users/tonybai/test/go/incremental-build/demo1

### 笔者注：编译pkg3包

/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/compile -o $WORK/b004/_pkg_.a -trimpath "$WORK/b004=&gt;" -p demo1/pkg3 -lang=go1.18 -complete -buildid pX9UOIUBAZfMKmMgHv3q/pX9UOIUBAZfMKmMgHv3q -goversion go1.18rc1 -c=4 -nolocalimports -importcfg $WORK/b004/importcfg -pack ./pkg3/pkg3.go ./pkg3/pkg3_1.go
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b004/_pkg_.a # internal
cp $WORK/b004/_pkg_.a /Users/tonybai/Library/Caches/go-build/0c/0c3ce444d214c6c2999ba01b01eb4888c7864947d88bfcf63a41db4ac44002c2-d # internal
demo1
mkdir -p $WORK/b001/
cat &gt;$WORK/b001/importcfg &lt;&lt; 'EOF' # internal
# import config
packagefile demo1/pkg1=/Users/tonybai/Library/Caches/go-build/24/24519941f74b316c8e83f2d2462b62370692c5f56b04ec3df97e3124ff8b4633-d
packagefile demo1/pkg2=/Users/tonybai/Library/Caches/go-build/fe/fef7890aa0cf3bb97e872d2b49cd834a5fad87cd5d8bf052dca65e4cecb541d2-d
packagefile demo1/pkg3=$WORK/b004/_pkg_.a
packagefile runtime=/Users/tonybai/Library/Caches/go-build/0e/0e28018e12d646c32443e88953b839c7ba0be3198e6a61afc8a74c0b3e76696a-d
EOF

### 笔者注：编译依赖pkg3包的main.go

/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=&gt;" -p main -lang=go1.18 -complete -buildid cQBq3r5n1_wurKrb8Xmq/cQBq3r5n1_wurKrb8Xmq -goversion go1.18rc1 -c=4 -nolocalimports -importcfg $WORK/b001/importcfg -pack ./main.go
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b001/_pkg_.a # internal
cp $WORK/b001/_pkg_.a /Users/tonybai/Library/Caches/go-build/e2/e2818b14455f4dd54caf5f731c7b3b6b8254a37a8912e73c33b327771069bde7-d # internal
cat &gt;$WORK/b001/importcfg.link &lt;&lt; 'EOF' # internal
packagefile demo1=$WORK/b001/_pkg_.a
packagefile demo1/pkg1=/Users/tonybai/Library/Caches/go-build/24/24519941f74b316c8e83f2d2462b62370692c5f56b04ec3df97e3124ff8b4633-d
packagefile demo1/pkg2=/Users/tonybai/Library/Caches/go-build/fe/fef7890aa0cf3bb97e872d2b49cd834a5fad87cd5d8bf052dca65e4cecb541d2-d
packagefile demo1/pkg3=$WORK/b004/_pkg_.a
packagefile runtime=/Users/tonybai/Library/Caches/go-build/0e/0e28018e12d646c32443e88953b839c7ba0be3198e6a61afc8a74c0b3e76696a-d
packagefile internal/abi=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/abi.a
packagefile internal/bytealg=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/bytealg.a
packagefile internal/cpu=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/cpu.a
packagefile internal/goarch=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goarch.a
packagefile internal/goexperiment=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goexperiment.a
packagefile internal/goos=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goos.a
packagefile runtime/internal/atomic=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/atomic.a
packagefile runtime/internal/math=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/math.a
packagefile runtime/internal/sys=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/sys.a
modinfo "0w\xaf\f\x92t\b\x02A\xe1\xc1\a\xe6\xd6\x18\xe6path\tdemo1\nmod\tdemo1\t(devel)\t\nbuild\t-compiler=gc\nbuild\tCGO_ENABLED=1\nbuild\tCGO_CFLAGS=\nbuild\tCGO_CPPFLAGS=\nbuild\tCGO_CXXFLAGS=\nbuild\tCGO_LDFLAGS=\nbuild\tGOARCH=amd64\nbuild\tGOOS=darwin\nbuild\tGOAMD64=v1\nbuild\tvcs=git\nbuild\tvcs.revision=6534186d4b5b80c6c056237191fc703fa99cd19e\nbuild\tvcs.time=2022-03-12T13:52:57Z\nbuild\tvcs.modified=true\n\xf92C1\x86\x18 r\x00\x82B\x10A\x16\xd8\xf2"
EOF
mkdir -p $WORK/b001/exe/
cd .
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=qvFsK1K1jm8CeANRL3a2/cQBq3r5n1_wurKrb8Xmq/BB3nEx0b9edm7IM0XGAQ/qvFsK1K1jm8CeANRL3a2 -extld=clang $WORK/b001/_pkg_.a
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b001/exe/a.out # internal
mv $WORK/b001/exe/a.out demo1
rm -r $WORK/b001/
</code></pre>
<p>我们看到：虽然只修改了pkg3包下面的源文件pkg3_1.go，但go build还是会将整个包的所有源文件都重新编译一次。依赖pkg3包的main.go也会被随之重新编译。就此可以证实，<strong>Go的增量编译是以Go包为基本单位的，而不是以单个源文件为单位的</strong>。这与go工具缓存在gocache中的中间目标文件(pkg.a)<strong>以包为单位的</strong>是一致的。</p>
<h4>e) 当间接依赖的包发生了变动</h4>
<p>前面的示例展示的都是直接依赖包发生变动后，增量构建涵盖的编译单元范畴。如果某个包的间接依赖包发生变化，该包是否会参与增量构建呢？答案是肯定的。我们继续用示例来证明一下。</p>
<p>我们为该demo1项目增加pkg4包，并使得pkg3依赖pkg4。这样就会出现main.go直接依赖pkg3包，间接依赖pkg4包的情况。我们在添加完pkg4包后，进行一次构建。之后修改pkg4包的部分内容，然后再执行构建，其输出日志如下：</p>
<pre><code>$go build -x -v
WORK=/var/folders/cz/sbj5kg2d3m3c6j650z0qfm800000gn/T/go-build2817187631
cd /Users/tonybai/test/go
git status --porcelain
cd /Users/tonybai/test/go
git show -s --no-show-signature --format=%H:%ct
demo1/pkg4
mkdir -p $WORK/b005/
cat &gt;$WORK/b005/importcfg &lt;&lt; 'EOF' # internal
# import config
EOF

### 笔者注：编译demo1/pkg4包

cd /Users/tonybai/test/go/incremental-build/demo1
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/compile -o $WORK/b005/_pkg_.a -trimpath "$WORK/b005=&gt;" -p demo1/pkg4 -lang=go1.18 -complete -buildid AIv0TfCgKL2o00SexGru/AIv0TfCgKL2o00SexGru -goversion go1.18rc1 -c=4 -nolocalimports -importcfg $WORK/b005/importcfg -pack ./pkg4/pkg4.go
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b005/_pkg_.a # internal
cp $WORK/b005/_pkg_.a /Users/tonybai/Library/Caches/go-build/7e/7e2b8f229f8ca2f1ee315438f61cad5421bb5af9ed155e88a460faca806f4f90-d # internal
demo1/pkg3
mkdir -p $WORK/b004/
cat &gt;$WORK/b004/importcfg &lt;&lt; 'EOF' # internal
# import config
packagefile demo1/pkg4=$WORK/b005/_pkg_.a
EOF

### 笔者注：编译直接依赖demo1/pkg4包的demo1/pkg3包

/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/compile -o $WORK/b004/_pkg_.a -trimpath "$WORK/b004=&gt;" -p demo1/pkg3 -lang=go1.18 -complete -buildid ObVmRzLu3J1liPWzEiXx/ObVmRzLu3J1liPWzEiXx -goversion go1.18rc1 -c=4 -nolocalimports -importcfg $WORK/b004/importcfg -pack ./pkg3/pkg3.go ./pkg3/pkg3_1.go
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b004/_pkg_.a # internal
cp $WORK/b004/_pkg_.a /Users/tonybai/Library/Caches/go-build/4f/4f69cad7558ecf297799e29353bc802415785847c195555c22151f52abe1d9d9-d # internal
demo1
mkdir -p $WORK/b001/
cat &gt;$WORK/b001/importcfg &lt;&lt; 'EOF' # internal
# import config
packagefile demo1/pkg1=/Users/tonybai/Library/Caches/go-build/24/24519941f74b316c8e83f2d2462b62370692c5f56b04ec3df97e3124ff8b4633-d
packagefile demo1/pkg2=/Users/tonybai/Library/Caches/go-build/fe/fef7890aa0cf3bb97e872d2b49cd834a5fad87cd5d8bf052dca65e4cecb541d2-d
packagefile demo1/pkg3=$WORK/b004/_pkg_.a
packagefile runtime=/Users/tonybai/Library/Caches/go-build/0e/0e28018e12d646c32443e88953b839c7ba0be3198e6a61afc8a74c0b3e76696a-d
EOF

### 笔者注：编译间接依赖demo1/pkg4包的main.go

/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=&gt;" -p main -lang=go1.18 -complete -buildid i543xzAqlwVlWgQBYhsS/i543xzAqlwVlWgQBYhsS -goversion go1.18rc1 -c=4 -nolocalimports -importcfg $WORK/b001/importcfg -pack ./main.go
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b001/_pkg_.a # internal
cp $WORK/b001/_pkg_.a /Users/tonybai/Library/Caches/go-build/67/67a5ff80f6dbbbe01fcf9efb4d6ff380cb27fd1723b06b209a17987f1c74f425-d # internal
cat &gt;$WORK/b001/importcfg.link &lt;&lt; 'EOF' # internal
packagefile demo1=$WORK/b001/_pkg_.a
packagefile demo1/pkg1=/Users/tonybai/Library/Caches/go-build/24/24519941f74b316c8e83f2d2462b62370692c5f56b04ec3df97e3124ff8b4633-d
packagefile demo1/pkg2=/Users/tonybai/Library/Caches/go-build/fe/fef7890aa0cf3bb97e872d2b49cd834a5fad87cd5d8bf052dca65e4cecb541d2-d
packagefile demo1/pkg3=$WORK/b004/_pkg_.a
packagefile runtime=/Users/tonybai/Library/Caches/go-build/0e/0e28018e12d646c32443e88953b839c7ba0be3198e6a61afc8a74c0b3e76696a-d
packagefile demo1/pkg4=$WORK/b005/_pkg_.a
packagefile internal/abi=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/abi.a
packagefile internal/bytealg=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/bytealg.a
packagefile internal/cpu=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/cpu.a
packagefile internal/goarch=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goarch.a
packagefile internal/goexperiment=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goexperiment.a
packagefile internal/goos=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goos.a
packagefile runtime/internal/atomic=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/atomic.a
packagefile runtime/internal/math=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/math.a
packagefile runtime/internal/sys=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/sys.a
modinfo "0w\xaf\f\x92t\b\x02A\xe1\xc1\a\xe6\xd6\x18\xe6path\tdemo1\nmod\tdemo1\t(devel)\t\nbuild\t-compiler=gc\nbuild\tCGO_ENABLED=1\nbuild\tCGO_CFLAGS=\nbuild\tCGO_CPPFLAGS=\nbuild\tCGO_CXXFLAGS=\nbuild\tCGO_LDFLAGS=\nbuild\tGOARCH=amd64\nbuild\tGOOS=darwin\nbuild\tGOAMD64=v1\nbuild\tvcs=git\nbuild\tvcs.revision=6534186d4b5b80c6c056237191fc703fa99cd19e\nbuild\tvcs.time=2022-03-12T13:52:57Z\nbuild\tvcs.modified=true\n\xf92C1\x86\x18 r\x00\x82B\x10A\x16\xd8\xf2"
EOF
mkdir -p $WORK/b001/exe/
cd .
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=5U4vzFHU8ahkEvKH4-CV/i543xzAqlwVlWgQBYhsS/GOFyatqByKmjWK1zLLiq/5U4vzFHU8ahkEvKH4-CV -extld=clang $WORK/b001/_pkg_.a
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b001/exe/a.out # internal
mv $WORK/b001/exe/a.out demo1
rm -r $WORK/b001/
</code></pre>
<p>我们看到：pkg4包修改后，无论是直接依赖pkg4的包，还是间接依赖pkg4的包都会在下次增量构建时被重新编译。</p>
<h3>3. 小结</h3>
<p>由上面的示例我们看到：Go编译器是原生支持增量构建的，无需第三方构建管理工具的辅助。Go的增量构建是建立在<a href="https://tonybai.com/2018/02/17/some-changes-in-go-1-10">Go 1.10引入的build cache机制</a>的基础上的。Go的增量构建以Go包为单位，当Go包中的任一源文件发生变化时，Go都会对其进行重新构建，并且会连带构建所有直接或间接依赖该包的Go包。</p>
<p>本文示例源码在<a href="https://github.com/bigwhite/experiments/tree/master/incremental-build/demo1">这里</a>可以下载。</p>
<hr />
<p><a href="https://mp.weixin.qq.com/s/jUqAL7hf2GmMun64BJufEA">“Gopher部落”知识星球</a>旨在打造一个精品Go学习和进阶社群！高品质首发Go技术文章，“三天”首发阅读权，每年两期Go语言发展现状分析，每天提前1小时阅读到新鲜的Gopher日报，网课、技术专栏、图书内容前瞻，六小时内必答保证等满足你关于Go语言生态的所有需求！2022年，Gopher部落全面改版，将持续分享Go语言与Go应用领域的知识、技巧与实践，并增加诸多互动形式。欢迎大家加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-k8s-practice-with-qr.jpg" alt="img{512x368}" /></p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/。smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。2020年4月8日，中国三大电信运营商联合发布《5G消息白皮书》，51短信平台也会全新升级到“51商用消息平台”，全面支持5G RCS消息。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻)归档仓库 &#8211; https://github.com/bigwhite/gopherdaily</p>
<p>我的联系方式：</p>
<ul>
<li>微博：https://weibo.com/bigwhite20xx</li>
<li>微信公众号：iamtonybai</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>“Gopher部落”知识星球：https://public.zsxq.com/groups/51284458844544</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2022, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2022/03/21/go-native-support-incremental-build/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go 1.16中值得关注的几个变化</title>
		<link>https://tonybai.com/2021/02/25/some-changes-in-go-1-16/</link>
		<comments>https://tonybai.com/2021/02/25/some-changes-in-go-1-16/#comments</comments>
		<pubDate>Thu, 25 Feb 2021 02:49:56 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[bitbucket]]></category>
		<category><![CDATA[Compiler]]></category>
		<category><![CDATA[embed]]></category>
		<category><![CDATA[generics]]></category>
		<category><![CDATA[Git]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go-get]]></category>
		<category><![CDATA[go.mod]]></category>
		<category><![CDATA[go.sum]]></category>
		<category><![CDATA[Go1]]></category>
		<category><![CDATA[go1.13]]></category>
		<category><![CDATA[go1.16]]></category>
		<category><![CDATA[go1兼容性]]></category>
		<category><![CDATA[GODEBUG]]></category>
		<category><![CDATA[godoc]]></category>
		<category><![CDATA[goinstall]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[gomodule]]></category>
		<category><![CDATA[GOPATH]]></category>
		<category><![CDATA[Gopher]]></category>
		<category><![CDATA[GOVCS]]></category>
		<category><![CDATA[Go泛型]]></category>
		<category><![CDATA[hg]]></category>
		<category><![CDATA[inittrace]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[io/fs]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[linker]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[M1]]></category>
		<category><![CDATA[macos]]></category>
		<category><![CDATA[MADV_DONTNEED]]></category>
		<category><![CDATA[retract]]></category>
		<category><![CDATA[RobertGriesemer]]></category>
		<category><![CDATA[runtime]]></category>
		<category><![CDATA[RussCox]]></category>
		<category><![CDATA[Subversion]]></category>
		<category><![CDATA[svn]]></category>
		<category><![CDATA[vfs]]></category>
		<category><![CDATA[可移植性]]></category>
		<category><![CDATA[性能]]></category>
		<category><![CDATA[接口]]></category>
		<category><![CDATA[文件嵌入]]></category>
		<category><![CDATA[社区]]></category>
		<category><![CDATA[类型参数]]></category>
		<category><![CDATA[编译器]]></category>
		<category><![CDATA[苹果]]></category>
		<category><![CDATA[链接器]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=3109</guid>
		<description><![CDATA[辛丑牛年初七开工大吉的日子(2021.2.18)，Go核心开发团队为中国Gopher们献上了大礼 &#8211; Go 1.16版本正式发布了！国内Gopher可以在Go中国官网上下载到Go 1.16在各个平台的安装包： 2020年双12，Go 1.16进入freeze状态，即不再接受新feature，仅fix bug、编写文档和接受安全更新等，那时我曾写过一篇名为《Go 1.16新功能特性不完全前瞻》的文章。当时Go 1.16的发布说明尚处于早期草稿阶段，要了解Go 1.16功能特性都有哪些变化，只能结合当时的release note以及从Go 1.16里程碑中的issue列表中挖掘。 如今Go 1.16版本正式发布了，和当时相比，Go 1.16又有哪些变化呢？在这篇文章中，我们就来一起详细分析一下Go 1.16中那些值得关注的重要变化！ 一. 语言规范 如果你是Go语言新手，想必你一定很期待一个大版本的发布会带来许多让人激动人心的语言特性。但是Go语言在这方面肯定会让你“失望”的。伴随着Go 1.0版本一起发布的Go1兼容性承诺给Go语言的规范加了一个“框框”，从Go 1.0到Go 1.15版本，Go语言对语言规范的变更屈指可数，因此资深Gopher在阅读Go版本的release notes时总是很自然的略过这一章节，因为这一章节通常都是如下面这样的描述： 这就是Go的设计哲学：简单！绝不轻易向语言中添加新语法元素增加语言的复杂性。除非是那些社区呼声很高并且是Go核心团队认可的。我们也可以将Go从1.0到Go 1.16这段时间称为“Go憋大招”的阶段，因为就在Go团队发布1.16版本之前不久，Go泛型提案正式被Go核心团队接受(Accepted)： 这意味着什么呢？这意味着在2022年2月份(Go 1.18)，Gopher们将迎来Go有史以来最大一次语言语法变更并且这种变更依然是符合Go1兼容性承诺的，这将避免Go社区出现Python3给Python社区带去的那种“割裂”。不过就像《“能力越大，责任越大” &#8211; Go语言之父详解将于Go 1.18发布的Go泛型》一文中Go语言之父Robert Griesemer所说的那样：泛型引入了抽象，但滥用抽象而没有解决实际问题将带来不必要的复杂性，请三思而后行! 离泛型的落地还有一年时间，就让我们耐心等待吧！ 二. Go对各平台/OS支持的变更 Go语言具有良好的可移植性，对各主流平台和OS的支持十分全面和及时，Go官博曾发布过一篇文章，简要列出了自Go1以来对各主流平台和OS的支持情况： Go1（2012年3月）支持原始系统(译注：上面提到的两种操作系统和三种架构)以及64位和32位x86上的FreeBSD、NetBSD和OpenBSD，以及32位x86上的Plan9。 Go 1.3（2014年6月）增加了对64位x86上Solaris的支持。 Go 1.4（2014年12月）增加了对32位ARM上Android和64位x86上Plan9的支持。 Go 1.5（2015年8月）增加了对64位ARM和64位PowerPC上的Linux以及32位和64位ARM上的iOS的支持。 Go 1.6（2016年2月）增加了对64位MIPS上的Linux，以及32位x86上的Android的支持。它还增加了32位ARM上的Linux官方二进制下载，主要用于RaspberryPi系统。 Go 1.7（2016年8月）增加了对的z系统（S390x）上Linux和32位x86上Plan9的支持。 Go 1.8（2017年2月）增加了对32位MIPS上Linux的支持，并且它增加了64位PowerPC和z系统上Linux的官方二进制下载。 Go 1.9（2017年8月）增加了对64位ARM上Linux的官方二进制下载。 Go 1.12（2018年2月）增加了对32位ARM上Windows10 IoT Core的支持，如RaspberryPi3。它还增加了对64位PowerPC上AIX的支持。 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/go-1.16-1.png" alt="img{512x368}" /></p>
<p>辛丑牛年初七开工大吉的日子(2021.2.18)，Go核心开发团队为中国Gopher们献上了大礼 &#8211; <a href="https://mp.weixin.qq.com/s/7tYi-61teL0kBmWz7q2SGw">Go 1.16版本正式发布了</a>！国内Gopher可以在<a href="https://golang.google.cn/dl/">Go中国官网上</a>下载到Go 1.16在各个平台的安装包：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-1.16-2.png" alt="img{512x368}" /></p>
<p>2020年双12，Go 1.16进入freeze状态，即不再接受新feature，仅fix bug、编写文档和接受安全更新等，那时我曾写过一篇名为<a href="https://mp.weixin.qq.com/s/JzAQ3r9lDBad8PO6iAerqw">《Go 1.16新功能特性不完全前瞻》</a>的文章。当时Go 1.16的<a href="https://tip.golang.org/doc/go1.16">发布说明</a>尚处于早期草稿阶段，要了解Go 1.16功能特性都有哪些变化，只能结合当时的release note以及从<a href="https://github.com/golang/go/issues?q=is%3Aissue+milestone%3AGo1.16+is%3Aclosed">Go 1.16里程碑</a>中的issue列表中挖掘。</p>
<p>如今Go 1.16版本正式发布了，和当时相比，Go 1.16又有哪些变化呢？在这篇文章中，我们就来一起详细分析一下Go 1.16中那些值得关注的重要变化！</p>
<h3>一. 语言规范</h3>
<p>如果你是Go语言新手，想必你一定很期待一个大版本的发布会带来许多让人激动人心的语言特性。但是Go语言在这方面肯定会让你“失望”的。伴随着Go 1.0版本一起发布的<a href="https://tip.golang.org/doc/go1compat">Go1兼容性承诺</a>给Go语言的规范加了一个“框框”，从Go 1.0到<a href="https://mp.weixin.qq.com/s/B5onfyP7BPYCh_rMSBtfcQ">Go 1.15</a>版本，Go语言对语言规范的变更屈指可数，因此资深Gopher在阅读Go版本的release notes时总是很自然的略过这一章节，因为这一章节通常都是如下面这样的描述：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-1.16-3.png" alt="img{512x368}" /></p>
<p>这就是<a href="https://www.imooc.com/read/87/article/2321">Go的设计哲学：简单</a>！绝不轻易向语言中添加新语法元素增加语言的复杂性。除非是那些社区呼声很高并且是Go核心团队认可的。我们也可以将Go从1.0到Go 1.16这段时间称为“Go憋大招”的阶段，因为就在Go团队发布1.16版本之前不久，<a href="https://mp.weixin.qq.com/s/Cnko3hrrcFKpsfdlj3yXdQ">Go泛型提案</a>正式被Go核心团队接受(Accepted)：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-1.16-4.png" alt="img{512x368}" /></p>
<p>这意味着什么呢？这意味着在2022年2月份(Go 1.18)，Gopher们将迎来Go有史以来最大一次语言语法变更并且<strong>这种变更依然是符合Go1兼容性承诺的</strong>，这将避免Go社区出现Python3给Python社区带去的那种“割裂”。不过就像<a href="https://mp.weixin.qq.com/s/SMT40557JgQ9FjUkswznlA">《“能力越大，责任越大” &#8211; Go语言之父详解将于Go 1.18发布的Go泛型》</a>一文中Go语言之父<a href="https://github.com/griesemer">Robert Griesemer</a>所说的那样：<strong>泛型引入了抽象，但滥用抽象而没有解决实际问题将带来不必要的复杂性，请三思而后行</strong>! 离泛型的落地还有一年时间，就让我们耐心等待吧！</p>
<h3>二. Go对各平台/OS支持的变更</h3>
<p><strong>Go语言具有良好的<a href="https://tonybai.com/2017/06/27/an-intro-about-go-portability">可移植性</a>，对各主流平台和OS的支持十分全面和及时</strong>，Go官博曾<a href="https://mp.weixin.qq.com/s/FQWMwZnT8xmCGe1Ing_luQ">发布过一篇文章</a>，简要列出了自Go1以来对各主流平台和OS的支持情况：</p>
<ul>
<li>Go1（2012年3月）支持原始系统(译注：上面提到的两种操作系统和三种架构)以及64位和32位x86上的FreeBSD、NetBSD和OpenBSD，以及32位x86上的Plan9。</li>
<li>Go 1.3（2014年6月）增加了对64位x86上Solaris的支持。</li>
<li><a href="https://tonybai.com/2014/11/04/some-changes-in-go-1-4/">Go 1.4</a>（2014年12月）增加了对32位ARM上Android和64位x86上Plan9的支持。</li>
<li><a href="https://tonybai.com/2015/07/10/some-changes-in-go-1-5/">Go 1.5</a>（2015年8月）增加了对64位ARM和64位PowerPC上的Linux以及32位和64位ARM上的iOS的支持。</li>
<li><a href="https://tonybai.com/2016/02/21/some-changes-in-go-1-6/">Go 1.6</a>（2016年2月）增加了对64位MIPS上的Linux，以及32位x86上的Android的支持。它还增加了32位ARM上的Linux官方二进制下载，主要用于RaspberryPi系统。</li>
<li><a href="https://tonybai.com/2016/06/21/some-changes-in-go-1-7/">Go 1.7</a>（2016年8月）增加了对的z系统（S390x）上Linux和32位x86上Plan9的支持。</li>
<li><a href="https://tonybai.com/2017/02/03/some-changes-in-go-1-8/">Go 1.8</a>（2017年2月）增加了对32位MIPS上Linux的支持，并且它增加了64位PowerPC和z系统上Linux的官方二进制下载。</li>
<li><a href="https://tonybai.com/2017/07/14/some-changes-in-go-1-9/">Go 1.9</a>（2017年8月）增加了对64位ARM上Linux的官方二进制下载。</li>
<li><a href="https://tonybai.com/2019/03/02/some-changes-in-go-1-12">Go 1.12</a>（2018年2月）增加了对32位ARM上Windows10 IoT Core的支持，如RaspberryPi3。它还增加了对64位PowerPC上AIX的支持。</li>
<li><a href="https://tonybai.com/2020/03/08/some-changes-in-go-1-14">Go 1.14</a>（2019年2月）增加了对64位RISC-V上Linux的支持。</li>
</ul>
<p><a href="https://tonybai.com/2016/06/21/some-changes-in-go-1-7/">Go 1.7版本</a>中新增的<strong>go tool dist list</strong>命令还可以帮助我们快速了解各个版本究竟支持哪些平台以及OS的组合。下面是Go 1.16版本该命令的输出：</p>
<pre><code>$go tool dist list
aix/ppc64
android/386
android/amd64
android/arm
android/arm64
darwin/amd64
darwin/arm64
dragonfly/amd64
freebsd/386
freebsd/amd64
freebsd/arm
freebsd/arm64
illumos/amd64
ios/amd64
ios/arm64
js/wasm
linux/386
linux/amd64
linux/arm
linux/arm64
linux/mips
linux/mips64
linux/mips64le
linux/mipsle
linux/ppc64
linux/ppc64le
linux/riscv64
linux/s390x
netbsd/386
netbsd/amd64
netbsd/arm
netbsd/arm64
openbsd/386
openbsd/amd64
openbsd/arm
openbsd/arm64
openbsd/mips64
plan9/386
plan9/amd64
plan9/arm
solaris/amd64
windows/386
windows/amd64
windows/arm
</code></pre>
<p>通常我不太会过多关注每次Go版本发布时关于可移植性方面的内容，这次将可移植性单独作为章节主要是因为Go 1.16发布之前的<strong>Apple M1芯片事件</strong>！</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-1.16-5.jpg" alt="img{512x368}" /></p>
<p>苹果公司再次放弃Intel x86芯片而改用自造的基于Arm64的M1芯片引发业界激烈争论。但现实是搭载Arm64 M1芯片的苹果笔记本已经大量上市，对于编程语言开发团队来说，能做的只有尽快支持这一平台。因此，Go团队给出了在Go 1.16版本中增加对Mac M1的原生支持。</p>
<p>在Go 1.16版本之前，Go也支持darwin/arm64的组合，但那更多是为了构建在iOS上运行的Go应用(利用<a href="https://github.com/golang/mobile">gomobile</a>)。</p>
<p>Go 1.16做了进一步的细分：将darwin/arm64组合改为apple M1专用；而构建在iOS上运行的Go应用则使用ios/arm64。同时，Go 1.16还增加了ios/amd64组合用于支持在MacOS(amd64)上运行的<a href="https://tip.golang.org/misc/ios/README">iOS模拟器中运行Go应用</a>。</p>
<p>另外还值得一提的是在OpenBSD上，Go应用的系统调用需要通过libc发起，而不能再绕过libc而直接使用汇编指令了，这是出于对未来OpenBSD的一些兼容性要求考虑才做出的决定。</p>
<h3>三. Go module-aware模式成为默认！</h3>
<p>在泛型落地前，Go module依旧是这些年Go语言改进的重点(虽不是语言规范特性)。在Go 1.16版本中，Go module-aware模式成为了默认模式(另一种则是传统的gopath模式)。module-aware模式成为默认意味着什么呢？意味着GO111MODULE的值默认为on了。</p>
<p>自从Go 1.11加入go module，不同go版本在GO111MODULE为不同值的情况下开启的构建模式几经变化，上一次go module-aware模式的行为有较大变更还是在<a href="https://mp.weixin.qq.com/s/Txqvanb17LYQYgohNiUHig">Go 1.13版本</a>中。这里将Go 1.13版本之前、Go 1.13版本以及Go 1.16版本在GO111MODULE为不同值的情况下的行为做一下对比，这样我们可以更好的理解go 1.16中module-aware模式下的行为特性，下面我们就来做一下比对：</p>
<table>
<thead>
<tr>
<th>GO111MODULE</th>
<th>&lt; Go 1.13</th>
<th>Go 1.13</th>
<th>Go 1.16</th>
</tr>
</thead>
<tbody>
<tr>
<td>on</td>
<td>任何路径下都开启module-aware模式</td>
<td>任何路径下都开启module-aware模式</td>
<td>【默认值】：任何路径下都开启module-aware模式</td>
</tr>
<tr>
<td>auto</td>
<td>【默认值】：使用GOPATH mode还是module-aware mode，取决于要构建的源码目录所在位置以及是否包含go.mod文件。如果要构建的源码目录不在以GOPATH/src为根的目录体系下，且包含go.mod文件(两个条件缺一不可)，那么使用module-aware mode；否则使用传统的GOPATH mode。</td>
<td>【默认值】：只要当前目录或父目录下有go.mod文件时，就开启module-aware模式，无论源码目录是否在GOPATH外面</td>
<td>只有当前目录或父目录下有go.mod文件时，就开启module-aware模式，无论源码目录是否在GOPATH外面</td>
</tr>
<tr>
<td>off</td>
<td>gopath模式</td>
<td>gopath模式</td>
<td>gopath模式</td>
</tr>
</tbody>
</table>
<p>我们看到在Go 1.16模式下，依然可以回归到gopath模式。但Go核心团队已经决定拒绝<a href="https://github.com/golang/go/issues/37755">“继续保留GOPATH mode”的提案</a>，并计划在Go 1.17版本中彻底取消gopath mode，仅保留go module-aware mode：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-1.16-6.png" alt="img{512x368}" /></p>
<p>虽然目前仍有项目没有转换到go module下，但根据调查，大多数项目已经选择拥抱go module并完成了转换工作，因此笔者认为即便Go 1.17真的取消了GOPATH mode，对整个Go社区的影响也不会太大了。</p>
<p>Go 1.16中，go module机制还有其他几个变化，这里逐一来看一下：</p>
<h4>1. go build/run命令不再自动更新go.mod和go.sum了</h4>
<p>为了能更清晰看出Go 1.16与之前版本的差异，我们准备了一个小程序：</p>
<pre><code>// github.com/bigwhite/experiments/blob/master/go1.16-examples/go-modules/helloworld/go.mod
module github.com/bigwhite/helloworld

go 1.16

// github.com/bigwhite/experiments/blob/master/go1.16-examples/go-modules/helloworld/helloworld.go
package main

import "github.com/sirupsen/logrus"

func main() {
    logrus.Println("Hello, World")
}
</code></pre>
<p>我们使用<a href="https://mp.weixin.qq.com/s/B5onfyP7BPYCh_rMSBtfcQ">go 1.15版本</a>构建一下该程序：</p>
<pre><code>$go build
go: finding module for package github.com/sirupsen/logrus
go: downloading github.com/sirupsen/logrus v1.8.0
go: found github.com/sirupsen/logrus in github.com/sirupsen/logrus v1.8.0

$cat go.mod
module github.com/bigwhite/helloworld

go 1.16

require github.com/sirupsen/logrus v1.8.0

$cat go.sum
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU=
github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
</code></pre>
<p>在Go 1.15版本中，go build会自动分析源码中的依赖，如果go.mod中没有对该依赖的require，则会自动添加require，同时会将go.sum中将相关包(特定版本)的校验信息写入。</p>
<p>我们将上述helloworld恢复到初始状态，再用go 1.16来build一次：</p>
<pre><code>$go build
helloworld.go:3:8: no required module provides package github.com/sirupsen/logrus; to add it:
    go get github.com/sirupsen/logrus
</code></pre>
<p>我们看到go build没有成功，而是给出错误：go.mod中没有对logrus的require，并给出添加对logrus的require的方法(go get github.com/sirupsen/logrus)。</p>
<p>我们就按照go build给出的提示执行go get：</p>
<pre><code>$go get github.com/sirupsen/logrus
go: downloading github.com/magefile/mage v1.10.0
go get: added github.com/sirupsen/logrus v1.8.0

$cat go.mod
module github.com/bigwhite/helloworld

go 1.16

require github.com/sirupsen/logrus v1.8.0 // indirect

$cat go.sum
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g=
github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU=
github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

$go build
//ok
</code></pre>
<p>我们看到go build并不会向go 1.15及之前版本那样做出有“副作用”的动作：自动修改go.mod和go.sum，而是提示开发人员显式通过go get来添加缺少的包/module，即便是<a href="https://tonybai.com/2019/06/03/the-practice-of-upgrading-major-version-under-go-module/">依赖包major版本升级</a>亦是如此。</p>
<p>从自动更新go.mod，到通过提供-mod=readonly选项来避免自动更新go.mod，再到Go 1.16的禁止自动更新go.mod，笔者认为这个变化是Go不喜“隐式转型”的一种延续，即尽量不支持任何可能让开发者产生疑惑或surprise的隐式行为（就像隐式转型），取而代之的是要用一种显式的方式去完成(就像必须显式转型那样)。</p>
<p>我们也看到在go 1.16中，添加或更新go.mod中的依赖，只有显式使用go get。go mod tidy依旧会执行对go.mod的清理，即也可以修改go.mod。</p>
<h4>2. 推荐使用go install安装Go可执行文件</h4>
<p>在gopath mode下，go install基本“隐身”了，它能做的事情基本都被go get“越俎代庖”了。在go module时代初期，go install更是没有了地位。但Go团队现在想逐步恢复go install的角色：安装Go可执行文件！在Go 1.16中，当go install后面的包携带特定版本号时，go install将忽略当前go.mod中的依赖信息而直接编译安装可执行文件：</p>
<pre><code>// go install回将gopls v0.6.5安装到GOBIN下
$go install golang.org/x/tools/gopls@v0.6.5
</code></pre>
<p>并且后续，Go团队会让go get将专注于分析依赖，并获取go包/module，更新go.mod/go.sum，而<a href="https://github.com/golang/go/issues/43684">不再具有安装可执行Go程序的行为能力</a>，这样go get和go install就会各司其职，Gopher们也不会再被两者的重叠行为所迷惑了。现在如果不想go get编译安装，可使用go get -d。</p>
<h4>3. 作废module的特定版本</h4>
<p>在<a href="https://mp.weixin.qq.com/s/fg84g4OzSgoHDGbvVDbMKg">《如何作废一个已发布的Go module版本，我来告诉你！》</a>一文中，我曾详细探讨了Go引入module后如何作废一个已发布的go module版本。当时已经知晓Go 1.16会在go.mod中增加<a href="https://github.com/golang/mod/commit/c0d644d00ab849f4506f17a98a5740bf0feff020">retract指示符</a>，因此也给出了在Go 1.16下retract一个module版本的原理和例子(基于当时的go tip)。</p>
<p>Go 1.16正式版在工具的输出提示方面做了进一步的优化，让开发人员体验更为友好。我们还是以一个简单的例子来看看在Go 1.16中作废一个module版本的过程吧。</p>
<p>在我的bitbucket账户下有一个名为m2的Go module(https://bitbucket.org/bigwhite/m2/)，当前它的版本为v1.0.0：</p>
<pre><code>// bitbucket.org/bigwhite/m2
$cat go.mod
module bitbucket.org/bigwhite/m2

go 1.15

$cat m2.go
package m2

import "fmt"

func M2() {
    fmt.Println("This is m2.M2 - v1.0.0")
}
</code></pre>
<p>我们在本地建立一个m2的消费者：</p>
<pre><code>// github.com/bigwhite/experiments/blob/master/go1.16-examples/go-modules/retract

$cat go.mod
module github.com/bigwhite/retractdemo

go 1.16

$cat main.go
package main

import "bitbucket.org/bigwhite/m2"

func main() {
    m2.M2()
}
</code></pre>
<p>运行这个消费者：</p>
<pre><code>$go run main.go
main.go:3:8: no required module provides package bitbucket.org/bigwhite/m2; to add it:
    go get bitbucket.org/bigwhite/m2
</code></pre>
<p>由于上面提到的原因，go run不会隐式修改go.mod，因此我们需要手工go get m2：</p>
<pre><code>$go get bitbucket.org/bigwhite/m2
go: downloading bitbucket.org/bigwhite/m2 v1.0.0
go get: added bitbucket.org/bigwhite/m2 v1.0.0
</code></pre>
<p>再来运行消费者，我们将看到以下运行成功的结果：</p>
<pre><code>$go run main.go
This is m2.M2 - v1.0.0
</code></pre>
<p>现在m2的作者对m2打了小补丁，版本升级到了v1.0.1。这时消费者通过go list命令可以看到m2的最新版本(前提：go proxy server上已经cache了最新的v1.0.1)：</p>
<pre><code>$go list -m -u all
github.com/bigwhite/retractdemo
bitbucket.org/bigwhite/m2 v1.0.0 [v1.0.1]
</code></pre>
<p>消费者可以通过go get将对m2的依赖升级到最新的v1.0.1：</p>
<pre><code>$go get bitbucket.org/bigwhite/m2@v1.0.1

go get: upgraded bitbucket.org/bigwhite/m2 v1.0.0 =&gt; v1.0.1
$go run main.go
This is m2.M2 - v1.0.1
</code></pre>
<p>m2作者收到issue，有人指出v1.0.1版本有安全漏洞，m2作者确认了该漏洞，但此时v1.0.1版已经发布并被缓存到各大go proxy server上，已经无法撤回。m2作者便想到了Go 1.16中引入的retract指示符，于是它在m2的go.mod用retract指示符做了如下更新：</p>
<pre><code>$cat go.mod
module bitbucket.org/bigwhite/m2

// 存在安全漏洞
retract v1.0.1

go 1.15
</code></pre>
<p>并将此次更新作为v1.0.2发布了出去！</p>
<p>之后，当消费者使用go list查看m2是否有最新更新时，便会看到retract提示：(前提：go proxy server上已经cache了最新的v1.0.2)</p>
<pre><code>$go list -m -u all
github.com/bigwhite/retractdemo
bitbucket.org/bigwhite/m2 v1.0.1 (retracted) [v1.0.2]
</code></pre>
<p>执行go get会收到带有更详尽信息的retract提示和问题解决建议：</p>
<pre><code>$go get .
go: warning: bitbucket.org/bigwhite/m2@v1.0.1: retracted by module author: 存在安全漏洞
go: to switch to the latest unretracted version, run:
    go get bitbucket.org/bigwhite/m2@latest
</code></pre>
<p>于是消费者按照提示执行go get bitbucket.org/bigwhite/m2@latest：</p>
<pre><code>$go get bitbucket.org/bigwhite/m2@latest
go get: upgraded bitbucket.org/bigwhite/m2 v1.0.1 =&gt; v1.0.2

$cat go.mod
module github.com/bigwhite/retractdemo

go 1.16

require bitbucket.org/bigwhite/m2 v1.0.2

$go run main.go
This is m2.M2 - v1.0.2
</code></pre>
<p>到此，retract的使命终于完成了！</p>
<h4>4. 引入GOVCS环境变量，控制module源码获取所使用的版本控制工具</h4>
<p>出于安全考虑，Go 1.16引入GOVCS环境变量，用于在go命令直接从代码托管站点获取源码时对所使用的版本控制工具进行约束，如果是从go proxy server获取源码，那么GOVCS将不起作用，因为go工具与go proxy server之间使用的是<a href="https://tip.golang.org/ref/mod#goproxy-protocol">GOPROXY协议</a>。</p>
<p>GOVCS的默认值为public:git|hg,private:all，即对所有公共module允许采用git或hg获取源码，而对私有module则不限制版本控制工具的使用。</p>
<p>如果要允许使用所有工具，可像下面这样设置GOVCS：</p>
<pre><code>GOVCS=*:all
</code></pre>
<p>如果要禁止使用任何版本控制工具去直接获取源码（不通过go proxy），那么可以像下面这样设置GOVCS:</p>
<pre><code>GOVCS=*:off
</code></pre>
<h4>5. 有关go module的文档更新</h4>
<p>自打<a href="https://mp.weixin.qq.com/s/PVxdtvSXgNpiD65TUo-TCg">Go 1.14版本</a>宣布go module生产可用后，Go核心团队在说服和帮助Go社区全面拥抱go module的方面不可谓不努力。在文档方面亦是如此，最初有关go module的文档仅局限于go build命令相关以及<a href="https://github.com/golang/go/wiki/Modules">有关go module的wiki</a>。随着go module日益成熟，go.mod格式的日益稳定，Go团队在1.16版本中还将go module相关文档升级到go reference的层次，与go language ref等并列：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-1.16-7.png" alt="img{512x368}" /></p>
<p>我们看到有关go module的ref文档包括：</p>
<ul>
<li><a href="https://tip.golang.org/ref/mod">Go Modules Reference</a> https://tip.golang.org/ref/mod</li>
<li><a href="https://tip.golang.org/doc/modules/gomod-ref">go.mod file reference</a> https://tip.golang.org/doc/modules/gomod-ref</li>
</ul>
<p>官方还编写了<a href="https://tip.golang.org/doc/#developing-modules">详细的Go module日常开发时的使用方法</a>，包括：开发与发布module、module发布与版本管理工作流、升级major号等。</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-1.16-8.png" alt="img{512x368}" /></p>
<p><strong>建议每个gopher都要将这些文档仔细阅读一遍，以更为深入了解和使用go module</strong>。</p>
<h3>四. 编译器与运行时</h3>
<h4>1. runtime/metrics包</h4>
<p>在<a href="https://mp.weixin.qq.com/s/JzAQ3r9lDBad8PO6iAerqw">《Go 1.16新功能特性不完全前瞻》</a>一文中，我们提到过：Go 1.16 新增了runtime/metrics包，以替代runtime.ReadMemStats和debug.ReadGCStats输出runtime的各种度量数据，这个包更通用稳定，性能也更好。限于篇幅这里不展开，后续可能会以单独的文章讲解这个新包。</p>
<h4>2. GODEBUG环境变量支持跟踪包init函数的消耗</h4>
<p>GODEBUG=inittrace=1这个特性也保留在了Go 1.16正式版当中了。当GODEBUG环境变量包含inittrace=1时，Go运行时将会报告各个源代码文件中的init函数的执行时间和内存开辟消耗情况。我们用上面的helloworld示例(github.com/bigwhite/experiments/blob/master/go1.16-examples/go-modules/helloworld)来看看该特性的效果：</p>
<pre><code>$go build
$GODEBUG=inittrace=1 ./helloworld
init internal/bytealg @0.006 ms, 0 ms clock, 0 bytes, 0 allocs
init runtime @0.037 ms, 0.031 ms clock, 0 bytes, 0 allocs
init errors @0.29 ms, 0.005 ms clock, 0 bytes, 0 allocs
init math @0.31 ms, 0 ms clock, 0 bytes, 0 allocs
init strconv @0.33 ms, 0.002 ms clock, 32 bytes, 2 allocs
init sync @0.35 ms, 0.003 ms clock, 16 bytes, 1 allocs
init unicode @0.37 ms, 0.10 ms clock, 24568 bytes, 30 allocs
init reflect @0.49 ms, 0.002 ms clock, 0 bytes, 0 allocs
init io @0.51 ms, 0.003 ms clock, 144 bytes, 9 allocs
init internal/oserror @0.53 ms, 0 ms clock, 80 bytes, 5 allocs
init syscall @0.55 ms, 0.010 ms clock, 752 bytes, 2 allocs
init time @0.58 ms, 0.010 ms clock, 384 bytes, 8 allocs
init path @0.60 ms, 0 ms clock, 16 bytes, 1 allocs
init io/fs @0.62 ms, 0.002 ms clock, 16 bytes, 1 allocs
init internal/poll @0.63 ms, 0.001 ms clock, 64 bytes, 4 allocs
init os @0.65 ms, 0.089 ms clock, 4472 bytes, 20 allocs
init fmt @0.77 ms, 0.006 ms clock, 32 bytes, 2 allocs
init bytes @0.84 ms, 0.004 ms clock, 48 bytes, 3 allocs
init context @0.87 ms, 0 ms clock, 128 bytes, 4 allocs
init encoding/binary @0.89 ms, 0.002 ms clock, 16 bytes, 1 allocs
init encoding/base64 @0.90 ms, 0.015 ms clock, 1408 bytes, 4 allocs
init encoding/json @0.93 ms, 0.002 ms clock, 32 bytes, 2 allocs
init log @0.95 ms, 0 ms clock, 80 bytes, 1 allocs
init golang.org/x/sys/unix @0.96 ms, 0.002 ms clock, 48 bytes, 1 allocs
init bufio @0.98 ms, 0 ms clock, 176 bytes, 11 allocs
init github.com/sirupsen/logrus @0.99 ms, 0.009 ms clock, 312 bytes, 5 allocs
INFO[0000] Hello, World
</code></pre>
<p>以下面这行为例：</p>
<pre><code>init fmt @0.77 ms, 0.006 ms clock, 32 bytes, 2 allocs
</code></pre>
<ul>
<li>0.77ms表示的是自从程序启动后到fmt包init执行所过去的时间(以ms为单位)</li>
<li>0.006 ms clock表示fmt包init函数执行的时间(以ms为单位)</li>
<li>312 bytes表示fmt包init函数在heap上分配的内存大小；</li>
<li>5 allocs表示的是fmt包init函数在heap上执行内存分配操作的次数。</li>
</ul>
<h4>3. Go runtime默认使用MADV_DONTNEED</h4>
<p>Go 1.15版本时，我们可以通过GODEBUG=madvdontneed=1让Go runtime使用MADV_DONTNEED替代MADV_FREE达到更积极的将不用的内存释放给OS的效果(如果使用MADV_FREE，只有OS内存压力很大时，才会真正回收内存)，这将使得通过top查看到的常驻系统内存(RSS或RES)指标更实时也更真实反映当前Go进程对os内存的实际占用情况(仅使用linux)。</p>
<p>在Go 1.16版本中，Go runtime将MADV_DONTNEED作为默认值了，我们可以用一个小例子来对比一下这种变化：</p>
<pre><code>// github.com/bigwhite/experiments/blob/master/go1.16-examples/runtime/memalloc.go
package main

import "time"

func allocMem() []byte {
    b := make([]byte, 1024*1024*1) //1M
    return b
}

func main() {
    for i := 0; i &lt; 100000; i++ {
        _ = allocMem()
        time.Sleep(500 * time.Millisecond)
    }
}
</code></pre>
<p>我们在linux上使用go 1.16版本编译该程序，考虑到优化和inline的作用，我们在编译时关闭优化和内联：</p>
<pre><code>$go build -gcflags "-l -N" memalloc.go
</code></pre>
<p>接下来，我们分两次运行该程序，并使用top监控其RES指标值：</p>
<pre><code>$./memalloc
$ top -p 9273
  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
 9273 root      20   0  704264   5840    856 S  0.0  0.3   0:00.03 memalloc
 9273 root      20   0  704264   3728    856 S  0.0  0.2   0:00.05 memalloc
 ... ...

$GODEBUG=madvdontneed=0 ./memalloc
$ top -p 9415

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
 9415 root      20   0  704264   5624    856 S  0.0  0.3   0:00.03 memalloc
 9415 root      20   0  704264   5624    856 S  0.0  0.3   0:00.05 memalloc
</code></pre>
<p>我们看到默认运行的memalloc(开启MADV_DONTNEED)，RES很积极的变化，当上一次显示5840，下一秒内存就被归还给OS，RES变为3728。而关闭MADV_DONTNEED（GODEBUG=madvdontneed=0）的memalloc，OS就会很lazy的回收内存，RES一直显示5624这个值。</p>
<h4>4. Go链接器的进一步进行<a href="https://golang.org/s/better-linker">现代化改造</a></h4>
<p>新一代Go链接器的更新计划从Go 1.15版本开始，在Go 1.15版本链接器的性能、资源占用、最终二进制文件大小等方面都有了一定幅度的优化提升。Go 1.16版本延续了这一势头：相比于Go 1.15，官方宣称(在linux上)性能有20%-25%的提升，资源占用下降5%-15%。更为直观的是编译出的二进制文件的size，我实测了一下文件大小下降10%以上：</p>
<pre><code>-rwxr-xr-x   1 tonybai  staff    22M  2 21 23:03 my-large-app-demo*
-rwxr-xr-x   1 tonybai  staff    25M  2 21 23:02 my-large-app-demo-go1.15*
</code></pre>
<p>并且和Go 1.15的链接器优化仅针对amd64平台和基于ELF格式的OS不同，<strong>这次的链接器优化已经扩展到所有平台和os组合上</strong>。</p>
<h3>五. 标准库</h3>
<h4>1. io/fs包</h4>
<p>Go 1.16标准库新增io/fs包，并定义了一个fs.File接口用于表示一个只读文件树(tree of file)的抽象。之所以要<a href="https://github.com/golang/go/issues/41190">加入io/fs包并新增fs.File接口</a>源于对<a href="https://github.com/golang/go/issues/41191">嵌入静态资源文件(embed static asset)的实现需求</a>。虽说实现embed功能特性是直接原因，但io/fs的加入也不是“临时起意”，早在很多年前的godoc实现时，对一个抽象的文件系统接口的需求就已经被提了出来并给出了实现：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-1.16-9.png" alt="" /></p>
<p>最终这份实现以godoc工具的<a href="https://github.com/golang/tools/tree/master/godoc/vfs">vfs包</a>的形式一直长期存在着。虽然它的实现有些复杂，抽象程度不够，但却对<a href="https://github.com/golang/proposal/blob/master/design/draft-iofs.md">io/fs包的设计</a>有着重要的参考价值。同时也部分弥补了<a href="https://github.com/golang/go/issues/14106">Rob Pike老爷子当年没有将os.File设计为interface的遗憾</a>，<a href="https://github.com/golang/go/issues/5636">Ian Lance Taylor 2013年提出的增加VFS层的想法</a>也一并得以实现。</p>
<p>io/fs包的两个最重要的接口如下：</p>
<pre><code>// $GOROOT/src/io/fs/fs.go

// An FS provides access to a hierarchical file system.
//
// The FS interface is the minimum implementation required of the file system.
// A file system may implement additional interfaces,
// such as ReadFileFS, to provide additional or optimized functionality.
type FS interface {
        // Open opens the named file.
        //
        // When Open returns an error, it should be of type *PathError
        // with the Op field set to "open", the Path field set to name,
        // and the Err field describing the problem.
        //
        // Open should reject attempts to open names that do not satisfy
        // ValidPath(name), returning a *PathError with Err set to
        // ErrInvalid or ErrNotExist.
        Open(name string) (File, error)
}

// A File provides access to a single file.
// The File interface is the minimum implementation required of the file.
// A file may implement additional interfaces, such as
// ReadDirFile, ReaderAt, or Seeker, to provide additional or optimized functionality.
type File interface {
        Stat() (FileInfo, error)
        Read([]byte) (int, error)
        Close() error
}
</code></pre>
<p>FS接口代表虚拟文件系统的最小抽象，File接口则是虚拟文件的最小抽象，我们可以基于这两个接口进行扩展以及对接现有的一些实现。io/fs包也给出了一些扩展FS的“样例”：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-1.16-10.png" alt="" /></p>
<p>这两个接口的设计也是“Go秉持定义小接口惯例”的延续(更多关于这方面的内容，可以参考我的专栏文章<a href="https://www.imooc.com/read/87/article/2425">《定义小接口是Go惯例》</a>)。</p>
<p>io/fs包的加入也契合了Go社区对vfs的需求，在Go团队决定加入io/fs并提交实现后，社区做出了积极的反应，在github上我们能看到好多为各类对象提供针对io/fs.FS接口实现的项目：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-1.16-11.png" alt="" /></p>
<p>io/fs.FS和File接口在后续Go演进过程中会像io.Writer和io.Reader一样成为Gopher们在操作类文件树时最爱的接口。</p>
<h4>2. embed包</h4>
<p>在<a href="https://mp.weixin.qq.com/s/JzAQ3r9lDBad8PO6iAerqw">《Go 1.16新功能特性不完全前瞻》</a>一文中我们曾重点说了Go 1.16将支持在Go二进制文件中嵌入静态文件并给出了一个在webserver中嵌入文本文件的例子：</p>
<pre><code>// github.com/bigwhite/experiments/blob/master/go1.16-examples/stdlib/embed/webserver/hello.txt
hello, go 1.16

// github.com/bigwhite/experiments/blob/master/go1.16-examples/stdlib/embed/webserver/main.go
package main

import (
         _  "embed"
    "net/http"
)

//go:embed hello.txt
var s string

func main() {
    http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte(s))
    }))
    http.ListenAndServe(":8080", nil)
}
</code></pre>
<p>我们看到在这个例子，通过//go:embed hello.txt，我们可以轻易地将hello.txt的内容存储在包级变量s中，而s将作为每个http request的应答返回给客户端。</p>
<p><a href="https://github.com/golang/go/issues/41191">在Go二进制文件中嵌入静态资源文件</a>是Go核心团队对社区广泛需求的积极回应。在go 1.16以前，Go社区开源的类嵌入静态文件的项目不下十多个，在Russ Cox<a href="https://github.com/golang/proposal/blob/master/design/draft-embed.md">关于embed的设计草案</a>中，他就列了十多个：</p>
<ul>
<li>github.com/jteeuwen/go-bindata(主流实现)</li>
<li>github.com/alecthomas/gobundle</li>
<li>github.com/GeertJohan/go.rice</li>
<li>github.com/go-playground/statics</li>
<li>github.com/gobuffalo/packr</li>
<li>github.com/knadh/stuffbin</li>
<li>github.com/mjibson/esc</li>
<li>github.com/omeid/go-resources</li>
<li>github.com/phogolabs/parcello</li>
<li>github.com/pyros2097/go-embed</li>
<li>github.com/rakyll/statik</li>
<li>github.com/shurcooL/vfsgen</li>
<li>github.com/UnnoTed/fileb0x</li>
<li>github.com/wlbr/templify</li>
<li>perkeep.org/pkg/fileembed</li>
</ul>
<p>Go1.16原生支持嵌入并且给出一种开发者体验良好的实现方案，这对Go社区是一种极大的鼓励，也是Go团队重视社区声音的重要表现。</p>
<p>笔者认为embed机制是Go 1.16中玩法最多的一种机制，也是极具新玩法挖掘潜力的机制。在embed加入Go tip不久，很多Gopher就已经“脑洞大开”：</p>
<p>有通过embed嵌入版本号的：</p>
<pre><code>// github.com/bigwhite/experiments/blob/master/go1.16-examples/stdlib/embed/version/main.go
package main

import (
    _ "embed"
    "fmt"
    "strings"
)

var (
    Version string = strings.TrimSpace(version)
    //go:embed version.txt
    version string
)

func main() {
    fmt.Printf("Version %q\n", Version)
}

// github.com/bigwhite/experiments/blob/master/go1.16-examples/stdlib/embed/version/version.txt
v1.0.1
</code></pre>
<p>有通过embed打印自身源码的：</p>
<pre><code>// github.com/bigwhite/experiments/blob/master/go1.16-examples/stdlib/embed/printself/main.go
package main

import (
        _ "embed"
        "fmt"
)

//go:embed main.go
var src string

func main() {
        fmt.Print(src)
}
</code></pre>
<p>更是有<a href="https://blog.lawrencejones.dev/golang-embed/">将一个完整的、复杂的带有js支持的web站点直接嵌入到go二进制文件中的示例</a>，鉴于篇幅，这里就不一一列举了。</p>
<p>Go擅长于Web服务，而embed机制的引入粗略来看，可以大大简化web服务中资源文件的部署，估计这也是之前社区青睐各种静态资源文件嵌入项目的原因。embed估计也会成为Go 1.16中最被gopher们喜爱的功能特性。</p>
<p>不过embed机制的实现目前有如下一些局限：</p>
<ul>
<li>仅支持在包级变量前使用//go:embed指示符，还不支持在函数/方法内的局部变量上应用embed指示符（当然我们可以通过将包级变量赋值给局部变量来过渡一下）；</li>
<li>使用//go:embed指示符的包必须以空导入的方式导入embed包，二者是成对出现的，缺一不可；</li>
</ul>
<h4>3. net包的变化</h4>
<p>在Go 1.16之前，我们检测在一个已关闭的网络上进行I/O操作或在I/O完成前网络被关闭的情况，只能通过匹配字符串”use of closed network connection”的方式来进行。之前的版本没有针对这个错误定义“哨兵错误变量”(更多关于哨兵错误变量的内容，可以参考我的专栏文章<a href="https://www.imooc.com/read/87/article/2433">《别笑！这就是 Go 的错误处理哲学》</a>)，Go 1.16增加了ErrClosed这个“哨兵错误变量”，我们可以通过errors.Is(err, net.ErrClosed)来检测是否是上述错误情况。</p>
<h3>六. 小结</h3>
<p>从Go 1.16版本变更的功能特性中，我看到了Go团队更加重视社区的声音，这也是Go团队一直持续努力的目标。在最新的Go proposal review meeting的结论中，我们还看到了这样的一个<a href="https://github.com/golang/go/issues/43931">proposal</a>被accept：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-1.16-12.png" alt="" /></p>
<p>要知道这个proposal的提议是将在Go 1.18才会落地的泛型实现分支merge到Go项目master分支，也就是说在Go 1.17中就会包含“不会发布的”泛型部分实现，这在之前是不可能实现的(之前，新proposal必须有原型实现的分支，实现并经过社区测试与Go核心委员会评估后才会在特定版本merge到master分支)。虽说泛型的开发有其特殊情况，但能被accept，这恰证明了Go社区的声音在Go核心团队日益受到重视。</p>
<p><strong>如果你还没有升级到Go 1.16，那么现在正是时候</strong>。</p>
<p>本文中涉及的代码可以在<a href="https://github.com/bigwhite/experiments/tree/master/go1.16-examples">这里</a>下载。https://github.com/bigwhite/experiments/tree/master/go1.16-examples</p>
<hr />
<p><a href="https://mp.weixin.qq.com/s/jUqAL7hf2GmMun64BJufEA">“Gopher部落”知识星球</a>正式转正（从试运营星球变成了正式星球）！“gopher部落”旨在打造一个精品Go学习和进阶社群！高品质首发Go技术文章，“三天”首发阅读权，每年两期Go语言发展现状分析，每天提前1小时阅读到新鲜的Gopher日报，网课、技术专栏、图书内容前瞻，六小时内必答保证等满足你关于Go语言生态的所有需求！部落目前虽小，但持续力很强。在2021年上半年，部落将策划两个专题系列分享，并且是部落独享哦：</p>
<ul>
<li>Go技术书籍的书摘和读书体会系列</li>
<li>Go与eBPF系列</li>
</ul>
<p>考虑到部落尚处于推广期，这里仍然为大家准备了新人优惠券，虽然优惠幅度有所下降，但依然物超所值，早到早享哦！</p>
<p><img src="http://image.tonybai.com/img/202011/gopher-tribe-zsxq.png" alt="" /></p>
<p>Go技术专栏“<a href="https://www.imooc.com/read/87">改善Go语⾔编程质量的50个有效实践</a>”正在慕课网火热热销中！本专栏主要满足广大gopher关于Go语言进阶的需求，围绕如何写出地道且高质量Go代码给出50条有效实践建议，上线后收到一致好评！欢迎大家订阅！目前该技术专栏正在新春促销！关注我的个人公众号“iamtonybai”，发送“go专栏活动”即可获取专栏专属优惠码，可在订阅专栏时抵扣20元哦(2021.2月末前有效)。</p>
<p><img src="http://image.tonybai.com/img/202011/go-column-pgo-with-qr-and-text.png" alt="" /></p>
<p>我的网课“<a href="https://coding.imooc.com/class/284.html">Kubernetes实战：高可用集群搭建、配置、运维与应用</a>”在慕课网热卖中，欢迎小伙伴们订阅学习！</p>
<p><img src="https://tonybai.com/wp-content/uploads/k8s-practice-with-qr-and-text.png" alt="img{512x368}" /></p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/。smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。2020年4月8日，中国三大电信运营商联合发布《5G消息白皮书》，51短信平台也会全新升级到“51商用消息平台”，全面支持5G RCS消息。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻)归档仓库 &#8211; https://github.com/bigwhite/gopherdaily</p>
<p>我的联系方式：</p>
<ul>
<li>微博：https://weibo.com/bigwhite20xx</li>
<li>微信公众号：iamtonybai</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>“Gopher部落”知识星球：https://public.zsxq.com/groups/51284458844544</li>
</ul>
<p>微信赞赏：<br />
<img src="https://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2021, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2021/02/25/some-changes-in-go-1-16/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>2020年Go语言盘点：新冠大流行阻挡不了Go演进的步伐</title>
		<link>https://tonybai.com/2020/12/30/the-2020-review-of-go-programming-language/</link>
		<comments>https://tonybai.com/2020/12/30/the-2020-review-of-go-programming-language/#comments</comments>
		<pubDate>Wed, 30 Dec 2020 05:11:15 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[2020]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[defer]]></category>
		<category><![CDATA[Gartner]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go-module]]></category>
		<category><![CDATA[go1.14]]></category>
		<category><![CDATA[go1.15]]></category>
		<category><![CDATA[go1.16]]></category>
		<category><![CDATA[go1.18]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GOPATH]]></category>
		<category><![CDATA[GopherCon]]></category>
		<category><![CDATA[gopls]]></category>
		<category><![CDATA[GOPROXY]]></category>
		<category><![CDATA[HypeCycle]]></category>
		<category><![CDATA[init]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[linker]]></category>
		<category><![CDATA[M1]]></category>
		<category><![CDATA[panic]]></category>
		<category><![CDATA[pkg.go.dev]]></category>
		<category><![CDATA[RobertGriesemer]]></category>
		<category><![CDATA[RussCox]]></category>
		<category><![CDATA[Testing]]></category>
		<category><![CDATA[TIOBE]]></category>
		<category><![CDATA[tzdata]]></category>
		<category><![CDATA[vscode]]></category>
		<category><![CDATA[vscode-go]]></category>
		<category><![CDATA[大流行]]></category>
		<category><![CDATA[技术成熟度曲线]]></category>
		<category><![CDATA[抢占式调度]]></category>
		<category><![CDATA[新冠病毒]]></category>
		<category><![CDATA[泛型]]></category>
		<category><![CDATA[类型参数]]></category>
		<category><![CDATA[链接器]]></category>
		<category><![CDATA[高德纳]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=3059</guid>
		<description><![CDATA[2020，这一六十年一遇的庚子年的确“名不虚传”。在这一年发生了很多事，而最受瞩目的事情莫过于新冠疫情的全球大流行。疫情给全球的经济带来了近似毁灭性的打击，给人们的生命带来了极大威胁，给人们的生活也带来了很大痛苦及不确定性。好在这个糟糕的2020年马上就要过去了！相信此时此刻每个人心中都会有一句呐喊：“2020，快滚吧！”。 然而肆虐的新冠疫情并没有阻挡住Go语言前进的坚实步伐。在这艰难的一年中，在Go核心开发团队和Go社区的齐心协力下，Go同样取得了不俗的成绩，甚至在2020年3月(那时Go 1.14版本刚刚发布不到一个月)，Go在TIOBE的编程语言排行榜中还一度挤进前十(而2019年同期，Go仅位列18位)： 这恰说明Go语言的开发与推广工作得到了更多来自全球的开发者的认可。在这篇文章中，我们就来做一下2020年Go语言的盘点，看看在2020年围绕Go语言、Go社区和Go生态圈都发生了哪些有影响和有意义的事情。 1. 面对大流行，Go核心团队给出“定心丸” 大流行始于2020年1月的武汉，但真正的全球大流行则大致始于2020年3月。面对新冠全球大流行，Go核心开发团队于3月25日作出反应，在官博发表文章《Go, the Go Community, and the Pandemic》，迅速调整了Go语言2020年的演进计划，给出了大流行期间的工作原则： Go始终排在诸如个人和家庭健康与安全之类的基本问题之后； 调整全年Go技术会议的计划，推迟或改为线上举办虚拟技术大会，为全球Gopher提供获取这些会议最新信息的渠道服务； 为在线培训师、Go职位发布提供便利服务； 为新冠病毒提供帮助工作台：https://covid-oss-help.org/； 调整Go工作计划，缩减Go 1.15中包含的新特性和改进，但会遵循Go 1.15的发布时间表；重点支持gopls、pkg.go.dev的演进和优化。 Go核心开发团队的这份声明虽然简短，但却给Go社区吃了一颗“定心丸”，为Go语言在2020新冠大流行年中的稳步演进确定了节奏，指明了方向，奠定了基础。 2. Go在2020年值得关注的那些变化 2020一年，Go核心开发团队、社区和生态圈做了很多工作，但这里无法一一枚举，仅挑出一些重要的变化列在这里： 2020年2月26日，Go 1.14版本发布。主要的变动点包括： 嵌入接口的方法集可重叠； 基于系统信号机制实现了异步抢占式的goroutine调度； defer性能得以继续优化，理论上有30%的性能提升； go module已经生产就绪，并支持subversion源码仓库； 重新实现了运行时的timer； testing包的T和B类型都增加了自己的Cleanup方法。 2020年4月20日，发布2019年Go开发者调查结果： 参与2019开发者调查的gopher数量几乎为2018年的2倍，达到10,975人； 大多数受访者每天都在使用Go，而且这个数字每年都有上升的趋势； Go的使用仍然集中在科技公司，但Go越来越多地出现在更广泛的行业中，如金融和媒体； 调查的大部分指标的同比值都很稳定； 受访者正在使用Go来解决类似的问题，特别是构建API/RPC服务和CLI，和他们工作的组织规模大小关系不大； 大多数团队试图快速更新到最新的Go版本；当第三方供应商迟迟不支持当前的Go版本时，就会给开发者造成采用障碍； 现在Go生态系统中几乎所有人都在使用go module，但围绕包管理的一些混乱仍然存在； 需要改进的高优先级领域包括调试、go module使用以及与云服务交互的体验改善； VS Code和GoLand的使用量持续增加；现在每4个受访者中就有3个首选它们。 2020年6月，vscode-go扩展(vscode上的go标准插件)将主代码库从github.com/microsoft/vscode-go迁移到github.com/golang/vscode-go，成为Go官方项目的一部分。 同在2020年6月，pkg.go.dev网站开源！该网站是Go团队在Go社区建设方面做出的主要工作，开源后的pkg.go.dev将接收更多来自社区的想法和改进意见，比如：11月，pkg.go.dev就发布了新版页面设计；原godoc.org的请求也被重定向到pkg.go.dev(广大gopher可能需要一段时间来适应这种改变)。 2020年8月，Go 1.15版本发布，其主要的变动点包括： GOPROXY新增以管道符为分隔符的代理列表值； module cache的存储路径可设置; 改善派生自原生类型的自定义类型变量在panic时的输出形式； 将小整数([0,255])转换为interface类型值时将不会额外分配内存； [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/the-2020-review-of-go-programming-language-1.png" alt="img{512x368}" /></p>
<p>2020，这一六十年一遇的庚子年的确“名不虚传”。在这一年发生了很多事，而最受瞩目的事情莫过于<strong>新冠疫情的全球大流行</strong>。疫情给全球的经济带来了近似毁灭性的打击，给人们的生命带来了极大威胁，给人们的生活也带来了很大痛苦及不确定性。好在<strong>这个糟糕的2020年马上就要过去了</strong>！相信此时此刻每个人心中都会有一句呐喊：“<strong>2020，快滚吧</strong>！”。</p>
<p>然而肆虐的新冠疫情并没有阻挡住Go语言前进的坚实步伐。在这艰难的一年中，在Go核心开发团队和Go社区的齐心协力下，Go同样取得了<strong>不俗的成绩</strong>，甚至在2020年3月(那时<a href="https://mp.weixin.qq.com/s/PVxdtvSXgNpiD65TUo-TCg">Go 1.14版本</a>刚刚发布不到一个月)，Go在<a href="https://tiobe.com/tiobe-index/">TIOBE的编程语言排行榜</a>中还一度<a href="https://my.oschina.net/u/4593547/blog/4453355">挤进前十</a>(而2019年同期，Go仅位列18位)：</p>
<p><img src="https://tonybai.com/wp-content/uploads/the-2020-review-of-go-programming-language-4.jpg" alt="img{512x368}" /></p>
<p>这恰说明Go语言的开发与推广工作得到了更多来自全球的开发者的认可。在这篇文章中，我们就来做一下2020年Go语言的盘点，看看在2020年围绕Go语言、Go社区和Go生态圈都发生了哪些有影响和有意义的事情。</p>
<h3>1. 面对大流行，Go核心团队给出“定心丸”</h3>
<p>大流行始于2020年1月的武汉，但真正的全球大流行则大致始于2020年3月。面对新冠全球大流行，Go核心开发团队于3月25日作出反应，在官博发表文章<a href="https://blog.golang.org/pandemic">《Go, the Go Community, and the Pandemic》</a>，迅速调整了Go语言2020年的演进计划，给出了大流行期间的工作原则：</p>
<ul>
<li>Go始终排在诸如个人和家庭健康与安全之类的基本问题之后；</li>
<li>调整全年Go技术会议的计划，推迟或改为线上举办虚拟技术大会，为全球Gopher提供获取这些会议最新信息的渠道服务；</li>
<li>为在线培训师、Go职位发布提供便利服务；</li>
<li>为新冠病毒提供<a href="https://covid-oss-help.org/">帮助工作台</a>：https://covid-oss-help.org/；</li>
<li>调整Go工作计划，缩减<a href="https://mp.weixin.qq.com/s/B5onfyP7BPYCh_rMSBtfcQ">Go 1.15</a>中包含的新特性和改进，但会遵循Go 1.15的发布时间表；重点支持gopls、pkg.go.dev的演进和优化。</li>
</ul>
<p>Go核心开发团队的这份声明虽然简短，但却给Go社区吃了一颗“定心丸”，为Go语言在2020新冠大流行年中的稳步演进确定了节奏，指明了方向，奠定了基础。</p>
<h3>2. Go在2020年值得关注的那些变化</h3>
<p>2020一年，Go核心开发团队、社区和生态圈做了很多工作，但这里无法一一枚举，仅挑出一些重要的变化列在这里：</p>
<ul>
<li>
<p>2020年2月26日，Go 1.14版本发布。主要的变动点包括：</p>
<ul>
<li>嵌入接口的方法集可重叠；</li>
<li>基于系统信号机制实现了异步抢占式的goroutine调度；</li>
<li>defer性能得以继续优化，理论上有30%的性能提升；</li>
<li>go module已经生产就绪，并支持subversion源码仓库；</li>
<li>重新实现了运行时的timer；</li>
<li>testing包的T和B类型都增加了自己的<strong>Cleanup</strong>方法。</li>
</ul>
</li>
<li>
<p>2020年4月20日，发布<a href="https://blog.golang.org/survey2019-results">2019年Go开发者调查结果</a>：</p>
<ul>
<li>参与2019开发者调查的gopher数量几乎为2018年的2倍，达到10,975人；</li>
<li>大多数受访者每天都在使用Go，而且这个数字每年都有上升的趋势；</li>
<li>Go的使用仍然集中在科技公司，但Go越来越多地出现在更广泛的行业中，如金融和媒体；</li>
<li>调查的大部分指标的同比值都很稳定；</li>
<li>受访者正在使用Go来解决类似的问题，特别是构建API/RPC服务和CLI，和他们工作的组织规模大小关系不大；</li>
<li>大多数团队试图快速更新到最新的Go版本；当第三方供应商迟迟不支持当前的Go版本时，就会给开发者造成采用障碍；</li>
<li>现在Go生态系统中几乎所有人都在使用<a href="https://mp.weixin.qq.com/s/RThCEQOdytQxwrMP7XRTRw">go module</a>，但围绕包管理的一些混乱仍然存在；</li>
<li>需要改进的高优先级领域包括调试、go module使用以及与云服务交互的体验改善；</li>
<li>VS Code和GoLand的使用量持续增加；现在每4个受访者中就有3个首选它们。</li>
</ul>
</li>
<li>
<p>2020年6月，vscode-go扩展(vscode上的go标准插件)将主代码库从github.com/microsoft/vscode-go迁移到github.com/golang/vscode-go，成为Go官方项目的一部分。</p>
</li>
<li>
<p>同在2020年6月，pkg.go.dev网站开源！该网站是Go团队在Go社区建设方面做出的主要工作，开源后的pkg.go.dev将接收更多来自社区的想法和改进意见，比如：11月，<a href="https://blog.golang.org/pkgsite-redesign">pkg.go.dev就发布了新版页面设计</a>；<a href="https://blog.golang.org/godoc.org-redirect">原godoc.org的请求也被重定向到pkg.go.dev</a>(广大gopher可能需要一段时间来适应这种改变)。</p>
</li>
<li>
<p>2020年8月，<a href="https://mp.weixin.qq.com/s/B5onfyP7BPYCh_rMSBtfcQ">Go 1.15版本发布</a>，其主要的变动点包括：</p>
<ul>
<li>GOPROXY新增以管道符为分隔符的代理列表值；</li>
<li>module cache的存储路径可设置;</li>
<li>改善派生自原生类型的自定义类型变量在panic时的输出形式；</li>
<li>将小整数([0,255])转换为interface类型值时将不会额外分配内存；</li>
<li>加入更现代化的链接器(linker)，新链接器的性能要提高20%，内存占用减少30%；</li>
<li>增加tzdata包。</li>
</ul>
</li>
<li>
<p>2020年11月初，全球最具影响力的Go语言技术大会<a href="https://www.gophercon.com/">GopherCon 2020</a>在线上举行！Austin Clements详细讲解了Go 1.14加入的基于系统信号的抢占式调度器；Go语言之父之一的Robert Griesemer讲解了<a href="https://mp.weixin.qq.com/s/SMT40557JgQ9FjUkswznlA">Go泛型当前的状态以及未来的计划</a>。会后Russ Cox确认了Go团队将在Go 1.18版本中<a href="https://mp.weixin.qq.com/s/14WeOQBdezWTC5OqQrJtfg">加入Go泛型(类型参数)</a>作为试验特性；</p>
</li>
<li>
<p>2020年11月10日，Russ Cox代表Go核心开发团队发文庆祝<a href="https://mp.weixin.qq.com/s/woQeEQUhOLJ7KSE5rm5q6g"><strong>Go语言发布11周年</strong></a>，在文中他回顾了Go这一年来的收获以及对2021年<a href="https://mp.weixin.qq.com/s/JzAQ3r9lDBad8PO6iAerqw">Go 1.16</a>和Go 1.17的展望。文中他还提到了GOPATH的历史使命即将结束，Go将开启全面module-aware模式的Go工具链时代！(下图来自推特)：</p>
</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/the-2020-review-of-go-programming-language-5.jpeg" alt="img{512x368}" /></p>
<ul>
<li>
<p>2020年12月中旬，Go 1.16beta1发布。在<a href="https://mp.weixin.qq.com/s/JzAQ3r9lDBad8PO6iAerqw">Go 1.16</a>中，Go将原生提供对Apple M1芯片(darwin/arm64)的支持；同时，在Go 1.16中go module将成为默认包依赖管理机制；Go 1.16还提供了支持在Go二进制文件中嵌入静态文件的官方原生方案，支持对init函数的执行时间和内存消耗的跟踪，链接器性能得到进一步优化等。</p>
</li>
<li>
<p>2020年12月16日，gopls <a href="https://github.com/golang/tools/releases/tag/gopls%2Fv0.6.0">v0.6.0</a>发布。同期，vscode-go也正<a href="https://github.com/golang/vscode-go/issues/1037">计划将gopls作为默认语言服务器</a>。</p>
</li>
</ul>
<h3>3. Go语言当前的状态：已来到“稳定爬升的光明期”</h3>
<p>今年笔者在知乎上滞留的时间比往年要长一些，看到很多人问与Go相关的一些问题，大致都是询问有关Go语言前景的，比如：</p>
<ul>
<li><a href="https://www.zhihu.com/question/379439622/answer/1618633268">2020年以后是Go语言的天下吗？</a></li>
<li><a href="https://www.zhihu.com/question/386183447/answer/1575697918">2020年各个大厂内部Go语言开发环境是怎样的呢？有什么可以分享的经验吗？</a></li>
<li><a href="https://www.zhihu.com/question/431762724/answer/1593984796">Go语言前景如何？</a></li>
<li><a href="https://www.zhihu.com/question/435974651/answer/1648652343">2021年后哪个后端编程语言会越来越流行？</a></li>
</ul>
<p>无论上述问题的题目有何不同，其本质的疑问都是“<strong>Go语言前景/钱景如何，值不值得投入去学习?</strong>”。那么是否存在一种成熟的方法能相对客观地描会出Go语言的发展态势并能对未来Go的走势做出指导呢？我想Gartner的<a href="https://www.gartner.com/en/research/methodologies/gartner-hype-cycle"><strong>技术成熟度曲线（The Hype Cycle）</strong></a>或许可以一试。</p>
<p>我们知道Gartner的技术成熟度曲线又叫技术循环曲线，是企业用来评估新科技是否要采用或采用时机的一种可视化方法，它利用时间轴与该技术在市面上的可见度(媒体曝光度)决定要不要采用以及何时该种新科技，下面就是一条典型的技术成熟度曲线的形状：</p>
<p><img src="https://tonybai.com/wp-content/uploads/the-2020-review-of-go-programming-language-6.png" alt="img{512x368}" /></p>
<p>同理，将该技术成熟度曲线应用于某种编程语言，比如Go，我们就可以用它来判断该编程语言所处的成熟阶段以辅助决定要不要采用以及何时采用该门语言。我们从知名的<a href="https://www.tiobe.com/tiobe-index/go/">TIOBE编程语言指数排行榜获取Go从2009年开源以来至今的指数曲线图</a>，并且根据<a href="https://tip.golang.org/doc/devel/release.html">Go版本发布史</a>在图中标记出了各个时段的Go发布版本：</p>
<p><img src="https://tonybai.com/wp-content/uploads/the-2020-review-of-go-programming-language-2.png" alt="img{512x368}" /></p>
<p>对比上面的Gartner成熟度曲线，相信你肯定有所发现。我们共同来解释一下：</p>
<ul>
<li>Go语言从2009年宣布开源以来，经历了两次“高峰”：一次是2009年刚刚宣布开源后，一次是在Go1.7~Go 1.9期间。显然，第一次的高峰实际上是一个“假高峰”，那时的Go连1.0版本都尚未发布，我们完全可以将其“剔除”掉。</li>
<li>从图中来看，Go语言的技术萌芽期是比较长的，从2012年的Go 1.0一直持续到2015年的<a href="https://tonybai.com/2015/07/10/some-changes-in-go-1-5/">Go 1.5</a>；</li>
<li><a href="https://tonybai.com/2015/07/10/some-changes-in-go-1-5/">Go 1.5版本</a>的自举以及Go垃圾回收延迟的大幅下降“引爆”了Go的“媒体曝光度”，Go技术的“期望膨胀期”开始，经历从<a href="https://tonybai.com/2016/02/21/some-changes-in-go-1-6/">Go 1.6</a>到<a href="https://tonybai.com/2017/07/14/some-changes-in-go-1-9/">Go 1.9版本</a>的发布后，业界对Go的期望达到了峰值；</li>
<li>从Go 1.10开始，Go似乎变得“仿徨”起来，原本期望Go“一统天下”的愿望没能实现，全面出击失败后，期望的落空导致了人们对<a href="https://mp.weixin.qq.com/s/TJsEvqPA00qvGSRr6a8Emg">Go产生了“功能孱弱劣势”的印象</a>，于是Go在Go 1.11发布前跌到了“泡沫破裂”的谷底；</li>
<li><a href="https://tonybai.com/2018/11/19/some-changes-in-go-1-11">Go 1.11</a>引入了<a href="https://tonybai.com/2019/06/03/the-practice-of-upgrading-major-version-under-go-module/">Go module</a>，给社区解决<a href="https://tonybai.com/2019/09/21/brief-history-of-go-package-management">Go包依赖问题</a>打了一剂强心剂，于是Go又开始了缓慢的爬升；</li>
<li>从TIOBE提供的曲线来看，<a href="https://tonybai.com/2019/03/02/some-changes-in-go-1-12">Go 1.12</a>到<a href="https://mp.weixin.qq.com/s/B5onfyP7BPYCh_rMSBtfcQ">Go 1.15版本</a>的发布让我们有信心认为Go已经进入了“稳步爬升的光明期”。</li>
</ul>
<p>到此，我相信知乎上的很多问题都应该迎刃而解了，剩下的只是<a href="https://mp.weixin.qq.com/s/2rsBJbz55nDEDax6vqKE5w">如何学习Go的细节</a>和<a href="https://mp.weixin.qq.com/s/RThCEQOdytQxwrMP7XRTRw">如何Go进阶</a>了。</p>
<p>不过可能还有很多朋友会问，Go何时能达到<strong>实质生产高峰期</strong>呢？这个问题真不好回答。但进入了“稳步爬升的光明期”后的Go到达实质生产高峰期只是一个时间问题了，也许2022年初发布的<a href="https://mp.weixin.qq.com/s/SMT40557JgQ9FjUkswznlA">支持Go泛型特性</a>的Go 1.18版本会快速推动Go向更高阶段进发！</p>
<h3>4. 展望Go的2021：继续蓄力，迎接下一个“引爆点”</h3>
<p>促使Go回到“稳步爬升光明期”的go module机制将在2021年年初正式发布的Go 1.16中成为默认包依赖管理机制。而<a href="https://github.com/golang/go/milestone/145">Go 1.16版本</a>也已经处于特性冻结并发布了beta1版本的阶段，其更多特性可以参考我的<a href="https://mp.weixin.qq.com/s/JzAQ3r9lDBad8PO6iAerqw">“Go 1.16新功能特性不完全前瞻”</a>一文。</p>
<p>将于2021年八月发布的<a href="https://github.com/golang/go/milestone/163">Go 1.17的里程碑</a>已经建立, 从里程碑的内容来看，已基本确定加入的功能特性和改进包括：</p>
<ul>
<li>针对x86-64的新的<a href="https://github.com/golang/go/issues/40724">基于寄存器的调用约定</a>（不破坏现有程序集！），这将使程序与主流语言的ABI模型保持一致，并且整体更快；</li>
<li><a href="https://github.com/golang/go/issues/41184">加入build指示器新语法</a>：<strong>//go:build</strong>；</li>
<li><a href="https://github.com/golang/go/issues/395">一个十多年前的issue</a>被Go团队accept：使用<strong>(*[4]int)(x)</strong>语法将切片x转型为一个数组类型指针(*[4]int)。 </li>
</ul>
<p>当然Go 1.17还会持续优化链接器，更多功能特性和改进还待Go团队策划补充。</p>
<p>而万众期待的Go泛型依然会继续打磨，从2016年Ian Lance Taylor提出<a href="https://github.com/golang/proposal/blob/master/design/15292-generics.md">“Go should have generics”</a>的设计草案以来，Go泛型草案至今已经讨论了4年多了，这再次证明了Go团队对于这类会显著增加Go复杂性的特性是多么地“慎之又慎”。虽然Go团队初步确定了在Go 1.18版本中将Go泛型（类型参数）落地，但近期Go项目中关于Go泛型的<a href="https://github.com/golang/go/issues/15292">主issue：proposal: spec: generic programming facilities</a>中仍然有不少反对的声音。Go团队在<strong>“继续保持Go简单”</strong>的道路上真是任重道远啊！</p>
<p>总之，2021年，Go将继续稳步爬升，也许爬的并没有那么快，但在我看来，这是在积蓄力量，等待着下一个引爆点。</p>
<h3>5. 小结</h3>
<p>Go在新冠疫情大流行的历史时期依旧步行稳健，为下一个“引爆点”积极蓄力。Go在自己传统领域依旧存在明显优势，比如：企业级应用、基础设施、中间件、微服务API、命令行应用等，并且在这些领域取得了越来越多开发者的青睐。</p>
<p>Go在其他领域也有“意外收获”，比如：<a href="https://www.imperva.com/blog/python-and-go-top-the-chart-of-2019s-most-popular-hacking-tools/">在黑客工具领域，Go已经逐渐威胁着Python的龙头地位了</a>，显然<a href="https://www.imooc.com/read/87/article/2321">语法简单</a>、<a href="https://www.imooc.com/read/87/article/2340">原生并发</a>、<a href="https://www.imooc.com/read/87/article/2341">自带“电池”</a>、轻松跨平台的编译以及编译为独立二进制文件的Go与黑客的需求十分契合。不过，在安全领域成为了进攻“武器”，这想必是Go设计者们所意料不到的。</p>
<h3>6. 福利！2020年本博客最受欢迎Go相关文章TOP10</h3>
<ul>
<li><a href="https://mp.weixin.qq.com/s/mkpnR8LYHtauBGzQC-SglQ">Go新泛型设计方案详解</a></li>
<li><a href="https://mp.weixin.qq.com/s/TJsEvqPA00qvGSRr6a8Emg">Go语言有哪些“劣势”</a></li>
<li><a href="https://mp.weixin.qq.com/s/woQeEQUhOLJ7KSE5rm5q6g">Go，11周年</a></li>
<li><a href="https://mp.weixin.qq.com/s/JzAQ3r9lDBad8PO6iAerqw">Go 1.16新功能特性不完全前瞻</a></li>
<li><a href="https://mp.weixin.qq.com/s/PVxdtvSXgNpiD65TUo-TCg">Go 1.14中值得关注的几个变化</a></li>
<li><a href="https://mp.weixin.qq.com/s/B5onfyP7BPYCh_rMSBtfcQ">Go 1.15中值得关注的几个变化</a></li>
<li><a href="https://mp.weixin.qq.com/s/zrM0I-CsEujAm6ho6AD79g">像跟踪分布式服务调用那样跟踪Go函数调用链</a></li>
<li><a href="https://mp.weixin.qq.com/s/2rsBJbz55nDEDax6vqKE5w">系统学习Go语言，有这几本书就够了</a></li>
<li><a href="https://mp.weixin.qq.com/s/rsDC-6paC5zN4sepWd5LqQ">通过实例深入理解sync.Map的工作原理</a></li>
<li><a href="https://mp.weixin.qq.com/s/RThCEQOdytQxwrMP7XRTRw">Go专栏“改善Go语言编程质量的50个有效实践”上线了</a></li>
</ul>
<hr />
<p><strong>Gopher部落</strong>知识星球已正式转正了！高品质首发Go技术文章，“三天”首发阅读权，每年两期Go语言发展现状分析，每天提前1小时阅读到新鲜的Gopher日报，网课、技术专栏、图书内容前瞻，六小时内必答保证等满足你关于Go语言生态的所有需求！星球首开，福利自然是少不了的！2020年年底之前，8.8折加入星球，下方图片扫起来吧，先到先得哦！</p>
<p><img src="http://image.tonybai.com/img/202011/gopher-tribe-zsxq.png" alt="" /></p>
<p>Go技术专栏“<a href="https://www.imooc.com/read/87">改善Go语⾔编程质量的50个有效实践</a>”正在慕课网火热热销中！本专栏主要满足广大gopher关于Go语言进阶的需求，围绕如何写出地道且高质量Go代码给出50条有效实践建议，上线后收到一致好评！欢迎大家订阅！</p>
<p><img src="http://image.tonybai.com/img/202011/go-column-pgo-with-qr-and-text.png" alt="" /></p>
<p>我的网课“<a href="https://coding.imooc.com/class/284.html">Kubernetes实战：高可用集群搭建、配置、运维与应用</a>”在慕课网热卖中，欢迎小伙伴们订阅学习！</p>
<p><img src="https://tonybai.com/wp-content/uploads/k8s-practice-with-qr-and-text.png" alt="img{512x368}" /></p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/<br />
smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。</p>
<p>2020年4月8日，中国三大电信运营商联合发布《5G消息白皮书》，51短信平台也会全新升级到“51商用消息平台”，全面支持5G RCS消息。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻)归档仓库 &#8211; https://github.com/bigwhite/gopherdaily</p>
<p>我的联系方式：</p>
<ul>
<li>微博：https://weibo.com/bigwhite20xx</li>
<li>微信公众号：iamtonybai</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>“Gopher部落”知识星球：https://public.zsxq.com/groups/51284458844544</li>
</ul>
<p>微信赞赏：<br />
<img src="https://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2020, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2020/12/30/the-2020-review-of-go-programming-language/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go 1.16新功能特性不完全前瞻</title>
		<link>https://tonybai.com/2020/12/12/a-forward-look-to-new-feature-of-go-1-16/</link>
		<comments>https://tonybai.com/2020/12/12/a-forward-look-to-new-feature-of-go-1-16/#comments</comments>
		<pubDate>Sat, 12 Dec 2020 03:37:41 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[arm64]]></category>
		<category><![CDATA[bson]]></category>
		<category><![CDATA[Cgo]]></category>
		<category><![CDATA[Darwin]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go.mod]]></category>
		<category><![CDATA[go1.15]]></category>
		<category><![CDATA[go1.16]]></category>
		<category><![CDATA[go:embed]]></category>
		<category><![CDATA[GOARCH]]></category>
		<category><![CDATA[GODEBUG]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[gomodule]]></category>
		<category><![CDATA[GOOS]]></category>
		<category><![CDATA[Gopher部落]]></category>
		<category><![CDATA[init]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[json]]></category>
		<category><![CDATA[linker]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[M1]]></category>
		<category><![CDATA[Macbook]]></category>
		<category><![CDATA[metrics]]></category>
		<category><![CDATA[retract]]></category>
		<category><![CDATA[RISC-V]]></category>
		<category><![CDATA[runtime]]></category>
		<category><![CDATA[structtag]]></category>
		<category><![CDATA[Unicode]]></category>
		<category><![CDATA[XML]]></category>
		<category><![CDATA[知识星球]]></category>
		<category><![CDATA[结构体]]></category>
		<category><![CDATA[运行时]]></category>
		<category><![CDATA[链接器]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=3026</guid>
		<description><![CDATA[2020年最后一个购物狂欢，双十二购物节“Gopher部落”知识星球推出双十二优惠！本年度最低折扣仅限今天一天。笔者建立“Gopher部落”旨在建立一个高质量的Go语言技术精品社区，持续不断的高质量技术资料分享，让加入的星友每天都有新收获！欢迎大家加入！ Go 1.16将于2021年2月发布。目前已经进入freeze状态，即不再接受新feature，仅fix bug、编写文档和接受安全更新等。 目前Go 1.16的发布说明尚处于早期草稿阶段，但Go团队成员正在致力于编写发布说明。Go 1.16的完全特性列表说明还得等真正发布前才能得到。如今要了解Go 1.16功能特性都有哪些变化，只能结合现有的release note以及从Go 1.16里程碑中的issue列表中挖掘。 下面就“挖掘”到的Go 1.16重要功能特性变化做简要的且不完全的前瞻。 1. 支持Apple Silicon M1芯片 Apple Silicon M1芯片Macbook的发布让Go团队紧急为Go 1.16增加对M1的支持。如果要跨平台编译，只需设定GOOS=darwin, GOARCH=arm64即可构建出可以在搭载M1芯片的Macbook上运行的Go应用。 同时Go 1.16还增加了对ios/amd64的支持，主要是为了支持在amd64架构上的MacOS上运行ios模拟器。 2. RISC-V架构支持cgo和-buildmode=pie RISC-V架构很可能是未来5-10年挑战ARM的最主要架构，Go语言持续加大对RISC-V架构的支持，在Go 1.16中对linux/riscv64又增加了cgo支持以及-buildmode=pie。不过目前对risc-v仍仅限于linux os。 3. 有关go module的变化 module-aware模式成为默认状态。如要回到gopath mode，将GO111MODULE设置为auto； go build和go test不会修改go.mod和go.sum文件。能修改这两个文件的命令只有go get和go mod tidy； go get之前的构建和安装go包的行为模式将被废弃。go get将专注于分析依赖，并获取go包/module，更新go.mod/go.sum； go install将恢复自己构建和安装包的“角色”（在go module加入后，go install日益受到冷落，这次翻身了)； go.mod将支持retract指示符，包或module作者可以利用该指示符在自己module的go.mod中标记某些版本撤回(因不安全、不兼容或损坏等原因)，不建议使用。 go.mod中的exclude指示符语义变更：Go 1.16中将忽略exclude指示的module/包依赖；而之前的版本go工具链仅仅是跳过exclude指示的版本，而使用该依赖包/module的下一个版本。 -i build flag废弃； go get的-insecure命令行标志选项作废，可以用GOINSECURE环境变量指示go get是否通过不安全的http去获取包； [...]]]></description>
			<content:encoded><![CDATA[<p>2020年最后一个购物狂欢，<a href="http://image.tonybai.com/img/202011/gopher-tribe-2020-12-12.png">双十二购物节“Gopher部落”知识星球推出双十二优惠！</a>本年度最低折扣仅限今天一天。笔者建立“Gopher部落”旨在建立一个高质量的Go语言技术精品社区，持续不断的高质量技术资料分享，让加入的星友每天都有新收获！欢迎大家加入！</p>
<p><img src="http://image.tonybai.com/img/202011/gopher-tribe-2020-12-12.png" alt="" /></p>
<p>Go 1.16将于2021年2月发布。目前已经进入freeze状态，即不再接受新feature，仅fix bug、编写文档和接受安全更新等。</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-1.16.png" alt="img{512x368}" /></p>
<p>目前Go 1.16的<a href="https://tip.golang.org/doc/go1.16">发布说明</a>尚处于早期草稿阶段，但Go团队成员正在致力于编写发布说明。Go 1.16的完全特性列表说明还得等真正发布前才能得到。如今要了解Go 1.16功能特性都有哪些变化，只能结合现有的release note以及从<a href="https://github.com/golang/go/issues?q=is%3Aissue+milestone%3AGo1.16+is%3Aclosed">Go 1.16里程碑</a>中的issue列表中挖掘。</p>
<p>下面就“挖掘”到的Go 1.16重要功能特性变化做简要的且不完全的前瞻。</p>
<h3>1. 支持Apple Silicon M1芯片</h3>
<p>Apple Silicon M1芯片Macbook的发布让Go团队紧急为Go 1.16增加对M1的支持。如果要跨平台编译，只需设定GOOS=darwin, GOARCH=arm64即可构建出可以在搭载M1芯片的Macbook上运行的Go应用。</p>
<p>同时Go 1.16还增加了对ios/amd64的支持，主要是为了支持在amd64架构上的MacOS上运行ios模拟器。</p>
<h3>2. RISC-V架构支持cgo和-buildmode=pie</h3>
<p>RISC-V架构很可能是未来5-10年挑战ARM的最主要架构，Go语言持续加大对RISC-V架构的支持，在Go 1.16中对linux/riscv64又增加了cgo支持以及-buildmode=pie。不过目前对risc-v仍仅限于linux os。</p>
<h3>3. 有关go module的变化</h3>
<ul>
<li>module-aware模式成为默认状态。如要回到gopath mode，将GO111MODULE设置为auto；</li>
<li>go build和go test不会修改go.mod和go.sum文件。能修改这两个文件的命令只有go get和go mod tidy；</li>
<li>go get之前的构建和安装go包的行为模式将被废弃。go get将专注于分析依赖，并获取go包/module，更新go.mod/go.sum；</li>
<li>go install将恢复自己构建和安装包的“角色”（在go module加入后，go install日益受到冷落，这次翻身了)；</li>
<li><a href="https://github.com/golang/go/issues/24031">go.mod将支持retract指示符</a>，包或module作者可以利用该指示符在自己module的go.mod中标记某些版本撤回(因不安全、不兼容或损坏等原因)，不建议使用。</li>
<li>go.mod中的exclude指示符语义变更：Go 1.16中将忽略exclude指示的module/包依赖；而之前的版本go工具链仅仅是跳过exclude指示的版本，而使用该依赖包/module的下一个版本。</li>
<li>-i build flag废弃；</li>
<li>go get的-insecure命令行标志选项作废，可以用GOINSECURE环境变量指示go get是否通过不安全的http去获取包；</li>
</ul>
<h3>4. 支持在Go二进制文件中嵌入静态文件(文本、图片等)</h3>
<p>Go 1.16新增<strong>go:embed</strong>指示符和embed标准库包，二者一起用于支持在在Go二进制文件中嵌入静态文件。下面是一个在Go应用中嵌入文本文件用于http应答内容的小例子：</p>
<pre><code>// hello.txt
hello, go 1.16

// main.go
package main

import (
         _  "embed"
    "net/http"
)

//go:embed hello.txt
var s string

func main() {
    http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte(s))
    }))
    http.ListenAndServe(":8080", nil)
}
</code></pre>
<p>上述源码中的<strong>go:embed</strong>指示符的含义是：将hello.txt内容存储在字符串变量s中。我们构建该源码，并验证一下s中存储的是否是hello.txt中的数据：</p>
<pre><code>$ go build -o demo main.go
$ mv hello.txt hello.txt.bak // 将hello.txt改名，我们看看数据是否真的已经嵌入到二进制文件demo中了
$ ./demo

$curl localhost:8080
hello, go 1.16
</code></pre>
<h3>5.GODEBUG环境变量支持跟踪</h3>
<p>当GODEBUG环境变量包含inittrace=1时，Go运行时将会报告各个源代码文件中的init函数的执行时间和内存开辟消耗情况。比如对于上面的程序demo，我们按如下命令执行：</p>
<pre><code># GODEBUG=inittrace=1 ./demo
init internal/bytealg @0.014 ms, 0 ms clock, 0 bytes, 0 allocs
init runtime @0.033 ms, 0.015 ms clock, 0 bytes, 0 allocs
init errors @0.24 ms, 0.003 ms clock, 0 bytes, 0 allocs
init sync @0.47 ms, 0.001 ms clock, 16 bytes, 1 allocs
init io @0.66 ms, 0 ms clock, 144 bytes, 9 allocs
init internal/oserror @0.85 ms, 0 ms clock, 80 bytes, 5 allocs
init syscall @1.0 ms, 0.006 ms clock, 624 bytes, 2 allocs
init time @1.2 ms, 0.013 ms clock, 384 bytes, 8 allocs
init path @1.4 ms, 0.003 ms clock, 16 bytes, 1 allocs
init io/fs @1.6 ms, 0 ms clock, 16 bytes, 1 allocs
init context @2.3 ms, 0.002 ms clock, 128 bytes, 4 allocs
init math @2.5 ms, 0 ms clock, 0 bytes, 0 allocs
init strconv @2.7 ms, 0 ms clock, 32 bytes, 2 allocs
init unicode @2.9 ms, 0.065 ms clock, 23736 bytes, 26 allocs
init bytes @3.2 ms, 0 ms clock, 48 bytes, 3 allocs
init crypto @3.3 ms, 0.001 ms clock, 160 bytes, 1 allocs
init reflect @3.5 ms, 0.002 ms clock, 0 bytes, 0 allocs
init encoding/binary @3.7 ms, 0 ms clock, 16 bytes, 1 allocs
init crypto/cipher @3.8 ms, 0 ms clock, 16 bytes, 1 allocs
init crypto/aes @4.0 ms, 0.003 ms clock, 16 bytes, 1 allocs
init internal/poll @4.1 ms, 0 ms clock, 64 bytes, 4 allocs
init os @4.2 ms, 0.029 ms clock, 544 bytes, 13 allocs
init fmt @4.4 ms, 0.003 ms clock, 32 bytes, 2 allocs
init math/rand @4.5 ms, 0.023 ms clock, 5440 bytes, 3 allocs
init math/big @4.7 ms, 0.002 ms clock, 32 bytes, 2 allocs
init crypto/sha512 @4.8 ms, 0.004 ms clock, 0 bytes, 0 allocs
init encoding/asn1 @5.0 ms, 0.004 ms clock, 224 bytes, 7 allocs
init vendor/golang.org/x/crypto/cryptobyte @5.1 ms, 0 ms clock, 48 bytes, 2 allocs
init crypto/ecdsa @5.3 ms, 0 ms clock, 48 bytes, 3 allocs
init bufio @5.4 ms, 0.003 ms clock, 176 bytes, 11 allocs
init crypto/rand @5.6 ms, 0.001 ms clock, 120 bytes, 4 allocs
init crypto/rsa @5.7 ms, 0.007 ms clock, 648 bytes, 18 allocs
init crypto/sha1 @5.8 ms, 0 ms clock, 0 bytes, 0 allocs
init crypto/sha256 @5.9 ms, 0 ms clock, 0 bytes, 0 allocs
init encoding/base64 @5.9 ms, 0.006 ms clock, 1408 bytes, 4 allocs
init crypto/md5 @6.0 ms, 0 ms clock, 0 bytes, 0 allocs
init encoding/hex @6.1 ms, 0 ms clock, 16 bytes, 1 allocs
init crypto/x509/pkix @6.1 ms, 0.001 ms clock, 624 bytes, 2 allocs
init path/filepath @6.2 ms, 0 ms clock, 16 bytes, 1 allocs
init vendor/golang.org/x/net/dns/dnsmessage @6.3 ms, 0.009 ms clock, 1616 bytes, 27 allocs
init net @6.3 ms, 0.029 ms clock, 2840 bytes, 74 allocs
init crypto/dsa @6.5 ms, 0 ms clock, 16 bytes, 1 allocs
init crypto/x509 @6.5 ms, 0.016 ms clock, 4768 bytes, 15 allocs
init io/ioutil @6.7 ms, 0.002 ms clock, 16 bytes, 1 allocs
init vendor/golang.org/x/sys/cpu @6.7 ms, 0.009 ms clock, 1280 bytes, 1 allocs
init vendor/golang.org/x/crypto/chacha20poly1305 @6.8 ms, 0 ms clock, 16 bytes, 1 allocs
init vendor/golang.org/x/crypto/curve25519 @6.9 ms, 0 ms clock, 0 bytes, 0 allocs
init crypto/tls @7.0 ms, 0.007 ms clock, 1600 bytes, 11 allocs
init log @7.0 ms, 0 ms clock, 80 bytes, 1 allocs
init mime @7.1 ms, 0.008 ms clock, 1232 bytes, 4 allocs
init mime/multipart @7.2 ms, 0.001 ms clock, 192 bytes, 4 allocs
init compress/flate @7.3 ms, 0.012 ms clock, 4240 bytes, 7 allocs
init hash/crc32 @7.4 ms, 0.014 ms clock, 1024 bytes, 1 allocs
init compress/gzip @7.5 ms, 0 ms clock, 32 bytes, 2 allocs
init vendor/golang.org/x/text/transform @7.5 ms, 0 ms clock, 80 bytes, 5 allocs
init vendor/golang.org/x/text/unicode/bidi @7.6 ms, 0.005 ms clock, 272 bytes, 2 allocs
init vendor/golang.org/x/text/secure/bidirule @7.7 ms, 0.008 ms clock, 16 bytes, 1 allocs
init vendor/golang.org/x/text/unicode/norm @7.8 ms, 0.002 ms clock, 0 bytes, 0 allocs
init vendor/golang.org/x/net/idna @7.8 ms, 0 ms clock, 0 bytes, 0 allocs
init vendor/golang.org/x/net/http/httpguts @7.9 ms, 0.002 ms clock, 848 bytes, 3 allocs
init vendor/golang.org/x/net/http2/hpack @7.9 ms, 0.063 ms clock, 22440 bytes, 32 allocs
init net/http/internal @8.1 ms, 0.005 ms clock, 1808 bytes, 3 allocs
init vendor/golang.org/x/net/http/httpproxy @8.2 ms, 0 ms clock, 336 bytes, 2 allocs
init net/http @8.3 ms, 0.026 ms clock, 10280 bytes, 113 allocs
</code></pre>
<p>我们看到各个依赖包中的init函数执行的消耗情况都被输出了出来，根据这些信息，我们可以很容易判断出init函数中可能存在的性能问题或瓶颈。</p>
<h3>6. 链接器进一步优化</h3>
<p>既<a href="https://mp.weixin.qq.com/s/B5onfyP7BPYCh_rMSBtfcQ">Go 1.15</a>实现了go linker的第一阶段优化后，Go 1.16中继续实施了对linker的第二阶段优化。优化后的链接器要平均比Go 1.15的快20%-25%，消耗的内存却减少5%-15%。</p>
<h3>7. struct field的tag中的多个key可以合并写</h3>
<p>如果某个结构体支持多种编码格式的序列化和反序列化，比如：json、bson、xml，那么之前版本需要按如下书写该结构体的字段tag，冗长且重复：</p>
<pre><code>type MyStruct struct {
  Field1 string `json:"field_1,omitempty" bson:"field_1,omitempty" xml:"field_1,omitempty" form:"field_1,omitempty" other:"value"`
}
</code></pre>
<p><a href="https://github.com/golang/go/issues/40281">Go 1.16支持将多个key进行合并</a>，上面的tag可以写成如下形式：</p>
<pre><code>type MyStruct struct {
  Field1 string `json bson xml form:"field_1,omitempty" other:"value"`
}
</code></pre>
<h3>8. 其他改变</h3>
<ul>
<li>新增runtime/metrics包，以替代runtime.ReadMemStats和debug.ReadGCStats输出runtime的各种度量数据，这个包更通用稳定，性能也更好；</li>
<li>新增io/fs包，用于提供只读的操作os的文件树的高级接口；</li>
<li>对Unicode标准的支持从12.0.0升级为13.0.0。 </li>
</ul>
<h3>附录：安装go tip版本的两种方式</h3>
<h4>1) 从源码安装</h4>
<pre><code>$git clone https//github.com/golang/go.git
$cd go/src
$./all.bash
</code></pre>
<h4>2) 使用gotip工具安装</h4>
<pre><code>$go get golang.org/dl/gotip
$gotip download
</code></pre>
<hr />
<p>我的Go技术专栏：“<a href="https://www.imooc.com/read/87">改善Go语⾔编程质量的50个有效实践</a>”上线了，欢迎大家订阅学习！</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-column-pgo-with-qr-and-text.png" alt="img{512x368}" /></p>
<p>我的网课“<a href="https://coding.imooc.com/class/284.html">Kubernetes实战：高可用集群搭建、配置、运维与应用</a>”在慕课网热卖中，欢迎小伙伴们订阅学习！</p>
<p><img src="https://tonybai.com/wp-content/uploads/k8s-practice-with-qr-and-text.png" alt="img{512x368}" /></p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/<br />
smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。</p>
<p>2020年4月8日，中国三大电信运营商联合发布《5G消息白皮书》，51短信平台也会全新升级到“51商用消息平台”，全面支持5G RCS消息。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻)归档仓库 &#8211; https://github.com/bigwhite/gopherdaily</p>
<p>我的联系方式：</p>
<ul>
<li>微博：https://weibo.com/bigwhite20xx</li>
<li>微信公众号：iamtonybai</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>“Gopher部落”知识星球：https://public.zsxq.com/groups/51284458844544</li>
</ul>
<p>微信赞赏：<br />
<img src="https://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2020, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2020/12/12/a-forward-look-to-new-feature-of-go-1-16/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go，11周年</title>
		<link>https://tonybai.com/2020/11/11/go-opensource-11-years/</link>
		<comments>https://tonybai.com/2020/11/11/go-opensource-11-years/#comments</comments>
		<pubDate>Tue, 10 Nov 2020 23:32:10 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[defer]]></category>
		<category><![CDATA[fuzzing]]></category>
		<category><![CDATA[generics]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go1.14]]></category>
		<category><![CDATA[go1.15]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[gomodule]]></category>
		<category><![CDATA[GOPATH]]></category>
		<category><![CDATA[GopherCon]]></category>
		<category><![CDATA[gopls]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[linker]]></category>
		<category><![CDATA[Module]]></category>
		<category><![CDATA[pkg.go.dev]]></category>
		<category><![CDATA[protobuf]]></category>
		<category><![CDATA[RussCox]]></category>
		<category><![CDATA[vscode]]></category>
		<category><![CDATA[泛型]]></category>
		<category><![CDATA[链接器]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=2981</guid>
		<description><![CDATA[本文翻译自Go官方博客文章《Eleven Years of Go》，原作者：Russ Cox。 今天，我们一起庆祝Go语言正式开业发布11周年。去年的“Go turning 10”周年庆典聚会似乎已成为久远的回忆。这是艰难的一年，但我们一直保持了Go开发的步伐，并积累了很多亮点。 在去年11月，我们在庆祝Go 10周年后不久就发布和上线了go.dev和pkg.go.dev站点。 今年2月，Go 1.14版本提供了第一个正式的“生产就绪”的go module实现，并进行了许多性能改进，包括更快的defer和真正抢占式的goroutine调度，以减少调度和垃圾收集延迟。 在今年三月初，我们推出了新版protobuf API：google.golang.org/protobuf，大幅改善了对protobuf reflection和自定义消息的支持。 当新冠疫情大流行发生时，我们决定在春季暂停所有公开发布或活动，因为大家都知道所有人的注意力都聚焦在其他地方。但是我们一直在努力，我们的团队中的一个成员加入了Apple/Google发起的“privacy-preserving exposure notifications”项目，以支持全球范围内的联系人追踪工作。5月，该小组启动了用Go编写的 reference backend server。 我们继续改进gopls，这让许多编辑器受益并都启用了高级Go-aware支持。六月份，VSCode Go扩展正式加入Go项目，现在由从事gopls的同一位开发人员维护。 同样在6月，由于Go社区的反馈意见，我们还将pkg.go.dev背后的代码开源，并将其作为Go项目的一部分。 6月下旬，我们 发布了有关Go generics的最新设计草案，以及原型工具和一个支持go generics实验语法的playground。 7月，我们发布并讨论了三个新的有关Go未来演化的设计草案：go:build、文件系统接口和构建时文件嵌入。（我们将在2021年看到所有新特性） 8月，Go 1.15版本发布！该版本以优化和bug修复为主，没有提供太多新功能。其最重要的部分是开始重写链接器，这使它在进行大型项目构建时，平均运行速度提高了20％，平均使用的内存减少了30％。 上个月，我们发起了年度Go用户调查。分析结果后，我们会将结果发布到博客上。 Go社区已经与其他所有人一起适应了“虚拟优先”的原则，今年我们看到了许多虚拟聚会和十多个虚拟Go会议。上周，Go团队在Google Open Source Live中举办了“Go Day”活动。 前进 我们也对Go语言在其第12年即将发生的事情感到非常兴奋。近期，Go团队成员将参加GopherCon 2020并做以下展示和分享。请打开您的日历，做好提醒标记！ 11月11日上午10:00，Robert Griesemer的演讲“Typing [Generic] Go”；在10:30 AM进行Q&#38;A。 11月11日中午12:00，现场播放Go时间播客的实况录像：“What to Expect When You’re NOT Expecting”，该集播客由包括Hana Kim组成的专家调试小组主持。 Michael [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/go-11th-years-old.png" alt="img{512x368}" /></p>
<p>本文翻译自Go官方博客文章<a href="https://blog.golang.org/11years">《Eleven Years of Go》</a>，原作者：<a href="https://swtch.com/~rsc/">Russ Cox</a>。</p>
<p>今天，我们一起庆祝Go语言正式开业发布11周年。去年的<a href="https://tonybai.com/2019/11/09/go-opensource-10-years/">“Go turning 10”</a>周年庆典聚会似乎已成为久远的回忆。这是艰难的一年，但我们一直保持了Go开发的步伐，并积累了很多亮点。</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-11th-years-old-1.jpg" alt="img{512x368}" /></p>
<p>在去年11月，我们在庆祝<a href="https://tonybai.com/2019/11/09/go-opensource-10-years/">Go 10周年</a>后不久就发布和上线了<a href="https://tonybai.com/2019/11/14/what-the-godev-website-bring-to-gophers/">go.dev和pkg.go.dev</a>站点。</p>
<p>今年2月，<a href="https://tonybai.com/2020/03/08/some-changes-in-go-1-14/">Go 1.14版本</a>提供了第一个正式的“生产就绪”的go module实现，并进行了许多性能改进，包括<a href="https://golang.org/design/34481-opencoded-defers">更快的defer</a>和<a href="https://golang.org/design/24543/conservative-inner-frame">真正抢占式的goroutine调度</a>，以减少调度和垃圾收集延迟。</p>
<p>在今年三月初，我们推出了<a href="https://blog.golang.org/protobuf-apiv2">新版protobuf API</a>：<a href="https://pkg.go.dev/google.golang.org/protobuf">google.golang.org/protobuf</a>，大幅改善了对protobuf reflection和自定义消息的支持。</p>
<p>当新冠疫情大流行发生时，我们决定在春季暂停所有公开发布或活动，因为大家都知道所有人的注意力都聚焦在其他地方。但是我们一直在努力，我们的团队中的一个成员加入了Apple/Google发起的<a href="https://www.google.com/covid19/exposurenotifications/">“privacy-preserving exposure notifications”</a>项目，以支持全球范围内的联系人追踪工作。5月，该小组启动了用Go编写的 <a href="https://github.com/google/exposure-notifications-server">reference backend server</a>。</p>
<p>我们继续改进<a href="https://www.youtube.com/watch?v=EFJfdWzBHwE">gopls</a>，这让许多编辑器受益并都启用了<a href="https://github.com/golang/tools/blob/master/gopls/doc/user.md">高级Go-aware支持</a>。六月份，<a href="https://blog.golang.org/vscode-go">VSCode Go扩展正式加入Go项目</a>，现在由从事gopls的同一位开发人员维护。</p>
<p>同样在6月，由于Go社区的反馈意见，我们还<a href="https://blog.golang.org/pkgsite">将pkg.go.dev背后的代码开源</a>，并将其作为Go项目的一部分。</p>
<p>6月下旬，我们 发布了<a href="https://blog.golang.org/generics-next-step">有关Go generics的最新设计草案</a>，以及原型工具和一个<a href="https://go2goplay.golang.org/">支持go generics实验语法的playground</a>。</p>
<p>7月，我们发布并讨论了三个新的有关Go未来演化的设计草案：<a href="https://golang.org/design/draft-gobuild">go:build</a>、<a href="https://golang.org/design/draft-iofs">文件系统接口</a>和<a href="https://golang.org/design/draft-embed">构建时文件嵌入</a>。（我们将在2021年看到所有新特性）</p>
<p>8月，<a href="https://tonybai.com/2020/10/11/some-changes-in-go-1-15/">Go 1.15版本发布</a>！该版本以优化和bug修复为主，没有提供太多新功能。其最重要的部分是开始重写链接器，这使它在进行大型项目构建时，平均运行速度提高了20％，平均使用的内存减少了30％。</p>
<p>上个月，我们发起了<a href="https://blog.golang.org/survey2020">年度Go用户调查</a>。分析结果后，我们会将结果发布到博客上。</p>
<p>Go社区已经与其他所有人一起适应了“虚拟优先”的原则，今年我们看到了许多虚拟聚会和十多个虚拟Go会议。上周，<a href="https://opensourcelive.withgoogle.com/events/go">Go团队在Google Open Source Live中举办了“Go Day”活动</a>。</p>
<h3>前进</h3>
<p>我们也对Go语言在其第12年即将发生的事情感到非常兴奋。近期，Go团队成员将参加<a href="https://www.gophercon.com/">GopherCon 2020</a>并做以下展示和分享。请打开您的日历，做好提醒标记！</p>
<ul>
<li>11月11日上午10:00，Robert Griesemer的演讲“Typing [Generic] Go”；在10:30 AM进行Q&amp;A。</li>
<li>11月11日中午12:00，现场播放Go时间播客的实况录像：“What to Expect When You’re NOT Expecting”，该集播客由包括Hana Kim组成的专家调试小组主持。</li>
<li>Michael Knyszek在11月11日下午1:00发表演讲“Evolving the Go Memory Manager&#8217;s RAM and CPU Efficiency” ；在下午1:50进行Q&amp;A。</li>
<li>Dan Scales在11月11日下午5:10发表演讲“Implementing Faster Defers”； 在下午5:40进行Q&amp;A。</li>
<li>11月12日下午3点，与朱莉·邱（Julie Qiu），丽贝卡·史翠宝（Rebecca Stambler），拉斯·考克斯（Russ Cox），萨默·阿杰曼尼（Sameer Ajmani）和范·里珀（Van Riper）一起的现场问答环节“ Go Team-Ask Me Anything” 。</li>
<li>奥斯汀·克莱门茨（Austin Clements）在11月12日下午4:45发表演讲“Pardon the Interruption: Loop Preemption in Go 1.14” ； 在下午5:15进行Q&amp;A。</li>
<li>乔纳森·阿姆斯特丹（Jonathan Amsterdam）在11月13日下午1:00发表的演讲：“Working with Errors” ； 在下午1:50进行Q&amp;A。</li>
<li>卡门·安多（Carmen Andoh）11月13日下午5:55发表的演讲“Crossing the Chasm for Go: Two Million Users and Growing” 。</li>
</ul>
<h3>Go发布计划</h3>
<p>2021年2月，Go 1.16版本将发布，该版本将包括新的<a href="https://tip.golang.org/pkg/io/fs/">文件系统接口</a>和<a href="https://tip.golang.org/pkg/embed/">构建时文件嵌入</a>。它将完成链接器的重写，从而带来更多的性能改进。它将包括对新的Apple Silicon（GOARCH=arm64）Mac的支持。</p>
<p>2021年8月，Go 1.17版本无疑会带来更多功能和改进，尽管远远不够，确切的细节仍然悬而未决。它将包括一个针对x86-64新的基于寄存器的调用约定（不破坏现有程序集！），这将使程序整体更快。（对其他体系结构的支持将在以后的版本中发布。）新的<strong>//go:build</strong>行肯定会包含一个不错的功能，肯定比当前<strong>// +build</strong>更不容易出错。我们希望明年可以进行Beta测试的另一个备受期待的功能是<a href="https://golang.org/design/draft-fuzzing">对go test命令中的模糊测试(fuzz test)的支持</a>。</p>
<h3>有关Go module</h3>
<p>明年，我们将继续致力于开发对Go module的支持，并将其很好地集成到整个Go生态系统中。Go 1.16将包括我们迄今为止最流畅的Go module体验。我们最近的一项调查的初步结果是，现在有96％的用户已采用Go模块（高于一年前的90％）。</p>
<p>我们还将最终终止对基于GOPATH的开发的支持：使用标准库以外的依赖项的任何程序都将需要一个go.mod。（如果您尚未切换到go module，请参阅<a href="https://golang.org/wiki/GOPATH">GOPATH Wiki页面</a>以获取有关从GOPATH到go module的最后一步的详细信息。）</p>
<p>从一开始，<a href="https://research.swtch.com/vgo-intro">Go module的目标</a>就是“将软件包版本的概念添加到Go开发人员和我们的工具的常用词汇中”，从而为整个Go生态系统中的module和版本提供深度支持。整个生态系统对包版本的广泛理解使得<a href="https://blog.golang.org/modules2019">go module镜像、chechsum数据库和module index</a>成为可能。在明年，我们将看到更多module支持被添加到更多的工具和系统中。例如，我们计划研究新的工具，以帮助模块作者发布新版本（go release），并帮助module使用者摆脱过时的API并完成迁移（新的go fix）。</p>
<p>一个更为有说服力的例子是，<a href="https://github.com/golang/tools/blob/master/gopls/README.md">我们创建了gopls</a>来减少编辑器为支持Go而依赖许多外部工具的情况：将依赖一堆不支持go module的工具转变为只依赖一个支持module的工具。明年，我们将准备让VSCode Go扩展默认使用gopls，以提供出色的、现成的module体验，并将发布gopls 1.0。当然，gopls最大的优势之一是它与编辑器无关：任何支持<a href="https://langserver.org/">语言服务器协议</a>的编辑器都可以使用它。</p>
<p>版本信息的另一个重要用途是跟踪构建中的任何程序包是否具有已知漏洞。明年，我们计划开发一个已知漏洞的数据库以及基于该数据库进行漏洞检查的工具程序。</p>
<p>Go软件包发现站点pkg.go.dev是Go module启用的版本感知系统的另一个示例。我们一直致力于正确实现核心功能和用户体验，包括<a href="https://blog.golang.org/pkgsite-redesign">今天重新设计后的pkg.go.dev的上线</a>。明年，我们将godoc.org统一为pkg.go.dev。我们还将扩展展示每个软件包的版本时间线，显示每个版本的重要更改，已知漏洞等，以实现<a href="https://research.swtch.com/deps">你进行依赖添加决策</a>时所需的所有信息。</p>
<p>我们很高兴看到从GOPATH到Go模块的旅程即将完成，以及Go模块正在启用的所有出色的依赖关系感知工具。</p>
<h3>有关Go generics</h3>
<p>每个人心中的下一个功能特性当然是泛型。如上所述，我们于今年6月发布了<a href="https://blog.golang.org/generics-next-step">有关泛型</a>的<a href="https://blog.golang.org/generics-next-step">最新设计草案</a>。从那时起，我们一直在做细节上的完善，并将注意力转移到了实现可生产版本的细节上。我们将在2021年的整个过程中继续努力，以期在年底之前为人们提供一些试用的目标，也许它是Go 1.18 beta的一部分。</p>
<h3>感谢大家</h3>
<p>Go不仅限于我们这些Google Go团队的成员。我们要感谢与我们一起开发Go项目和工具的贡献者。除此之外，Go之所以成功，是因为所有在Go蓬勃发展的生态系统中工作并为之贡献的人们。Go之外的世界度过了艰难的一年。非常感谢您抽出宝贵的时间加入我们，并帮助Go取得成功。谢谢。我们希望大家都安全，并祝您一切顺利。</p>
<hr />
<p>我的Go技术专栏：“<a href="https://www.imooc.com/read/87">改善Go语⾔编程质量的50个有效实践</a>”上线了，欢迎大家订阅学习！</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-column-pgo-with-qr-and-text.png" alt="img{512x368}" /></p>
<p>我的网课“<a href="https://coding.imooc.com/class/284.html">Kubernetes实战：高可用集群搭建、配置、运维与应用</a>”在慕课网上线了，感谢小伙伴们学习支持！</p>
<p><img src="https://tonybai.com/wp-content/uploads/k8s-practice-with-qr-and-text.png" alt="img{512x368}" /></p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/<br />
smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。</p>
<p>2020年4月8日，中国三大电信运营商联合发布《5G消息白皮书》，51短信平台也会全新升级到“51商用消息平台”，全面支持5G RCS消息。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻)归档仓库 &#8211; https://github.com/bigwhite/gopherdaily</p>
<p>我的联系方式：</p>
<ul>
<li>微博：https://weibo.com/bigwhite20xx</li>
<li>微信公众号：iamtonybai</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
</ul>
<p>微信赞赏：<br />
<img src="https://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2020, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2020/11/11/go-opensource-11-years/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go 1.15中值得关注的几个变化</title>
		<link>https://tonybai.com/2020/10/11/some-changes-in-go-1-15/</link>
		<comments>https://tonybai.com/2020/10/11/some-changes-in-go-1-15/#comments</comments>
		<pubDate>Sun, 11 Oct 2020 03:10:06 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[arm64]]></category>
		<category><![CDATA[cache]]></category>
		<category><![CDATA[Darwin]]></category>
		<category><![CDATA[GNU]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go1.11]]></category>
		<category><![CDATA[go1.13]]></category>
		<category><![CDATA[go1.14]]></category>
		<category><![CDATA[go1.15]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[gomodule]]></category>
		<category><![CDATA[GOPATH]]></category>
		<category><![CDATA[GOPROXY]]></category>
		<category><![CDATA[GOROOT]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[json]]></category>
		<category><![CDATA[linker]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Module]]></category>
		<category><![CDATA[objdump]]></category>
		<category><![CDATA[Package]]></category>
		<category><![CDATA[panic]]></category>
		<category><![CDATA[Reflect]]></category>
		<category><![CDATA[RISC-V]]></category>
		<category><![CDATA[tzdata]]></category>
		<category><![CDATA[unsafe]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[zoneinfo]]></category>
		<category><![CDATA[优化]]></category>
		<category><![CDATA[内存分配]]></category>
		<category><![CDATA[包]]></category>
		<category><![CDATA[标准库]]></category>
		<category><![CDATA[汇编]]></category>
		<category><![CDATA[编译器]]></category>
		<category><![CDATA[链接器]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=2955</guid>
		<description><![CDATA[Go 1.15版本在8月12日就正式发布了，给我的感觉就是发布的挺痛快^_^。这种感觉来自与之前版本发布时间的对比：Go 1.13版本发布于当年的9月4日，更早的Go 1.11版本发布于当年的8月25日。 不过这个时间恰与我家二宝出生和老婆月子时期有重叠，每天照顾孩子团团转的我实在抽不出时间研究Go 1.15的变化:(。如今，我逐渐从照顾二宝的工作中脱离出来^_^，于是“Go x.xx版本值得关注的几个变化”系列将继续下去。关注Go语言的演变对掌握和精通Go语言大有裨益，凡是致力于成为一名高级Gopher的读者都应该密切关注Go的演进。 截至写稿时，Go 1.15最新版是Go 1.15.2。Go 1.15一如既往的遵循Go1兼容性承诺。语言规范方面没有任何变化。可以说这是一个“面子”上变化较小的一个版本，但“里子”的变化还是不少的，在本文中我就和各位读者一起就重要变化逐一了解一下。 一. 平台移植性 Go 1.15版本不再对darwin/386和darwin/arm两个32位平台提供支持了。Go 1.15及以后版本仅对darwin/amd64和darwin/arm64版本提供支持。并且不再对macOS 10.12版本之前的版本提供支持。 Go 1.14版本中，Go编译器在被传入-race和-msan的情况下，默认会执行-d=checkptr，即对unsafe.Pointer的使用进行合法性检查。-d=checkptr主要检查两项内容： 当将unsafe.Pointer转型为&#42;T时，T的内存对齐系数不能高于原地址的； 做完指针算术后，转换后的unsafe.Pointer仍应指向原先Go堆对象 但在Go 1.14中，这个检查并不适用于Windows操作系统。Go 1.15中增加了对windows系统的支持。 对于RISC-V架构，Go社区展现出十分积极的姿态，早在Go 1.11版本，Go就为RISC-V cpu架构预留了GOARCH值：riscv和riscv64。Go 1.14版本则为64bit RISC-V提供了在linux上的实验性支持(GOOS=linux, GOARCH=riscv64)。在Go 1.15版本中，Go在GOOS=linux, GOARCH=riscv64的环境下的稳定性和性能得到持续提升，并且已经可以支持goroutine异步抢占式调度了。 二. 工具链 1. GOPROXY新增以管道符为分隔符的代理列表值 在Go 1.13版本中，GOPROXY支持设置为多个proxy的列表，多个proxy之间采用逗号分隔。Go工具链会按顺序尝试列表中的proxy以获取依赖包数据，但是当有proxy server服务不可达或者是返回的http状态码不是404也不是410时，go会终止数据获取。但是当列表中的proxy server返回其他错误时，Go命令不会向GOPROXY列表中的下一个值所代表的的proxy server发起请求，这种行为模式没能让所有gopher满意，很多Gopher认为Go工具链应该向后面的proxy server请求，直到所有proxy server都返回失败。Go 1.15版本满足了Go社区的需求，新增以管道符“&#124;”为分隔符的代理列表值。如果GOPROXY配置的proxy server列表值以管道符分隔，则无论某个proxy server返回什么错误码，Go命令都会向列表中的下一个proxy server发起新的尝试请求。 注：Go 1.15版本中GOPROXY环境变量的默认值依旧为https://proxy.golang.org,direct。 2. module cache的存储路径可设置 Go module机制自打在Go 1.11版本中以试验特性的方式引入时就将module的本地缓存默认放在了\$GOPATH/pkg/mod下（如果没有显式设置GOPATH，那么默认值将是~/go；如果GOPATH下面配置了多个路径，那么选择第一个路径），一直到Go [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/go-1.15-1.png" alt="img{512x368}" /></p>
<p><a href="https://tip.golang.org/doc/go1.15">Go 1.15版本</a>在8月12日就正式发布了，给我的感觉就是发布的挺痛快^_^。这种感觉来自与之前版本发布时间的对比：<a href="https://tonybai.com/2019/10/27/some-changes-in-go-1-13/">Go 1.13版本</a>发布于当年的9月4日，更早的<a href="https://tonybai.com/2018/11/19/some-changes-in-go-1-11/">Go 1.11版本</a>发布于当年的8月25日。</p>
<p>不过这个时间恰与我家<a href="https://tonybai.com/2020/07/29/my-second-daughter-was-born/">二宝出生</a>和老婆月子时期有重叠，每天照顾孩子团团转的我实在抽不出时间研究Go 1.15的变化:(。如今，我逐渐从照顾二宝的工作中脱离出来^_^，于是“Go x.xx版本值得关注的几个变化”系列将继续下去。关注Go语言的演变对掌握和精通Go语言大有裨益，凡是致力于成为一名高级Gopher的读者都应该密切关注Go的演进。<br />
截至写稿时，Go 1.15最新版是Go 1.15.2。Go 1.15一如既往的遵循<a href="https://tip.golang.org/doc/go1compat.html">Go1兼容性承诺</a>。<a href="https://tip.golang.org/ref/spec">语言规范</a>方面没有任何变化。可以说这是一个“面子”上变化较小的一个版本，但“里子”的变化还是不少的，在本文中我就和各位读者一起就重要变化逐一了解一下。</p>
<h3>一. 平台移植性</h3>
<p>Go 1.15版本不再对darwin/386和darwin/arm两个32位平台提供支持了。Go 1.15及以后版本仅对darwin/amd64和darwin/arm64版本提供支持。并且不再对macOS 10.12版本之前的版本提供支持。</p>
<p><a href="https://tonybai.com/2020/03/08/some-changes-in-go-1-14/">Go 1.14版本</a>中，Go编译器在被传入-race和-msan的情况下，默认会执行<strong>-d=checkptr</strong>，即对unsafe.Pointer的使用进行<a href="https://github.com/golang/go/issues/34964">合法性检查</a>。<strong>-d=checkptr</strong>主要检查两项内容：</p>
<ul>
<li>
<p>当将unsafe.Pointer转型为&#42;T时，T的内存对齐系数不能高于原地址的；</p>
</li>
<li>
<p>做完指针算术后，转换后的unsafe.Pointer仍应指向原先Go堆对象</p>
</li>
</ul>
<p>但在Go 1.14中，这个检查并不适用于Windows操作系统。Go 1.15中增加了对windows系统的支持。</p>
<p>对于<a href="https://riscv.org">RISC-V</a>架构，Go社区展现出十分积极的姿态，早在<a href="https://tonybai.com/2018/11/19/some-changes-in-go-1-11/">Go 1.11版本</a>，Go就为RISC-V cpu架构预留了GOARCH值：riscv和riscv64。<a href="https://tonybai.com/2020/03/08/some-changes-in-go-1-14/">Go 1.14版本</a>则为64bit RISC-V提供了在linux上的实验性支持(GOOS=linux, GOARCH=riscv64)。在Go 1.15版本中，Go在GOOS=linux, GOARCH=riscv64的环境下的稳定性和性能得到持续提升，并且已经可以支持goroutine异步抢占式调度了。</p>
<h3>二. 工具链</h3>
<h4>1. GOPROXY新增以管道符为分隔符的代理列表值</h4>
<p>在<a href="https://tonybai.com/2019/10/27/some-changes-in-go-1-13/">Go 1.13版本</a>中，<a href="https://tonybai.com/2018/11/26/hello-go-module-proxy/">GOPROXY</a>支持设置为多个proxy的列表，多个proxy之间采用逗号分隔。Go工具链会按顺序尝试列表中的proxy以获取依赖包数据，但是当有proxy server服务不可达或者是返回的http状态码不是404也不是410时，go会终止数据获取。但是当列表中的proxy server返回其他错误时，Go命令不会向GOPROXY列表中的下一个值所代表的的proxy server发起请求，这种行为模式没能让所有gopher满意，<strong>很多Gopher认为Go工具链应该向后面的proxy server请求，直到所有proxy server都返回失败</strong>。Go 1.15版本满足了Go社区的需求，新增以管道符“|”为分隔符的代理列表值。如果GOPROXY配置的proxy server列表值以管道符分隔，则无论某个proxy server返回什么错误码，Go命令都会向列表中的下一个proxy server发起新的尝试请求。</p>
<blockquote>
<p>注：Go 1.15版本中GOPROXY环境变量的默认值依旧为<strong>https://proxy.golang.org,direct</strong>。</p>
</blockquote>
<h4>2. module cache的存储路径可设置</h4>
<p><a href="https://tonybai.com/2019/06/03/the-practice-of-upgrading-major-version-under-go-module/">Go module机制</a>自打在<a href="https://tonybai.com/2018/11/19/some-changes-in-go-1-11/">Go 1.11版本</a>中以试验特性的方式引入时就将module的本地缓存默认放在了<strong>\$GOPATH/pkg/mod</strong>下（如果没有显式设置GOPATH，那么默认值将是<strong>~/go</strong>；如果GOPATH下面配置了多个路径，那么选择第一个路径），一直到Go 1.14版本，这个位置都是无法配置的。</p>
<p>Go module的引入为去除GOPATH提供了前提，于是module cache的位置也要尽量与GOPATH“脱钩”：Go 1.15提供了GOMODCACHE环境变量用于自定义module cache的存放位置。如果没有显式设置GOMODCACHE，那么module cache的默认存储路径依然是<strong>\$GOPATH/pkg/mod</strong>。</p>
<h3>三. 运行时、编译器和链接器</h3>
<h4>1. panic展现形式变化</h4>
<p>在Go 1.15之前，如果传给panic的值是bool, complex64, complex128, float32, float64, int, int8, int16, int32, int64, string, uint, uint8, uint16, uint32, uint64, uintptr等原生类型的值，那么panic在触发时会输出具体的值，比如：</p>
<pre><code>// go1.15-examples/runtime/panic.go

package main

func foo() {
    var i uint32 = 17
    panic(i)
}

func main() {
    foo()
}
</code></pre>
<p>使用Go 1.14运行上述代码，得到如下结果：</p>
<pre><code>$go run panic.go
panic: 17

goroutine 1 [running]:
main.foo(...)
    /Users/tonybai/go/src/github.com/bigwhite/experiments/go1.15-examples/runtime/panic.go:5
main.main()
    /Users/tonybai/go/src/github.com/bigwhite/experiments/go1.15-examples/runtime/panic.go:9 +0x39
exit status 2
</code></pre>
<p>Go 1.15版本亦是如此。但是对于派生于上述原生类型的自定义类型而言，Go 1.14只是输出变量地址：</p>
<pre><code>// go1.15-examples/runtime/panic.go

package main

type myint uint32

func bar() {
    var i myint = 27
    panic(i)
}

func main() {
    bar()
}
</code></pre>
<p>使用Go 1.14运行上述代码：</p>
<pre><code>$go run panic.go
panic: (main.myint) (0x105fca0,0xc00008e000)

goroutine 1 [running]:
main.bar(...)
    /Users/tonybai/go/src/github.com/bigwhite/experiments/go1.15-examples/runtime/panic.go:12
main.main()
    /Users/tonybai/go/src/github.com/bigwhite/experiments/go1.15-examples/runtime/panic.go:17 +0x39
exit status 2

</code></pre>
<p>Go 1.15针对此情况作了展示优化，即便是派生于这些原生类型的自定义类型变量，panic也可以输出其值。使用Go 1.15运行上述代码的结果如下：</p>
<pre><code>$go run panic.go
panic: main.myint(27)

goroutine 1 [running]:
main.bar(...)
    /Users/tonybai/go/src/github.com/bigwhite/experiments/go1.15-examples/runtime/panic.go:12
main.main()
    /Users/tonybai/go/src/github.com/bigwhite/experiments/go1.15-examples/runtime/panic.go:17 +0x39
exit status 2

</code></pre>
<h4>2. 将小整数([0,255])转换为interface类型值时将不会额外分配内存</h4>
<p>Go 1.15在runtime/iface.go中做了一些优化改动：增加一个名为staticuint64s的数组，预先为[0,255]这256个数分配了内存。然后在convT16、convT32等运行时转换函数中判断要转换的整型值是否小于256(len(staticuint64s))，如果小于，则返回staticuint64s数组中对应的值的地址；否则调用mallocgc分配新内存。</p>
<pre><code>$GOROOT/src/runtime/iface.go

// staticuint64s is used to avoid allocating in convTx for small integer values.
var staticuint64s = [...]uint64{
        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
        0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
        0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
        0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
        0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
        0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,

        ... ...

        0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
        0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,

}

func convT16(val uint16) (x unsafe.Pointer) {
        if val &lt; uint16(len(staticuint64s)) {
                x = unsafe.Pointer(&amp;staticuint64s[val])
                if sys.BigEndian {
                        x = add(x, 6)
                }
        } else {
                x = mallocgc(2, uint16Type, false)
                *(*uint16)(x) = val
        }
        return
}

func convT32(val uint32) (x unsafe.Pointer) {
        if val &lt; uint32(len(staticuint64s)) {
                x = unsafe.Pointer(&amp;staticuint64s[val])
                if sys.BigEndian {
                        x = add(x, 4)
                }
        } else {
                x = mallocgc(4, uint32Type, false)
                *(*uint32)(x) = val
        }
        return
}

</code></pre>
<p>我们可以用下面例子来验证一下：</p>
<pre><code>// go1.15-examples/runtime/tinyint2interface.go

package main

import (
    "math/rand"
)

func convertSmallInteger() interface{} {
    i := rand.Intn(256)
    var j interface{} = i
    return j
}

func main() {
    for i := 0; i &lt; 100000000; i++ {
        convertSmallInteger()
    }
}

</code></pre>
<p>我们分别用go 1.14和go 1.15.2编译这个源文件（注意关闭内联等优化，否则很可能看不出效果）：</p>
<pre><code>// go 1.14

go build  -gcflags="-N -l" -o tinyint2interface-go14 tinyint2interface.go

// go 1.15.2

go build  -gcflags="-N -l" -o tinyint2interface-go15 tinyint2interface.go

</code></pre>
<p>我们使用下面命令输出程序执行时每次GC的信息：</p>
<pre><code>$env GODEBUG=gctrace=1 ./tinyint2interface-go14
gc 1 @0.025s 0%: 0.009+0.18+0.021 ms clock, 0.079+0.079/0/0.20+0.17 ms cpu, 4-&gt;4-&gt;0 MB, 5 MB goal, 8 P
gc 2 @0.047s 0%: 0.003+0.14+0.013 ms clock, 0.031+0.099/0.064/0.037+0.10 ms cpu, 4-&gt;4-&gt;0 MB, 5 MB goal, 8 P
gc 3 @0.064s 0%: 0.008+0.20+0.016 ms clock, 0.071+0.071/0.018/0.081+0.13 ms cpu, 4-&gt;4-&gt;0 MB, 5 MB goal, 8 P
gc 4 @0.081s 0%: 0.005+0.14+0.013 ms clock, 0.047+0.059/0.023/0.032+0.10 ms cpu, 4-&gt;4-&gt;0 MB, 5 MB goal, 8 P
gc 5 @0.098s 0%: 0.005+0.10+0.017 ms clock, 0.042+0.073/0.027/0.080+0.13 ms cpu, 4-&gt;4-&gt;0 MB, 5 MB goal, 8 P

... ...

gc 192 @3.264s 0%: 0.003+0.11+0.013 ms clock, 0.024+0.060/0.005/0.035+0.11 ms cpu, 4-&gt;4-&gt;0 MB, 5 MB goal, 8 P
gc 193 @3.281s 0%: 0.005+0.13+0.032 ms clock, 0.042+0.075/0.041/0.050+0.25 ms cpu, 4-&gt;4-&gt;0 MB, 5 MB goal, 8 P
gc 194 @3.298s 0%: 0.004+0.12+0.013 ms clock, 0.033+0.072/0.030/0.033+0.10 ms cpu, 4-&gt;4-&gt;0 MB, 5 MB goal, 8 P
gc 195 @3.315s 0%: 0.003+0.17+0.023 ms clock, 0.029+0.062/0.055/0.024+0.18 ms cpu, 4-&gt;4-&gt;0 MB, 5 MB goal, 8 P

$env GODEBUG=gctrace=1 ./tinyint2interface-go15
</code></pre>
<p>我们看到和go 1.14编译的程序不断分配内存，不断导致GC相比，go1.15.2没有输出GC信息，间接证实了小整数转interface变量值时不会触发内存分配。</p>
<h4>3. 加入更现代化的链接器(linker)</h4>
<p>一个新版的<a href="https://golang.org/s/better-linker">现代化linker</a>正在逐渐加入到Go中，Go 1.15是新版linker的起点。后续若干版本，linker优化会逐步加入进来。在Go 1.15中，对于大型项目，新链接器的性能要提高20%，内存占用减少30%。</p>
<h4>4. objdump支持输出GNU汇编语法</h4>
<p>go 1.15为objdump工具增加了-gnu选项，<strong>以在Go汇编的后面，辅助输出GNU汇编，便于对照</strong>：</p>
<pre><code>// go 1.14：

$go tool objdump -S tinyint2interface-go15|more
TEXT go.buildid(SB)

  0x1001000             ff20                    JMP 0(AX)
  0x1001002             476f                    OUTSD DS:0(SI), DX
  0x1001004             206275                  ANDB AH, 0x75(DX)
  0x1001007             696c642049443a20        IMULL $0x203a4449, 0x20(SP), BP
... ...

//go 1.15.2：

$go tool objdump  -S -gnu tinyint2interface-go15|more
TEXT go.buildid(SB)

  0x1001000             ff20                    JMP 0(AX)                            // jmpq *(%rax)           

  0x1001002             476f                    OUTSD DS:0(SI), DX                   // rex.RXB outsl %ds:(%rsi),(%dx)
  0x1001004             206275                  ANDB AH, 0x75(DX)                    // and %ah,0x75(%rdx)     

  0x1001007             696c642049443a20        IMULL $0x203a4449, 0x20(SP), BP      // imul $0x203a4449,0x20(%rsp,%riz,2),%ebp

... ...

</code></pre>
<h3>四. 标准库</h3>
<p>和以往发布的版本一样，标准库有大量小改动，这里挑出几个笔者感兴趣的和大家一起看一下。</p>
<h4>1. 增加tzdata包</h4>
<p>Go time包中很多方法依赖时区数据，但不是所有平台上都自带时区数据。Go time包会以下面顺序搜寻时区数据：</p>
<pre><code>- ZONEINFO环境变量指示的路径中

- 在类Unix系统中一些常见的存放时区数据的路径（zoneinfo_unix.go中的zoneSources数组变量中存放这些常见路径）：

    "/usr/share/zoneinfo/",
    "/usr/share/lib/zoneinfo/",
    "/usr/lib/locale/TZ/"

- 如果平台没有，则尝试使用$GOROOT/lib/time/zoneinfo.zip这个随着go发布包一起发布的时区数据。但在应用部署的环境中，很大可能不会进行go安装。

</code></pre>
<p>如果go应用找不到时区数据，那么go应用运行将会受到影响，就如下面这个例子：</p>
<pre><code>// go1.15-examples/stdlib/tzdata.go

package main

import (
    "fmt"
    "time"
)

func main() {
    loc, err := time.LoadLocation("America/New_York")
    if err != nil {
        fmt.Println("LoadLocation error:", err)
        return
    }
    fmt.Println("LoadLocation is:", loc)
}

</code></pre>
<p>我们移除系统的时区数据(比如将/usr/share/zoneinfo改名)和Go安装包自带的zoneinfo.zip(改个名)后，在Go 1.15.2下运行该示例：</p>
<pre><code>$ go run tzdata.go
LoadLocation error: unknown time zone America/New_York

</code></pre>
<p>为此，Go 1.15提供了一个将时区数据嵌入到Go应用二进制文件中的方法：<strong>导入time/tzdata包</strong>：</p>
<pre><code>// go1.15-examples/stdlib/tzdata.go

package main

import (
    "fmt"
    "time"
    _ "time/tzdata"
)

func main() {
    loc, err := time.LoadLocation("America/New_York")
    if err != nil {
        fmt.Println("LoadLocation error:", err)
        return
    }
    fmt.Println("LoadLocation is:", loc)
}

</code></pre>
<p>我们再用go 1.15.2运行一下上述导入tzdata包的例子：</p>
<pre><code>$go run testtimezone.go
LoadLocation is: America/New_York

</code></pre>
<p>不过由于附带tzdata数据，应用二进制文件的size会增大大约800k，下面是在ubuntu下的实测值：</p>
<pre><code>-rwxr-xr-x 1 root root 2.0M Oct 11 02:42 tzdata-withouttzdata*
-rwxr-xr-x 1 root root 2.8M Oct 11 02:42 tzdata-withtzdata*

</code></pre>
<h4>2. 增加json解码限制</h4>
<p>json包是日常使用最多的go标准库包之一，在Go 1.15中，go按照json规范的要求，为json的解码增加了一层限制：</p>
<pre><code>// json规范要求

//https://tools.ietf.org/html/rfc7159#section-9

A JSON parser transforms a JSON text into another representation.  A
   JSON parser MUST accept all texts that conform to the JSON grammar.
   A JSON parser MAY accept non-JSON forms or extensions.

   An implementation may set limits on the size of texts that it
   accepts.  An implementation may set limits on the maximum depth of
   nesting.  An implementation may set limits on the range and precision
   of numbers.  An implementation may set limits on the length and
   character contents of strings.

</code></pre>
<p>这个限制就是增加了一个对json文本最大缩进深度值：</p>
<pre><code>// $GOROOT/src/encoding/json/scanner.go

// This limits the max nesting depth to prevent stack overflow.
// This is permitted by https://tools.ietf.org/html/rfc7159#section-9
const maxNestingDepth = 10000
</code></pre>
<p>如果一旦传入的json文本数据缩进深度超过maxNestingDepth，那json包就会panic。当然，绝大多数情况下，我们是碰不到缩进10000层的超大json文本的。因此，该limit对于99.9999%的gopher都没啥影响。</p>
<h4>3. reflect包</h4>
<p>Go 1.15版本之前reflect包<a href="https://github.com/golang/go/issues/38521">存在一处行为不一致的问题</a>，我们看下面例子(例子来源于https://play.golang.org/p/Jnga2_6Rmdf)：</p>
<pre><code>// go1.15-examples/stdlib/reflect.go

package main

import "reflect"

type u struct{}

func (u) M() { println("M") }

type t struct {
    u
    u2 u
}

func call(v reflect.Value) {
    defer func() {
        if err := recover(); err != nil {
            println(err.(string))
        }
    }()
    v.Method(0).Call(nil)
}

func main() {
    v := reflect.ValueOf(t{}) // v := t{}
    call(v)                   // v.M()
    call(v.Field(0))          // v.u.M()
    call(v.Field(1))          // v.u2.M()
}

</code></pre>
<p>我们使用Go 1.14版本运行该示例：</p>
<pre><code>$go run reflect.go
M
M
reflect: reflect.flag.mustBeExported using value obtained using unexported field

</code></pre>
<p>我们看到同为类型t中的非导出字段(field)的u和u2(u是以嵌入类型方式称为类型t的字段的)，通过reflect包可以调用字段u的导出方法(如输出中的第二行的M)，却无法调用非导出字段u2的导出方法（如输出中的第三行的panic信息）。</p>
<p>这种不一致在Go 1.15版本中被修复，我们使用Go 1.15.2运行上述示例：</p>
<pre><code>$go run reflect.go
M
reflect: reflect.Value.Call using value obtained using unexported field
reflect: reflect.Value.Call using value obtained using unexported field

</code></pre>
<p>我们看到reflect无法调用非导出字段u和u2的导出方法了。但是reflect依然可以通过提升到类型t的方法来间接使用u的导出方法，正如运行结果中的第一行输出。<br />
<strong>这一改动可能会影响到遗留代码中使用reflect调用以类型嵌入形式存在的非导出字段方法的代码</strong>，如果你的代码中存在这样的问题，可以直接通过提升(promote)到包裹类型(如例子中的t)中的方法（如例子中的call(v)）来替代之前的方式。</p>
<h3>五. 小结</h3>
<p>由于Go 1.15删除了一些GC元数据和一些无用的类型元数据，Go 1.15编译出的二进制文件size会减少5%左右。我用一个中等规模的go项目实测了一下：</p>
<pre><code>-rwxr-xr-x   1 tonybai  staff    23M 10 10 16:54 yunxind*
-rwxr-xr-x   1 tonybai  staff    24M  9 30 11:20 yunxind-go14*

</code></pre>
<p>二进制文件size的确有变小，大约4%-5%。</p>
<p><strong>如果你还没有升级到Go 1.15，那么现在正是时候</strong>。</p>
<p>本文中涉及的代码可以在<a href="https://github.com/bigwhite/experiments/tree/master/go1.15-examples">这里</a>下载。https://github.com/bigwhite/experiments/tree/master/go1.15-examples</p>
<hr />
<p>我的Go技术专栏：“<a href="https://www.imooc.com/read/87">改善Go语⾔编程质量的50个有效实践</a>”上线了，欢迎大家订阅学习！</p>
<p>我的网课“<a href="https://coding.imooc.com/class/284.html">Kubernetes实战：高可用集群搭建、配置、运维与应用</a>”在慕课网上线了，感谢小伙伴们学习支持！</p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/<br />
smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。</p>
<p>2020年4月8日，中国三大电信运营商联合发布《5G消息白皮书》，51短信平台也会全新升级到“51商用消息平台”，全面支持5G RCS消息。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻)归档仓库 &#8211; https://github.com/bigwhite/gopherdaily</p>
<p>我的联系方式：</p>
<p>微博：https://weibo.com/bigwhite20xx<br />
微信公众号：iamtonybai<br />
博客：tonybai.com<br />
github: https://github.com/bigwhite</p>
<p>微信赞赏：<br />
<img src="https://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2020, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2020/10/11/some-changes-in-go-1-15/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Golang跨平台交叉编译</title>
		<link>https://tonybai.com/2014/10/20/cross-compilation-with-golang/</link>
		<comments>https://tonybai.com/2014/10/20/cross-compilation-with-golang/#comments</comments>
		<pubDate>Mon, 20 Oct 2014 08:40:52 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[6g]]></category>
		<category><![CDATA[6l]]></category>
		<category><![CDATA[Build]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Cgo]]></category>
		<category><![CDATA[Compile]]></category>
		<category><![CDATA[Compiler]]></category>
		<category><![CDATA[Darwin]]></category>
		<category><![CDATA[GCC]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[GOARCH]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GOOS]]></category>
		<category><![CDATA[GOROOT]]></category>
		<category><![CDATA[linker]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[编译器]]></category>
		<category><![CDATA[链接器]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1571</guid>
		<description><![CDATA[近期在某本书上看到Go跨平台交叉编译的强大功能，于是想自己测试一下。以下记录了测试过程以及一些结论，希望能给大家带来帮助。 我的Linux环境如下： uname -a Linux ubuntu-Server-14 3.13.0-32-generic #57-Ubuntu SMP Tue Jul 15 03:51:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux $ go version go version go1.3.1 linux/amd64 跨平台交叉编译涉及两个重要的环境变量：GOOS和GOARCH，分别代表Target Host OS和Target Host ARCH，如果没有显式设置这些环境变量，我们通过go env可以看到go编译器眼中这两个环境变量的当前值： $ go env GOARCH=&#34;amd64&#34; GOOS=&#34;linux&#34; GOHOSTARCH=&#34;amd64&#34; GOHOSTOS=&#34;linux&#34; &#8230; &#8230; 这里还有两个变量GOHOSTOS和GOHOSTARCH，分别表示的是当前所在主机的的OS和CPU ARCH。我的Go是采用安装包安装的，因此默认情况下，这两组环境变量的值都是来自当前主机的信息。 现在我们就来交叉编译一下：在linux/amd64平台下利用Go编译器编译一个可以运行在linux/amd64下的程序，样例程序如下： //testport.go package main import ( &#160;&#160;&#160;&#160;&#160;&#160;&#160; &#34;fmt&#34; &#160;&#160;&#160;&#160;&#160;&#160;&#160; &#34;os/exec&#34; &#160;&#160;&#160;&#160;&#160;&#160;&#160; &#34;bytes&#34; [...]]]></description>
			<content:encoded><![CDATA[<p>近期在某本书上看到<a href="http://golang.org">Go</a>跨平台交叉编译的强大功能，于是想自己测试一下。以下记录了测试过程以及一些结论，希望能给大家带来帮助。</p>
<p>我的Linux环境如下：</p>
<p><font face="Courier New">uname -a<br />
	Linux ubuntu-Server-14 3.13.0-32-generic #57-Ubuntu SMP Tue Jul 15 03:51:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux</font></p>
<p><font face="Courier New">$ go version<br />
	go version go1.3.1 linux/amd64</font></p>
<p>跨平台交叉编译涉及两个重要的环境变量：GOOS和GOARCH，分别代表Target Host OS和Target Host ARCH，如果没有显式设置这些环境变量，我们通过go env可以看到go编译器眼中这两个环境变量的当前值：</p>
<p><font face="Courier New">$ go env<br />
	GOARCH=&quot;amd64&quot;<br />
	GOOS=&quot;linux&quot;</font><br />
	<font face="Courier New">GOHOSTARCH=&quot;amd64&quot;<br />
	GOHOSTOS=&quot;linux&quot;</font><br />
	&#8230; &#8230;</p>
<p>这里还有两个变量GOHOSTOS和GOHOSTARCH，分别表示的是当前所在主机的的OS和CPU ARCH。我的Go是采用安装包安装的，因此默认情况下，这两组环境变量的值都是来自当前主机的信息。</p>
<p>现在我们就来交叉编译一下：在linux/amd64平台下利用Go编译器编译一个可以运行在linux/amd64下的程序，样例程序如下：</p>
<p><font face="Courier New">//testport.go<br />
	package main</font></p>
<p><font face="Courier New">import (<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;fmt&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;os/exec&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;bytes&quot;<br />
	)</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cmd := exec.Command(&quot;uname&quot;, &quot;-a&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var out bytes.Buffer<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cmd.Stdout = &amp;out</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; err := cmd.Run()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Err when executing uname command&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;I am running on&quot;, out.String())<br />
	}</font></p>
<p>在Linux/amd64下编译运行：</p>
<p><font face="Courier New">$ go build -o testport_linux testport.go<br />
	$ testport_linux<br />
	I am running on Linux ubuntu-Server-14 3.13.0-32-generic #57-Ubuntu SMP Tue Jul 15 03:51:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux</font></p>
<p>接下来，我们来尝试在Linux/amd64上编译一个可以运行在darwin/amd64上的程序。我只需修改GOOS和GOARCH两个标识目标主机OS和ARCH的环境变量：</p>
<p><font face="Courier New">$ GOOS=darwin GOARCH=amd64 go build -o testport_darwin testport.go<br />
	go build runtime: darwin/amd64 must be bootstrapped using make.bash</font></p>
<p>编译器报错了！提示darwin/amd64必须通过make.bash重新装载。显然，通过安装包安装到linux/amd64下的Go编译器还无法直接交叉编译出darwin/amd64下可以运行的程序，我们需要做一些准备工作。我们找找make.bash在哪里！</p>
<p>我们到Go的$GOROOT路径下去找make.bash，Go的安装路径下的组织很简约，扫一眼便知make.sh大概在$GOROOT/src下，打开make.sh，我们在文件头处看到如下一些内容：</p>
<p><font face="Courier New"># Environment variables that control make.bash:<br />
	#<br />
	# GOROOT_FINAL: The expected final Go root, baked into binaries.<br />
	# The default is the location of the Go tree during the build.<br />
	#<br />
	# GOHOSTARCH: The architecture for host tools (compilers and<br />
	# binaries).&nbsp; Binaries of this type must be executable on the current<br />
	# system, so the only common reason to set this is to set<br />
	# GOHOSTARCH=386 on an amd64 machine.<br />
	#<br />
	# GOARCH: The target architecture for installed packages and tools.<br />
	#<br />
	# GOOS: The target operating system for installed packages and tools.</font><br />
	&#8230; &#8230;</p>
<p>make.bash头并未简要说明文件的用途，但名为make.xx的文件想必是用来构建Go编译工具的。这里提到几个环境变量可以控制 make.bash的行为，显然GOARCH和GOOS更能引起我们的兴趣。我们再回过头来输出testport.go编译过程的详细信息：</p>
<p><font face="Courier New">$ go build -x -o testport_linux testport.go<br />
	WORK=/tmp/go-build286732099<br />
	mkdir -p $WORK/command-line-arguments/_obj/<br />
	cd /home/tonybai/Test/Go/porting<br />
	/usr/local/go/pkg/tool/linux_amd64/6g -o $WORK/command-line-arguments.a -trimpath $WORK -p command-line-arguments -complete -D _/home/tonybai/Test/Go/porting -I $WORK -pack ./testport.go<br />
	cd .<br />
	/usr/local/go/pkg/tool/linux_amd64/6l -o testport_linux -L $WORK -extld=gcc $WORK/command-line-arguments.a</font></p>
<p>我们发现Go实际上用的是$GOROOT/pkg/tool/linux_amd64下的6g（编译器）和6l（链接器）来完成整个编译过程的，看到6g 和6l所在目录名为linux_amd64，我们可以大胆猜测编译darwin/amd64 go程序应该使用的是$GOROOT/pkg/tool/darwin_amd64下的工具。不过在我在$GOROOT/pkg/tool下没有发现 darwin_amd64目录，也就是说我们通过安装包安装的Go仅自带了for linux_amd64的编译工具，要想交叉编译出for darwin_amd64的程序，我们需要通过make.bash来手工编译出这些工具。</p>
<p><font face="Courier New">tonybai@ubuntu-Server-14:/usr/local/go/pkg$ ls<br />
	linux_amd64&nbsp; linux_amd64_race&nbsp; obj&nbsp; tool</font></p>
<p><font face="Courier New">tonybai@ubuntu-Server-14:/usr/local/go/pkg/tool$ ls<br />
	linux_amd64</font></p>
<p>根据前面make.bash的用法说明，我们来尝试构建一下：</p>
<p><font face="Courier New">cd $GOROOT/src<br />
	sudo GOOS=darwin GOARCH=amd64 ./make.bash</font></p>
<p><font face="Courier New"># Building C bootstrap tool.<br />
	cmd/dist</font></p>
<p><font face="Courier New"># Building compilers and Go bootstrap tool for host, linux/amd64.<br />
	&#8230; &#8230;<br />
	cmd/cc<br />
	cmd/gc<br />
	cmd/6l<br />
	cmd/6a<br />
	cmd/6c<br />
	cmd/6g<br />
	pkg/runtime<br />
	&#8230; &#8230;<br />
	cmd/go<br />
	pkg/runtime (darwin/amd64)</font></p>
<p><font face="Courier New"># Building packages and commands for host, linux/amd64.<br />
	runtime<br />
	&#8230; &#8230;<br />
	text/scanner</font></p>
<p><font face="Courier New"># Building packages and commands for darwin/amd64.<br />
	runtime<br />
	errors<br />
	&#8230; &#8230;<br />
	testing/quick<br />
	text/scanner</font></p>
<p><font face="Courier New">&#8212;<br />
	Installed Go for darwin/amd64 in /usr/local/go<br />
	Installed commands in /usr/local/go/bin</font></p>
<p>编译后，我们再来试试编译for darwin_amd64的程序：</p>
<p><font face="Courier New">$ GOOS=darwin GOARCH=amd64 go build -x -o testport_darwin testport.go<br />
	WORK=/tmp/go-build972764136<br />
	mkdir -p $WORK/command-line-arguments/_obj/<br />
	cd /home/tonybai/Test/Go/porting<br />
	/usr/local/go/pkg/tool/linux_amd64/6g -o $WORK/command-line-arguments.a -trimpath $WORK -p command-line-arguments -complete -D _/home/tonybai/Test/Go/porting -I $WORK -pack ./testport.go<br />
	cd .<br />
	/usr/local/go/pkg/tool/linux_amd64/6l -o testport_darwin -L $WORK -extld=gcc $WORK/command-line-arguments.a</font></p>
<p>将文件copy到我的Mac Air下执行：</p>
<p><font face="Courier New">$chmod +x testport_darwin<br />
	$testport_darwin<br />
	I am running on Darwin TonydeMacBook-Air.local 13.1.0 Darwin Kernel Version 13.1.0: Thu Jan 16 19:40:37 PST 2014; root:xnu-2422.90.20~2/RELEASE_X86_64 x86_64</font></p>
<p>编译虽然成功了，但从-x输出的详细编译过程来看，Go编译连接使用的工具依旧是linux_amd64下的6g和6l，为什么没有使用darwin_amd64下的6g和6l呢？原来$GOROOT/pkg/tool/darwin_amd64下根本就没有6g和6l：</p>
<p><font face="Courier New">/usr/local/go/pkg/tool/darwin_amd64$ ls<br />
	addr2line&nbsp; cgo&nbsp; fix&nbsp; nm&nbsp; objdump&nbsp; pack&nbsp; yac</font>c</p>
<p>但查看一下<font face="Courier New">pkg/tool/linux_amd64/</font>下程序的更新时间：</p>
<p>/usr/local/go/pkg/tool/linux_amd64$ ls -l<br />
	<font face="Courier New">&#8230; &#8230;<br />
	-rwxr-xr-x 1 root root 2482877 10月 20 15:12 6g<br />
	-rwxr-xr-x 1 root root 1186445 10月 20 15:12 6l<br />
	&#8230; &#8230;</font></p>
<p>我们发现6g和6l都是被刚才的make.bash新编译出来的，我们可以得出结论：新6g和新6l目前既可以编译本地程序（linux/amd64)，也可以编译darwin/amd64下的程序了，例如重新编译testport_linux依旧ok：</p>
<p><font face="Courier New">$ go build -x -o testport_linux testport.go<br />
	WORK=/tmp/go-build636762567<br />
	mkdir -p $WORK/command-line-arguments/_obj/<br />
	cd /home/tonybai/Test/Go/porting<br />
	/usr/local/go/pkg/tool/linux_amd64/6g -o $WORK/command-line-arguments.a -trimpath $WORK -p command-line-arguments -complete -D _/home/tonybai/Test/Go/porting -I $WORK -pack ./testport.go<br />
	cd .<br />
	/usr/local/go/pkg/tool/linux_amd64/6l -o testport_linux -L $WORK -extld=gcc $WORK/command-line-arguments.a</font></p>
<p>如果我们还想给Go编译器加上交叉编译windows/amd64程序的功能，我们再执行一次make.bash：</p>
<p><font face="Courier New">sudo GOOS=windows GOARCH=amd64 ./make.bash</font></p>
<p>编译成功后，我们来编译一下Windows程序：</p>
<p><font face="Courier New">$ GOOS=windows GOARCH=amd64 go build -x -o testport_windows.exe testport.go<br />
	WORK=/tmp/go-build626615350<br />
	mkdir -p $WORK/command-line-arguments/_obj/<br />
	cd /home/tonybai/Test/Go/porting<br />
	/usr/local/go/pkg/tool/linux_amd64/6g -o $WORK/command-line-arguments.a -trimpath $WORK -p command-line-arguments -complete -D _/home/tonybai/Test/Go/porting -I $WORK -pack ./testport.go<br />
	cd .<br />
	/usr/local/go/pkg/tool/linux_amd64/6l -o testport_windows.exe -L $WORK -extld=gcc $WORK/command-line-arguments.a</font></p>
<p>把testport_windows.exe扔到Windows上执行，结果：</p>
<p><font face="Courier New">Err when executing uname command</font></p>
<p>显然Windows下没有uname命令，提示执行出错。</p>
<p>至此，我的Go编译器具备了在Linux下编译windows/amd64和darwin/amd64的能力。如果你还想增加其他平台的能力，就像上面那样操作执行make.bash即可。</p>
<p>如果在go源文件中有与C语言的交互代码，那么交叉编译功能是否还能奏效呢？毕竟C在各个平台上的运行库、链接库等都是不同的。我们先来看看这个例子，我们使用之前在《<a href="http://tonybai.com/2014/10/12/discussion-on-shared-mem-support-in-docker/">探讨docker容器对共享内存的支持情况</a>》一文中的一个例子：</p>
<p><font face="Courier New">//testport_cgoenabled.go<br />
	package main</font></p>
<p><font face="Courier New">//#include &lt;stdio.h&gt;<br />
	//#include &lt;sys/types.h&gt;<br />
	//#include &lt;sys/mman.h&gt;<br />
	//#include &lt;fcntl.h&gt;<br />
	//<br />
	//#define SHMSZ&nbsp;&nbsp;&nbsp;&nbsp; 27<br />
	//<br />
	//int shm_rd()<br />
	//{<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char c;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *shm = NULL;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *s = NULL;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int fd;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ((fd = open(&quot;./shm.txt&quot;, O_RDONLY)) == -1)&nbsp; {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	//<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; shm = (char*)mmap(shm, SHMSZ, PROT_READ, MAP_SHARED, fd, 0);<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!shm) {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -2;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	//<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(fd);<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s = shm;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int i = 0;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (i = 0; i &lt; SHMSZ &#8211; 1; i++) {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;%c &quot;, *(s + i));<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;\n&quot;);<br />
	//<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;<br />
	//}<br />
	import &quot;C&quot;</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i := C.shm_rd()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if i != 0 {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Mmap Share Memory Read Error:&quot;, i)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Mmap Share Memory Read Ok&quot;)<br />
	}</font></p>
<p>我们先编译出一个本地可运行的程序：</p>
<p><font face="Courier New">$ go build -x -o testport_cgoenabled_linux testport_cgoenabled.go<br />
	WORK=/tmp/go-build977176241<br />
	mkdir -p $WORK/command-line-arguments/_obj/<br />
	cd /home/tonybai/Test/Go/porting<br />
	CGO_LDFLAGS=&quot;-g&quot; &quot;-O2&quot; /usr/local/go/pkg/tool/linux_amd64/cgo -objdir $WORK/command-line-arguments/_obj/ &#8212; -I $WORK/command-line-arguments/_obj/ testport_cgoenabled.go<br />
	/usr/local/go/pkg/tool/linux_amd64/6c -F -V -w -trimpath $WORK -I $WORK/command-line-arguments/_obj/ -I /usr/local/go/pkg/linux_amd64 -o $WORK/command-line-arguments/_obj/_cgo_defun.6 -D GOOS_linux -D GOARCH_amd64 $WORK/command-line-arguments/_obj/_cgo_defun.c<br />
	gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -print-libgcc-file-name<br />
	gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -I $WORK/command-line-arguments/_obj/ -g -O2 -o $WORK/command-line-arguments/_obj/_cgo_main.o -c $WORK/command-line-arguments/_obj/_cgo_main.c<br />
	gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -I $WORK/command-line-arguments/_obj/ -g -O2 -o $WORK/command-line-arguments/_obj/_cgo_export.o -c $WORK/command-line-arguments/_obj/_cgo_export.c<br />
	gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -I $WORK/command-line-arguments/_obj/ -g -O2 -o $WORK/command-line-arguments/_obj/testport_cgoenabled.cgo2.o -c $WORK/command-line-arguments/_obj/testport_cgoenabled.cgo2.c<br />
	gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -o $WORK/command-line-arguments/_obj/_cgo_.o $WORK/command-line-arguments/_obj/_cgo_main.o $WORK/command-line-arguments/_obj/_cgo_export.o $WORK/command-line-arguments/_obj/testport_cgoenabled.cgo2.o -g -O2<br />
	/usr/local/go/pkg/tool/linux_amd64/cgo -objdir $WORK/command-line-arguments/_obj/ -dynimport $WORK/command-line-arguments/_obj/_cgo_.o -dynout $WORK/command-line-arguments/_obj/_cgo_import.c<br />
	/usr/local/go/pkg/tool/linux_amd64/6c -F -V -w -trimpath $WORK -I $WORK/command-line-arguments/_obj/ -I /usr/local/go/pkg/linux_amd64 -o $WORK/command-line-arguments/_obj/_cgo_import.6 -D GOOS_linux -D GOARCH_amd64 $WORK/command-line-arguments/_obj/_cgo_import.c<br />
	gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -o $WORK/command-line-arguments/_obj/_all.o $WORK/command-line-arguments/_obj/_cgo_export.o $WORK/command-line-arguments/_obj/testport_cgoenabled.cgo2.o -g -O2 -Wl,-r -nostdlib /usr/lib/gcc/x86_64-linux-gnu/4.8/libgcc.a<br />
	/usr/local/go/pkg/tool/linux_amd64/6g -o $WORK/command-line-arguments.a -trimpath $WORK -p command-line-arguments -D _/home/tonybai/Test/Go/porting -I $WORK -pack $WORK/command-line-arguments/_obj/_cgo_gotypes.go $WORK/command-line-arguments/_obj/testport_cgoenabled.cgo1.go<br />
	pack r $WORK/command-line-arguments.a $WORK/command-line-arguments/_obj/_cgo_import.6 $WORK/command-line-arguments/_obj/_cgo_defun.6 $WORK/command-line-arguments/_obj/_all.o # internal<br />
	cd .<br />
	/usr/local/go/pkg/tool/linux_amd64/6l -o testport_cgoenabled_linux -L $WORK -extld=gcc $WORK/command-line-arguments.a</font></p>
<p>输出了好多日志！不过可以看出Go编译器先调用CGO对Go源码中的C代码进行了编译，然后才是常规的Go编译，最后通过6l链接在一起。Cgo似乎直接使用了Gcc。我们再来试试跨平台编译：</p>
<p><font face="Courier New">$ GOOS=darwin GOARCH=amd64 go build -x -o testport_cgoenabled_darwin testport_cgoenabled.go<br />
	WORK=/tmp/go-build124869433<br />
	can&#39;t load package: no buildable Go source files in /home/tonybai/Test/Go/porting</font></p>
<p>当我们编译for Darwin/amd64平台的程序时，Go无法像之前那样的顺利完成编译，而是提示错误。从网上给出的资料来看，如果Go源码中包含C互操作代码，那么 目前依旧无法实现交叉编译，因为cgo会直接使用各个平台的本地c编译器去编译Go文件中的C代码。默认情况下，make.bash会置 CGO_ENABLED=0。</p>
<p>如果你非要将CGO_ENABLED设置为1去编译go的话，至少我得到了如下错误，导致无法编译通过：</p>
<p><font face="Courier New">$ sudo CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 ./make.bash &#8211;no-clean<br />
	&#8230; &#8230;<br />
	# Building packages and commands for darwin/amd64.<br />
	&#8230; &#8230;<br />
	37: error: &#39;AI_MASK&#39; undeclared (first use in this function)</font><br />
	&nbsp;</p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/10/20/cross-compilation-with-golang/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
