<?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; GOARCH</title>
	<atom:link href="http://tonybai.com/tag/goarch/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Fri, 01 May 2026 23:50:27 +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 考古：Go 官方如何决定支持你的 CPU 和 OS？</title>
		<link>https://tonybai.com/2026/01/01/go-archaeology-porting-policy/</link>
		<comments>https://tonybai.com/2026/01/01/go-archaeology-porting-policy/#comments</comments>
		<pubDate>Thu, 01 Jan 2026 05:16:40 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AlpineLinux]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[BlockReleases]]></category>
		<category><![CDATA[BrokenPorts]]></category>
		<category><![CDATA[builder]]></category>
		<category><![CDATA[CI]]></category>
		<category><![CDATA[FirstClassPorts]]></category>
		<category><![CDATA[Glibc]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[GOARCH]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GoogleGoTeam]]></category>
		<category><![CDATA[GOOS]]></category>
		<category><![CDATA[GoPortingPolicy]]></category>
		<category><![CDATA[musl]]></category>
		<category><![CDATA[OS]]></category>
		<category><![CDATA[port]]></category>
		<category><![CDATA[proposal]]></category>
		<category><![CDATA[runtime]]></category>
		<category><![CDATA[SecondaryPorts]]></category>
		<category><![CDATA[x/sys]]></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>
		<category><![CDATA[线程调度]]></category>
		<category><![CDATA[维护成本]]></category>
		<category><![CDATA[跨平台编译]]></category>
		<category><![CDATA[运行时]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5647</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/01/01/go-archaeology-porting-policy 大家好，我是Tony Bai。 当我们津津乐道于 Go 语言强大的跨平台编译能力——只需一个 GOOS=linux GOARCH=amd64 就能在 Mac 上编译出 Linux Go程序时，你是否想过，这些操作系统和 CPU 架构的组合（Port）是如何被选入 Go 核心代码库的？ 为什么 linux/amd64 稳如泰山，而 darwin/386 却消失在历史长河中？为什么新兴的 linux/riscv64 或 linux/loong64 能被接纳？ 这一切的背后，都遵循着一份严谨的 Go Porting Policy。今天，我们就来翻开这份“法典”，一探究竟。 什么是“Port”？ 在 Go 的语境下，一个 Port 指的是 操作系统 (OS) 与 处理器架构 (Architecture) 的特定组合。例如： linux/amd64：运行在 64 位 x86 处理器上的 Linux。 windows/arm64：运行在 ARM64 处理器上的 Windows。 每一个 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2026/go-archaeology-porting-policy-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/01/01/go-archaeology-porting-policy">本文永久链接</a> &#8211; https://tonybai.com/2026/01/01/go-archaeology-porting-policy</p>
<p>大家好，我是Tony Bai。</p>
<p>当我们津津乐道于 Go 语言强大的跨平台编译能力——只需一个 GOOS=linux GOARCH=amd64 就能在 Mac 上编译出 Linux Go程序时，你是否想过，这些操作系统和 CPU 架构的组合（Port）是如何被选入 Go 核心代码库的？</p>
<p>为什么 linux/amd64 稳如泰山，而 darwin/386 却消失在历史长河中？为什么新兴的 linux/riscv64 或 linux/loong64 能被接纳？</p>
<p>这一切的背后，都遵循着一份严谨的 <strong><a href="https://go.dev/wiki/PortingPolicy">Go Porting Policy</a></strong>。今天，我们就来翻开这份“法典”，一探究竟。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/system-programming-in-go-pr.png" alt="" /></p>
<h2>什么是“Port”？</h2>
<p>在 Go 的语境下，一个 <strong>Port</strong> 指的是 <strong>操作系统 (OS)</strong> 与 <strong>处理器架构 (Architecture)</strong> 的特定组合。例如：</p>
<ul>
<li>linux/amd64：运行在 64 位 x86 处理器上的 Linux。</li>
<li>windows/arm64：运行在 ARM64 处理器上的 Windows。</li>
</ul>
<p>每一个 Port 的引入，都意味着 Go 编译器后端需要生成对应的机器码，运行时（Runtime）需要处理特定的系统调用、内存管理和线程调度。这是一项巨大的工程。</p>
<h2>等级森严：First-Class Ports (一等公民)</h2>
<p>Go 官方将 Ports 分为两类，这并非歧视，而是基于<strong>稳定性承诺</strong>和<strong>维护成本</strong>的考量。</p>
<p><strong>First-Class Ports</strong> 是 Go 官方（Google Go Team）承诺全力支持的平台。它们享有最高级别的待遇，也承担着最重的责任：</p>
<ol>
<li><strong>阻断发布 (Block Releases)</strong>：如果任何一个 First-Class Port 的构建或测试失败，Go 的新版本（包括 Beta 和 RC）就<strong>绝对不会发布</strong>。</li>
<li><strong>官方兜底</strong>：Google 的 Go 团队负责维护这些平台的构建机器（Builder），并对任何破坏这些平台的代码变更负责。</li>
</ol>
<p>目前的 <strong>First-Class Ports</strong> 名单（极少，只有核心的几个）：<br />
*   linux/amd64, linux/386, linux/arm, linux/arm64<br />
*   darwin/amd64, darwin/arm64 (macOS)<br />
*   windows/amd64, windows/386</p>
<blockquote>
<p><strong>冷知识</strong>：Linux 下只有使用 glibc 的系统才算 First-Class。使用 musl (如 Alpine Linux) 的并不在这个名单里，虽然它们通常也能工作得很好。</p>
</blockquote>
<h2>社区的力量：Secondary Ports (次要组合)</h2>
<p>除了上述几个“亲儿子”，Go 支持的几十种其他平台（如 freebsd/*, openbsd/*, netbsd/*, aix/*, illumos/*, plan9/*, js/wasm 等）都属于 <strong>Secondary Ports</strong>。</p>
<p>它们的生存法则完全不同：</p>
<ol>
<li><strong>社区维护制</strong>：必须至少有<strong>两名</strong>活跃的社区开发者签名画押，承诺维护这个 Port。</li>
<li><strong>不阻碍发布</strong>：如果一个次要 Port 的构建挂了，Go 官方<strong>不会</strong>为了它推迟版本发布。它可能会在 Release Note 中被标记为“Broken”甚至“Unsupported”。</li>
<li><strong>自备干粮</strong>：维护者必须提供并维护构建机器，接入 Go 的 CI 系统。</li>
</ol>
<p>这意味着，如果你想让 Go 支持一个冷门的嵌入式系统，你不仅要贡献代码，还得长期确保持续集成（CI）是绿的。</p>
<h2>优胜劣汰：如何新增与移除？</h2>
<h3>新增一个 Port</h3>
<p>想让 Go 支持一个新的芯片架构（比如龙芯 LoongArch）？流程是严格的：</p>
<ol>
<li><strong>提交 Proposal</strong>：论证这个 Port 的价值（潜在用户量）与维护成本的平衡。</li>
<li><strong>找人</strong>：指定至少两名维护者。</li>
<li><strong>先行</strong>：可以在 x/sys 库中先行验证对新Port系统调用的支持，甚至在构建机器跑通之前，代码不能合入主分支。</li>
</ol>
<h3>移除一个 Port (Broken Ports)</h3>
<p>Go 不会无限制地背负历史包袱。一个 Port 如果满足以下条件，可能会被移除：</p>
<ul>
<li><strong>构建失败且无人修</strong>：如果一个 Secondary Port 长期构建失败，且维护者失联，它会被标记为 Broken。如果在下一个大版本（1.N+1）发布前还没修好，就会被移除。</li>
<li><strong>硬件消亡</strong>：如果硬件都停产了（例如 IBM POWER5），Go 也没必要支持了。</li>
<li><strong>厂商放弃</strong>：如果 OS 厂商都不支持了（例如老版本的 macOS），Go 也会跟随弃用。</li>
</ul>
<p>这就是为什么 Go 在某个版本后不再支持 Windows XP 或 macOS 10.12 的原因——<strong>为了让有限的开发资源聚焦在更广泛使用的系统上。</strong></p>
<h2>小结</h2>
<p>Go 的 Porting Policy 展示了一个成熟开源项目的治理智慧：<strong>核心聚焦，边界开放，权责对等</strong>。</p>
<p>它保证了 Go 在主流平台上的坚如磐石，同时也通过社区机制，让 Go 的触角延伸到了无数小众和新兴的领域。下次当你为一个冷门平台编译 Go 程序成功时，别忘了感谢那些默默维护 Builder 的社区志愿者们。</p>
<p>参考资料：https://go.dev/wiki/PortingPolicy</p>
<hr />
<p>还在为“复制粘贴喂AI”而烦恼？我的新专栏 <strong>《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》</strong> 将带你：</p>
<ul>
<li>告别低效，重塑开发范式</li>
<li>驾驭AI Agent(Claude Code)，实现工作流自动化</li>
<li>从“AI使用者”进化为规范驱动开发的“工作流指挥家”</li>
</ul>
<p>扫描下方二维码，开启你的AI原生开发之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/01/01/go-archaeology-porting-policy/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go包维护者必读：如何让你的Go包更易被发现、文档更专业？</title>
		<link>https://tonybai.com/2025/05/11/deep-into-pkg-go-dev/</link>
		<comments>https://tonybai.com/2025/05/11/deep-into-pkg-go-dev/#comments</comments>
		<pubDate>Sat, 10 May 2025 23:57:06 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Badge]]></category>
		<category><![CDATA[BuildContext]]></category>
		<category><![CDATA[doc]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go-get]]></category>
		<category><![CDATA[go.mod]]></category>
		<category><![CDATA[GOARCH]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[gomodule]]></category>
		<category><![CDATA[GoModuleProxy]]></category>
		<category><![CDATA[GOOS]]></category>
		<category><![CDATA[GOPROXY]]></category>
		<category><![CDATA[license]]></category>
		<category><![CDATA[links]]></category>
		<category><![CDATA[meta]]></category>
		<category><![CDATA[Package]]></category>
		<category><![CDATA[pkg.go.dev]]></category>
		<category><![CDATA[pkgsite]]></category>
		<category><![CDATA[protocol]]></category>
		<category><![CDATA[proxy.golang.org]]></category>
		<category><![CDATA[README]]></category>
		<category><![CDATA[retract]]></category>
		<category><![CDATA[wasm]]></category>
		<category><![CDATA[包]]></category>
		<category><![CDATA[许可证]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=4677</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/05/11/deep-into-pkg-go-dev 大家好，我是Tony Bai。 对于 Go 开发者而言，pkg.go.dev 不仅仅是一个查找包文档的网站，更是展示和推广自己辛勤成果的重要平台。理解其运作机制、掌握其使用技巧，并遵循其倡导的最佳实践，能显著提升你的 Go 包的专业度、可见性和社区友好度。本文将基于官方信息，和大家一起挖掘一下 pkg.go.dev 的宝藏知识，包括核心功能和关键建议。 让你的包“入住”pkg.go.dev pkg.go.dev 的数据来源于官方的 Go Module Proxy (proxy.golang.org)，并通过 Go Module Index (index.golang.org) 定期监测新的包版本。如果你的包尚未被收录，可以通过以下任一方式主动添加： 直接请求收录: 访问你的包在 pkg.go.dev 上对应的 URL (即使它显示“Not Found”)，例如 https://pkg.go.dev/example.com/my/module，然后点击页面上的 “Request” 按钮(如下图所示)。 触发 Proxy 请求: 向 proxy.golang.org 发送一个符合 Go Module Proxy 协议 的请求。例如，请求特定版本的 .info 文件： $curl https://proxy.golang.org/example.com/my/module/@v/v1.0.0.info 使用 go get 命令: 通过 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/deep-into-pkg-go-dev-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/05/11/deep-into-pkg-go-dev">本文永久链接</a> &#8211; https://tonybai.com/2025/05/11/deep-into-pkg-go-dev</p>
<p>大家好，我是Tony Bai。</p>
<p>对于 Go 开发者而言，pkg.go.dev 不仅仅是一个查找包文档的网站，更是展示和推广自己辛勤成果的重要平台。理解其运作机制、掌握其使用技巧，并遵循其倡导的最佳实践，能显著提升你的 Go 包的专业度、可见性和社区友好度。本文将基于官方信息，和大家一起挖掘一下 pkg.go.dev 的宝藏知识，包括核心功能和关键建议。</p>
<h2>让你的包“入住”pkg.go.dev</h2>
<p>pkg.go.dev 的数据来源于官方的 Go Module Proxy (proxy.golang.org)，并通过 Go Module Index (index.golang.org) 定期监测新的包版本。如果你的包尚未被收录，可以通过以下任一方式主动添加：</p>
<ul>
<li><strong>直接请求收录:</strong> 访问你的包在 pkg.go.dev 上对应的 URL (即使它显示“Not Found”)，例如 https://pkg.go.dev/example.com/my/module，然后点击页面上的 “Request” 按钮(如下图所示)。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/2025/deep-into-pkg-go-dev-2.png" alt="" /></p>
<ul>
<li><strong>触发 Proxy 请求:</strong> 向 proxy.golang.org 发送一个符合 <a href="https://pkg.go.dev/cmd/go/#hdr-Module_proxy_protocol">Go Module Proxy 协议</a> 的请求。例如，请求特定版本的 .info 文件：</li>
</ul>
<pre><code>$curl https://proxy.golang.org/example.com/my/module/@v/v1.0.0.info
</code></pre>
<ul>
<li><strong>使用 go get 命令:</strong> 通过 go get 命令下载你的包（确保 GOPROXY 指向官方代理），这也会触发代理获取该模块：</li>
</ul>
<pre><code class="bash">$GOPROXY=https://proxy.golang.org GO111MODULE=on go get example.com/my/module@v1.0.0
</code></pre>
<p>一旦 proxy.golang.org 索引了你的模块版本，pkg.go.dev 通常会在几分钟内获取并展示其文档。</p>
<h2>管理你的包版本：撤回不推荐的版本</h2>
<p>如果你希望从 pkg.go.dev 以及 go 命令的解析结果中隐藏某个模块的特定版本（例如，修复了严重 Bug 或安全漏洞后），应当使用 <strong>retract 指令</strong>。这需要在你的 go.mod 文件中添加 retract 指令，并发布一个新的模块版本。</p>
<pre><code>// go.mod
module example.com/my/module

go 1.18

retract (
    v1.0.0 // 解释为何撤回此版本
    [v1.0.1, v1.0.5] // 也可以撤回一个版本范围
)
</code></pre>
<p>详细信息请参考 Go 官方博客文章 <a href="https://go.dev/blog/go116-module-changes#module-retraction">New module changes in Go 1.16</a> 和 <a href="https://go.dev/ref/mod#go-mod-file-retract">modules reference</a>。</p>
<p><strong>关键点：</strong></p>
<ul>
<li>即使是最新版本也可以被撤回。</li>
<li>已发布的版本（包括被撤回的版本）无法被修改或重用。</li>
<li>如果源码仓库或域名已无法访问，导致无法通过发布新版本来撤回，可以向 pkgsite 团队<a href="https://go.dev/s/pkgsite-package-removal">提交请求</a>来隐藏所有版本文档。但请注意，这仅隐藏 pkg.go.dev 上的文档，模块本身仍可通过 go get 获取，除非它被正确撤回。</li>
</ul>
<h2>文档是如何生成的？</h2>
<p>pkg.go.dev 从 Go Module Mirror (proxy.golang.org/<module>/@v/<version>.zip) 下载 Go 源码，并基于源码中的注释生成文档。</p>
<ul>
<li><strong>遵循 godoc 指南:</strong> 编写文档时，应遵循为 godoc 工具制定的<a href="https://go.dev/blog/godoc">文档编写指南</a>。</li>
<li><strong>首句摘要至关重要:</strong> 包注释的第一句话应提供对包功能的良好总结。pkg.go.dev 会索引这句话并在搜索结果中显示它，直接影响用户对你包的第一印象。</li>
</ul>
<h3>理解 Build Context (构建上下文)</h3>
<p>Go 语言允许包在不同的操作系统 (GOOS) 和 CPU 架构 (GOARCH) 组合（称为“Build Context”，如 linux/amd64）下表现不同，甚至拥有不同的导出符号。</p>
<ul>
<li><strong>单一上下文:</strong> 如果包仅存在于一个 Build Context（如 syscall/js 仅用于 js/wasm），pkg.go.dev 会在文档右上角显示该上下文(如下图)。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/2025/deep-into-pkg-go-dev-3.png" alt="" /></p>
<ul>
<li><strong>多上下文差异:</strong> 如果包在不同上下文中存在差异，pkg.go.dev 会默认显示一个，并提供下拉菜单供用户切换查看其他支持的上下文(如下图)。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/2025/deep-into-pkg-go-dev-4.png" alt="" /></p>
<ul>
<li>
<p><strong>通用包:</strong> 对于在所有上下文中表现一致的包，则不显示上下文信息。</p>
</li>
<li>
<p><strong>支持范围:</strong> pkg.go.dev 仅考虑<a href="https://go.googlesource.com/pkgsite/+/master/internal/build_context.go#29">有限的一部分</a> Build Context。如果你的包仅存在于不受支持的上下文中，其文档可能不会显示。</p>
</li>
</ul>
<h3>源码链接：连接文档与定义</h3>
<p>pkg.go.dev 通常能自动检测包的源码位置，并在文档中提供从符号到其源码定义的链接。如果你的包源码链接未能正确显示，可以尝试：</p>
<ol>
<li><strong>go-source meta 标签:</strong> 在你的网站上添加符合<a href="https://github.com/golang/gddo/wiki/Source-Code-Links">特定格式</a>的 go-source meta 标签，这有助于 pkg.go.dev 解析源码位置（尽管该格式未考虑版本控制）。</li>
<li><strong>贡献模式:</strong> 如果上述方法无效，你需要将你的仓库或代码托管站点模式添加到 pkgsite 的配置中。参考<a href="https://go.googlesource.com/pkgsite#contributing">如何贡献 pkg.go.dev</a> 并提交一个 CL，向 <a href="https://go.googlesource.com/pkgsite/+/refs/heads/master/internal/source/source.go">internal/source</a> 包添加模式。</li>
</ol>
<h2>遵循最佳实践：提升你的包质量</h2>
<p>pkg.go.dev 会展示关于 Go 包和模块的一些关键细节，旨在推广社区的最佳实践。关注这些细节，能让你的包更受信任，更易于被其他开发者采用：</p>
<ul>
<li><strong>拥有 go.mod 文件:</strong> Go 模块系统是官方推荐的标准依赖管理方案。一个模块版本由其根目录下的 go.mod 文件定义。</li>
<li><strong>使用可再分发许可证 (Redistributable license):</strong> 这类许可证（如 MIT, Apache 2.0, BSD 等）对软件的使用、修改和再分发限制最小。pkg.go.dev 有其<a href="http://pkg.go.dev/license-policy">许可证策略</a>来判断许可证是否可再分发。</li>
<li><strong>打上版本标签 (Tagged version):</strong> go get 命令默认优先解析打了标签的版本 (遵循 <a href="https://semver.org/">Semantic Versioning</a>)。没有标签时，会查找最新的 commit。使用版本标签能为导入者提供更可预测的构建。参考 <a href="https://go.dev/blog/module-compatibility">Keeping Your Modules Compatible</a>。</li>
<li><strong>达到稳定版本 (Stable version):</strong> v0.x.y 版本的项目被认为是实验性的。当项目达到 v1.0.0 或更高版本时，即为稳定版本。这意味着后续的破坏性变更必须在新的主版本中进行（如 v2.0.0）。稳定版本给予开发者信心，在升级到最新的次要版本或修订版本时不会遇到破坏性变更。参考 <a href="https://go.dev/blog/v2-go-modules">Go Modules: v2 and Beyond</a>。</li>
</ul>
<h2>锦上添花：徽章、链接与快捷键</h2>
<ul>
<li><strong>创建徽章 (Badge):</strong> 使用<a href="https://pkg.go.dev/badge">徽章生成工具</a>为你的项目创建一个 pkg.go.dev 徽章，可以放置在 README 或项目网站上，方便用户快速访问你的包文档。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/2025/deep-into-pkg-go-dev-5.png" alt="" /></p>
<ul>
<li><strong>添加自定义链接:</strong> 你可以在 README 文件和包文档中添加自定义链接，这些链接会显示在 pkg.go.dev 页面上。下面是添加links的示例：</li>
</ul>
<pre><code># The Links Repo

This repo demonstrates pkgsite links.

## Links

- [pkg.go.dev](https://pkg.go.dev)
- [this file](README.md)

## How it works

Links are taken from a README heading named "Links".
</code></pre>
<p>展示的页面上的链接如下：</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/deep-into-pkg-go-dev-6.png" alt="" /></p>
<ul>
<li><strong>键盘快捷键:</strong> 在包文档页面输入 ? 可以查看可用的键盘快捷键，方便导航。</li>
</ul>
<h2>小结</h2>
<p>pkg.go.dev 是 Go 生态中连接包作者与使用者的重要桥梁。通过理解其运作方式，精心准备你的包（包括清晰的文档、规范的版本管理、合适的许可证以及遵循最佳实践），你的 Go 包将更容易被发现、理解和信赖。</p>
<hr />
<p><strong>提升Go包影响力，你有什么独门秘诀？</strong></p>
<p>pkg.go.dev 为我们提供了展示和推广Go包的官方平台。除了文中提到的这些技巧和最佳实践，<strong>你在维护和推广自己的Go包时，还有哪些特别的心得体会或踩过的“坑”？</strong> 比如，你是如何编写更吸引人的包描述？如何处理社区的Issue和PR？或者有什么让你的包在众多选择中脱颖而出的好方法？</p>
<p><strong>热烈欢迎在评论区分享你的宝贵经验，让我们共同打造更繁荣、更高质量的Go包生态！</strong></p>
<p>如果你不仅希望自己的Go包拥有专业的文档和良好的可见性，更渴望深入理解Go语言的设计哲学、掌握高级特性、提升项目工程化水平。</p>
<p>那么，我的 「Go &amp; AI 精进营」知识星球 将是你的理想伙伴！在这里，我们不仅探讨语言细节，更有【Go进阶课】、【Go原理课】等内容助你提升项目构建与维护能力。我会亲自为你解答Go开发中的各种疑难，你还能与众多优秀的Gopher交流思想、碰撞火花，共同探索Go在各个领域的最佳实践，包括如何更好地参与和贡献开源社区。</p>
<p>现在就扫码加入，与我们一起精进Go技能，让你的开源项目闪耀社区！ ✨</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-and-ai-tribe-zsxq-small-card.jpg" alt="img{512x368}" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/05/11/deep-into-pkg-go-dev/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go包构建：专家也未必了解的文件选择细节</title>
		<link>https://tonybai.com/2024/11/21/go-source-file-selection-details-when-building-package/</link>
		<comments>https://tonybai.com/2024/11/21/go-source-file-selection-details-when-building-package/#comments</comments>
		<pubDate>Wed, 20 Nov 2024 22:57:08 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[aix]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[BSD]]></category>
		<category><![CDATA[buildconstraints]]></category>
		<category><![CDATA[buildtag]]></category>
		<category><![CDATA[Cgo]]></category>
		<category><![CDATA[Darwin]]></category>
		<category><![CDATA[fmt]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go1.23]]></category>
		<category><![CDATA[GOARCH]]></category>
		<category><![CDATA[gobuild]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[golist]]></category>
		<category><![CDATA[gomodule]]></category>
		<category><![CDATA[GOOS]]></category>
		<category><![CDATA[Go语言精进之路]]></category>
		<category><![CDATA[illumos]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[JS]]></category>
		<category><![CDATA[Package]]></category>
		<category><![CDATA[ReleaseTags]]></category>
		<category><![CDATA[toolchain]]></category>
		<category><![CDATA[Unix]]></category>
		<category><![CDATA[wasm]]></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=4405</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2024/11/21/go-source-file-selection-details-when-building-package 在Go语言开发中，包（package）是代码组织的基本单位，也是基本的构建单元。Go编译器会将每个包构建成一个目标文件(.a)，然后通过链接器将这些目标文件链接在一起，形成最终的可执行程序。 尽管Go包的构建过程看似简单，但实际上蕴含着许多值得深入了解的细节。例如，当我们执行go build命令时，Go编译器是如何选择需要编译的源文件的？你可能会回答：“不就是通过文件名中的ARCH和OS标识以及构建约束（build constraints）来选择的吗？” 虽然你的答案并没有错，但如果我进一步提出以下问题，你是否还能给出确切的答案呢？ 假设一个Go源文件使用了如下的构建约束： //go:build unix package foo // ... ... 在执行GOOS=android go build时，这个文件是否会被编译？如果执行的是GOOS=aix go build呢？而“unix”究竟包含了哪些操作系统？ 再进一步，当一个源文件的文件名中包含ARCH和操作系统标识，并且文件内容中也使用了构建约束时，Go编译器会如何处理这些信息的优先级？ 即使是经验丰富的Go专家，对于上述在包构建过程中涉及的文件选择细节，可能也只能给出模糊的答案。 在实际开发中，我们常常需要针对不同操作系统和架构编写特定的代码，这意味着灵活性与复杂性并存。Go的构建约束和文件名约定虽然为我们提供了灵活性，但也带来了额外的复杂性。理解这些规则不仅有助于优化构建过程，还能有效避免潜在的错误和不必要的麻烦。 在这篇文章中，我将与大家探讨Go包构建过程中源文件选择的细节，包括文件名中ARCH和os标识约定和构建约束的作用，以及二者的优先级处理问题。希望通过这些内容，帮助开发者更好地掌握Go语言的构建机制，从而提高开发效率。 为了更好地说明Go包构建时的文件选择逻辑，我们先从Go包构建的一些“表象”说起。 注：在本文中，我们将使用Go 1.17引入的新版build constraints写法：//go:build ，之前的// +build aix darwin dragonfly freebsd js,wasm &#8230;写法已经不再被推荐使用。如果你想对旧版build constraints写法有一个全面了解以便与新写法对比，推荐阅读我的《Go语言精进之路：从新手到高手的编程思想、方法和技巧》第2册。 1. 表象 在Go工程中，通常一个目录对应一个Go包，每个Go包下可以存在多个以.go为后缀的Go源文件，这些源文件只能具有唯一的包名（测试源文件除外），以标准库fmt包为例，它的目录下的源文件列表如下(以Go 1.23.0源码为例)： $ls $GOROOT/src/fmt doc.go export_test.go print.go stringer_example_test.go errors.go fmt_test.go scan.go stringer_test.go errors_test.go format.go scan_test.go [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/go-source-file-selection-details-when-building-package-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2024/11/21/go-source-file-selection-details-when-building-package">本文永久链接</a> &#8211; https://tonybai.com/2024/11/21/go-source-file-selection-details-when-building-package</p>
<p>在Go语言开发中，<a href="https://tonybai.com/2023/06/18/go-package-design-guide/">包（package）是代码组织的基本单位</a>，也是基本的构建单元。Go编译器会将每个包构建成一个目标文件(.a)，然后通过链接器将这些目标文件链接在一起，形成最终的可执行程序。</p>
<p>尽管Go包的构建过程看似简单，但实际上蕴含着许多值得深入了解的细节。例如，当我们执行go build命令时，Go编译器是如何选择需要编译的源文件的？你可能会回答：“不就是通过文件名中的ARCH和OS标识以及构建约束（build constraints）来选择的吗？” 虽然你的答案并没有错，但如果我进一步提出以下问题，你是否还能给出确切的答案呢？</p>
<p>假设一个Go源文件使用了如下的构建约束：</p>
<pre><code>//go:build unix

package foo
// ... ...
</code></pre>
<p>在执行GOOS=android go build时，这个文件是否会被编译？如果执行的是GOOS=aix go build呢？而“unix”究竟包含了哪些操作系统？</p>
<p>再进一步，当一个源文件的文件名中包含ARCH和操作系统标识，并且文件内容中也使用了构建约束时，Go编译器会如何处理这些信息的优先级？</p>
<p>即使是经验丰富的Go专家，对于上述在包构建过程中涉及的文件选择细节，可能也只能给出模糊的答案。</p>
<p>在实际开发中，我们常常需要针对不同操作系统和架构编写特定的代码，这意味着灵活性与复杂性并存。Go的构建约束和文件名约定虽然为我们提供了灵活性，但也带来了额外的复杂性。理解这些规则不仅有助于优化构建过程，还能有效避免潜在的错误和不必要的麻烦。</p>
<p>在这篇文章中，我将与大家探讨<strong>Go包构建过程中源文件选择的细节</strong>，包括文件名中ARCH和os标识约定和构建约束的作用，以及二者的优先级处理问题。希望通过这些内容，帮助开发者更好地掌握Go语言的构建机制，从而提高开发效率。</p>
<p>为了更好地说明Go包构建时的文件选择逻辑，我们先从Go包构建的一些“表象”说起。</p>
<blockquote>
<p>注：在本文中，我们将使用<a href="https://tonybai.com/2021/08/17/some-changes-in-go-1-17">Go 1.17</a>引入的新版build constraints写法：//go:build ，之前的// +build aix darwin dragonfly freebsd js,wasm &#8230;写法已经不再被推荐使用。如果你想对旧版build constraints写法有一个全面了解以便与新写法对比，推荐阅读我的<a href="https://book.douban.com/subject/35720729/">《Go语言精进之路：从新手到高手的编程思想、方法和技巧》第2册</a>。</p>
</blockquote>
<h2>1. 表象</h2>
<p>在Go工程中，通常一个目录对应一个Go包，每个Go包下可以存在多个以.go为后缀的Go源文件，这些源文件只能具有唯一的包名（测试源文件除外），以标准库fmt包为例，它的目录下的源文件列表如下(以<a href="https://tonybai.com/2024/08/19/some-changes-in-go-1-23/">Go 1.23.0</a>源码为例)：</p>
<pre><code>$ls $GOROOT/src/fmt
doc.go              export_test.go          print.go            stringer_example_test.go
errors.go           fmt_test.go         scan.go             stringer_test.go
errors_test.go          format.go           scan_test.go
example_test.go         gostringer_example_test.go  state_test.go
</code></pre>
<p>在这些文件中，哪些最终进入到了fmt包的目标文件(fmt.a)中呢？<strong>贴心的Go工具链</strong>为我们提供了查看方法：</p>
<pre><code>$go list -f '{{.GoFiles}}' fmt
[doc.go errors.go format.go print.go scan.go]
</code></pre>
<p>对于独立于目标ARCH和OS的fmt包来说，其Go源文件的选择似乎要简单一些。我们看到，除了包测试文件(xxx&#95;test.go)，其他文件都被编译到了最终的fmt包中。</p>
<p>我们再来看一个与目标ARCH和OS相关性较高的net包。除去子目录，这个包目录下的Go源文件数量大约有220多个，但在<strong>macOS/amd64</strong>下通过go list查看最终进入net包目标文件的文件，大约只有几十个：</p>
<pre><code>$go list -f '{{.GoFiles}}' net
[addrselect.go cgo_darwin.go cgo_unix.go cgo_unix_syscall.go conf.go dial.go dnsclient.go dnsclient_unix.go dnsconfig.go dnsconfig_unix.go error_posix.go error_unix.go fd_posix.go fd_unix.go file.go file_unix.go hook.go hook_unix.go hosts.go interface.go interface_bsd.go interface_darwin.go ip.go iprawsock.go iprawsock_posix.go ipsock.go ipsock_posix.go lookup.go lookup_unix.go mac.go mptcpsock_stub.go net.go netcgo_off.go netgo_off.go nss.go parse.go pipe.go port.go port_unix.go rawconn.go rlimit_unix.go sendfile_unix_alt.go sock_bsd.go sock_posix.go sockaddr_posix.go sockopt_bsd.go sockopt_posix.go sockoptip_bsdvar.go sockoptip_posix.go splice_stub.go sys_cloexec.go tcpsock.go tcpsock_posix.go tcpsock_unix.go tcpsockopt_darwin.go tcpsockopt_posix.go udpsock.go udpsock_posix.go unixsock.go unixsock_posix.go unixsock_readmsg_cloexec.go writev_unix.go]
</code></pre>
<p>接下来，我们跳出Go标准库，来看一个自定义的示例：</p>
<pre><code>$tree -F buildconstraints/demo1
buildconstraints/demo1
├── foo/
│   ├── f1_android.go
│   ├── f2_linux.go
│   └── f3_darwin.go
└── go.mod

// buildconstraints/demo1/foo/f1_android.go 

//go:build linux

package foo

func F1() {
}

// buildconstraints/demo1/foo/f2_linux.go
//go:build android

package foo

func F2() {
}

// buildconstraints/demo1/foo/f3_darwin.go
//go:build android

package foo

func F3() {
}
</code></pre>
<p>在GOOS=android下构建buildconstraints/demo1/foo这个包，哪些文件会被选出来呢，看下面输出结果：</p>
<pre><code>$GOOS=android go list -f '{{.GoFiles}}' github.com/bigwhite/demo1/foo
[f1_android.go f2_linux.go]
</code></pre>
<p>如果说前两个示例还好理解，那这第三个示例很可能会让很多开发者觉得有些“发蒙”。 别急，上面三个示例都是表象，接下来，我们就来仔细探索一下Go构建时的文件选择机制。</p>
<h2>2. 文件选择机制</h2>
<p>Go包构建时选择源文件的机制还是蛮繁琐的，我们需要从源码入手梳理出其主要逻辑，在Go 1.23版本中，Go包构建过程源文件选择逻辑的代码位于\$GOROOT/src/go/build/build.go中，这个源文件有2k多行，不过不用担心，我这里会替你把主要调用逻辑梳理为下图：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-source-file-selection-details-when-building-package-2.png" alt="" /></p>
<p>函数Import调用Default.Import去获取包的详细信息，信息用build.Package结构表示：</p>
<pre><code>// $GOROOT/src/go/build/build.go
// A Package describes the Go package found in a directory.
  type Package struct {
      Dir           string   // directory containing package sources
      Name          string   // package name
      ImportComment string   // path in import comment on package statement
      Doc           string   // documentation synopsis
      ImportPath    string   // import path of package ("" if unknown)
      Root          string   // root of Go tree where this package lives
      SrcRoot       string   // package source root directory ("" if unknown)
      PkgRoot       string   // package install root directory ("" if unknown)
      PkgTargetRoot string   // architecture dependent install root directory ("" if unknown)
      BinDir        string   // command install directory ("" if unknown)
      Goroot        bool     // package found in Go root
      PkgObj        string   // installed .a file
      AllTags       []string // tags that can influence file selection in this directory
      ConflictDir   string   // this directory shadows Dir in $GOPATH
      BinaryOnly    bool     // cannot be rebuilt from source (has //go:binary-only-package comment)

      // Source files
      GoFiles           []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
      ... ...
</code></pre>
<p>其中的GoFiles就是参与Go包编译的源文件列表。</p>
<p>Default是默认的上下文信息，包括构建所需的默认goenv中几个环境变量，比如GOARCH、GOOS等的值：</p>
<pre><code>// Default is the default Context for builds.
// It uses the GOARCH, GOOS, GOROOT, and GOPATH environment variables
// if set, or else the compiled code's GOARCH, GOOS, and GOROOT.
var Default Context = defaultContext()
</code></pre>
<p>Context的Import方法代码行数很多，对于要了解文件选择细节的我们来说，其中最重要的调用是Context的matchFile方法。</p>
<p>matchFile正是那个<strong>用于确定某个Go源文件是否应该被选入最终包文件中的方法</strong>。它内部的逻辑可以分为两个主要步骤。</p>
<p>第一步是<strong>调用Context的goodOSArchFile方法对Go源文件的名字进行判定</strong>，goodOSArchFile方法的判定也有两个子步骤：</p>
<ul>
<li>判断名字中的OS和ARCH是否在Go支持的OS和ARCH列表中</li>
</ul>
<p>当前Go支持的OS和ARCH在syslist.go文件中有定义：</p>
<pre><code>// $GOROOT/src/go/build/syslist.go

// knownArch is the list of past, present, and future known GOARCH values.
// Do not remove from this list, as it is used for filename matching.
var knownArch = map[string]bool{
    "386":         true,
    "amd64":       true,
    "amd64p32":    true,
    "arm":         true,
    "armbe":       true,
    "arm64":       true,
    "arm64be":     true,
    "loong64":     true,
    "mips":        true,
    "mipsle":      true,
    "mips64":      true,
    "mips64le":    true,
    "mips64p32":   true,
    "mips64p32le": true,
    "ppc":         true,
    "ppc64":       true,
    "ppc64le":     true,
    "riscv":       true,
    "riscv64":     true,
    "s390":        true,
    "s390x":       true,
    "sparc":       true,
    "sparc64":     true,
    "wasm":        true,
}

// knownOS is the list of past, present, and future known GOOS values.
// Do not remove from this list, as it is used for filename matching.
// If you add an entry to this list, look at unixOS, below.
var knownOS = map[string]bool{
    "aix":       true,
    "android":   true,
    "darwin":    true,
    "dragonfly": true,
    "freebsd":   true,
    "hurd":      true,
    "illumos":   true,
    "ios":       true,
    "js":        true,
    "linux":     true,
    "nacl":      true,
    "netbsd":    true,
    "openbsd":   true,
    "plan9":     true,
    "solaris":   true,
    "wasip1":    true,
    "windows":   true,
    "zos":       true,
}
</code></pre>
<p>我们也可以通过下面命令查看：</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
freebsd/riscv64
illumos/amd64
ios/amd64
ios/arm64
js/wasm
linux/386
linux/amd64
linux/arm
linux/arm64
linux/loong64
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/ppc64
openbsd/riscv64
plan9/386
plan9/amd64
plan9/arm
solaris/amd64
wasip1/wasm
windows/386
windows/amd64
windows/arm
windows/arm64
</code></pre>
<blockquote>
<p>注：像sock_bsd.go、sock_posix.go这样的Go源文件，虽然它们的文件名中包含posix、bsd等字样，但这些文件实际上只是普通的Go源文件。其文件名本身并不会影响Go包在构建时选择文件的结果。</p>
</blockquote>
<ul>
<li>调用matchTag来判定该Go源文件名字中的OS和ARCH是否与当前上下文信息中的OS和ARCH匹配</li>
</ul>
<p>Go支持的源文件名组成格式如下：</p>
<pre><code>  //  name_$(GOOS).*
  //  name_$(GOARCH).*
  //  name_$(GOOS)_$(GOARCH).*
  //  name_$(GOOS)_test.*
  //  name_$(GOARCH)_test.*
  //  name_$(GOOS)_$(GOARCH)_test.*
</code></pre>
<p>不过这里有三个例外，即：</p>
<p>如果上下文中的GOOS=android，那么文件名字中OS值为linux的Go源文件也算是匹配的；</p>
<p>如果上下文中的GOOS=illumos，那么文件名字中OS值为solaris的Go源文件也算是匹配的；</p>
<p>如果上下文中的GOOS=ios，那么文件名字中OS值为darwin的Go源文件也算是匹配的。</p>
<p>还有一个特殊处理，那就是当文件名字中OS值为unix时，该源文件可以匹配以下上下文中GOOS的值：</p>
<pre><code>// $GOROOT/src/go/build/syslist.go

// unixOS is the set of GOOS values matched by the "unix" build tag.
// This is not used for filename matching.
// This list also appears in cmd/dist/build.go and
// cmd/go/internal/imports/build.go.
var unixOS = map[string]bool{
    "aix":       true,
    "android":   true,
    "darwin":    true,
    "dragonfly": true,
    "freebsd":   true,
    "hurd":      true,
    "illumos":   true,
    "ios":       true,
    "linux":     true,
    "netbsd":    true,
    "openbsd":   true,
    "solaris":   true,
}
</code></pre>
<p>这里面列出os都是所谓的“类Unix”操作系统。</p>
<p>如果goodOSArchFile方法返回文件名匹配成功，那么<strong>第二步就是调用Context的shouldBuild方法对Go源文件中的build constraints进行判定</strong>，这个判定过程也是调用matchTag完成的，因此规则与上面对matchTag的说明一致。如果判定match成功，那么该源文件将会被Go编译器编译到最终的Go包目标文件中去。</p>
<p>下面我们结合文章第一节“表象”中的那个自定义示例来判定一下为何最终会输出那个结果。</p>
<h2>3. 示例分析</h2>
<p>在buildconstraints/demo1/foo包目录中，一共有三个Go源文件：</p>
<pre><code>$tree -F foo
foo
├── f1_android.go
├── f2_linux.go
└── f3_darwin.go
</code></pre>
<p>注意：当前我的系统为<strong>darwin/amd64</strong>，但我们使用了GOOS=android的环境变量。我们顺着上一节梳理出来的文件选择判定的主逻辑，对着三个文件逐一过一遍。</p>
<ul>
<li>f1_android.go</li>
</ul>
<p>首先用goodOSArchFile判定文件名是否匹配。当GOOS=android时，文件名中的os为android，文件名匹配成功，</p>
<p>然后用shouldBuild判定文件中的build constraints是否匹配。该文件的约束为linux，在上面matchTag的三个例外规则里提到过，当GOOS=android时，如果build constraints是linux，是可以匹配的。</p>
<p>因此，f1_android.go将出现在最终编译文件列表中。</p>
<ul>
<li>f2_linux.go</li>
</ul>
<p>首先用goodOSArchFile判定文件名是否匹配。当GOOS=android时，文件名中的os为linux，linux显然在go支持的os列表中，并且根据matchTag的例外规则，当GOOS=android时，文件名中的os为linux时是可以匹配的。</p>
<p>然后用shouldBuild判定文件中的build constraints是否匹配。该文件的约束为android，与GOOS相同，可以匹配。</p>
<p>因此，f2_linux.go将出现在最终编译文件列表中。</p>
<ul>
<li>f3_darwin.go</li>
</ul>
<p>首先用goodOSArchFile判定文件名是否匹配。当GOOS=android时，文件名中的os为darwin，虽然darwin在go支持的os列表中，但darwin与GOOS=android并不匹配，因此在goodOSArchFile这步中，f3_darwin.go就被“淘汰”掉了！即便f3_darwin.go中的build constraints为android。</p>
<p>因此，f3_darwin.go不会出现在最终编译文件列表中。</p>
<p>如果再增加一个源文件f4_unix.go，其内容为：</p>
<pre><code>//go:build android

func F4() {
}
</code></pre>
<p>这个f4_unix.go是否会出现在最终的包编译文件列表中呢？这个作为思考题留给大家了，也欢迎你在评论区留言，说说你的思考结果。</p>
<h2>4. 小结</h2>
<p>在Go语言的开发过程中，包的构建是核心环节之一，而源文件的选择则是构建过程中一个复杂且关键的细节。本文深入探讨了Go编译器在执行go build命令时，如何根据文件名中的架构（ARCH）和操作系统（OS）标识，以及构建约束（build constraints），来选择需要编译的源文件。</p>
<p>通过具体示例，本文展示了不同文件名和构建约束如何影响最终的编译结果，并揭示了Go编译器处理这些信息的优先级。理解这些内部机制不仅能帮助开发者优化构建过程，还能有效避免潜在的错误。希望本文的分析能够给大家带去帮助。</p>
<blockquote>
<p>注：限于篇幅，本文仅针对包编译文件选择最复杂的部分进行的探索，而像ReleaseTags(比如: go1.21等)、cgo、&#95;test.go后缀等比较明显的约束并未涉及，同时对于新版build constraints的运算符组合也未提及，感兴趣的童鞋可以参考<a href="https://pkg.go.dev/cmd/go#hdr-Build_constraints">go build constraints</a>官方文档查阅。</p>
</blockquote>
<p>本文涉及的源码可以在<a href="https://github.com/bigwhite/experiments/tree/master/buildconstraints">这里</a>下载。</p>
<h2>5. 参考资料</h2>
<ul>
<li><a href="https://pkg.go.dev/cmd/go#hdr-Build_constraints">Go build constraints</a> &#8211; https://pkg.go.dev/cmd/go#hdr-Build_constraints</li>
<li><a href="https://github.com/golang/go/issues/25348">proposal: cmd/go: allow &amp;&amp; and || operators and parentheses in build tags</a> &#8211; https://github.com/golang/go/issues/25348</li>
<li><a href="https://go.googlesource.com/proposal/+/master/design/draft-gobuild.md">Bug-resistant build constraints — Draft Design</a> &#8211; https://go.googlesource.com/proposal/+/master/design/draft-gobuild.md</li>
<li><a href="https://github.com/golang/go/issues/41184">cmd/go: continue conversion to bug-resistant //go:build constraints</a> &#8211; https://github.com/golang/go/issues/41184</li>
<li><a href="https://go.dev/doc/go1.17">Go 1.17 release notes</a> &#8211; https://go.dev/doc/go1.17</li>
<li><a href="https://github.com/golang/go/issues/45454">cmd/go: provide build tags for architecture environment variables</a> &#8211; https://github.com/golang/go/issues/45454</li>
</ul>
<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/21/go-source-file-selection-details-when-building-package/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go 1.19中值得关注的几个变化</title>
		<link>https://tonybai.com/2022/08/22/some-changes-in-go-1-19/</link>
		<comments>https://tonybai.com/2022/08/22/some-changes-in-go-1-19/#comments</comments>
		<pubDate>Mon, 22 Aug 2022 02:23:50 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[atomic]]></category>
		<category><![CDATA[ballast]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Cgo]]></category>
		<category><![CDATA[CGO_FLAGS]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[DataRaceFree]]></category>
		<category><![CDATA[DRF-SC]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[generics]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Go1]]></category>
		<category><![CDATA[go1.18]]></category>
		<category><![CDATA[go1.19]]></category>
		<category><![CDATA[Go1.5]]></category>
		<category><![CDATA[GOARCH]]></category>
		<category><![CDATA[godoc]]></category>
		<category><![CDATA[goenv]]></category>
		<category><![CDATA[GOGC]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GOMEMLIMIT]]></category>
		<category><![CDATA[GOOS]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[heap]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[LeslieLamport]]></category>
		<category><![CDATA[loong64]]></category>
		<category><![CDATA[memory-model]]></category>
		<category><![CDATA[OOM]]></category>
		<category><![CDATA[pacer]]></category>
		<category><![CDATA[paper]]></category>
		<category><![CDATA[runtime]]></category>
		<category><![CDATA[RussCox]]></category>
		<category><![CDATA[SC]]></category>
		<category><![CDATA[SetFinalizer]]></category>
		<category><![CDATA[SetGCPercent]]></category>
		<category><![CDATA[SetMemoryLimit]]></category>
		<category><![CDATA[sync]]></category>
		<category><![CDATA[TSO]]></category>
		<category><![CDATA[twitch]]></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=3642</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2022/08/22/some-changes-in-go-1-19 我们知道Go团队在2015年重新规定了团队发布版本的节奏，将Go大版本的发布频率确定为每年两次，发布窗口定为每年的2月与8月。而实现自举的Go 1.5版本是这一个节奏下发布的第一个版本。一般来说，Go团队都会在这两个窗口的中间位置发布版本，不过这几年也有意外，比如承载着泛型落地责任的Go 1.18版本就延迟了一个月发布。 就在我们以为Go 1.19版本不会很快发布的时候，美国时间2022年8月2日，Go核心团队正式发布了Go 1.19版本，这个时间不仅在发布窗口内而且相对于惯例还提前了。为什么呢？很简单，Go 1.19是一个“小”版本，当然这里的“小”是相对于Go 1.18那样的“大”而言的。Go 1.19版本开发周期仅有2个月左右(3~5月初)，这样Go团队压缩了添加到Go 1.19版本中的feature数量。 不过尽管如此，Go 1.19中依然有几个值得我们重点关注的变化点，在这篇文章中我就和大家一起来看一下。 一. 综述 在6月份(那时Go 1.19版本已经Freeze)，我曾写过一篇《Go 1.19新特性前瞻》，简要介绍了当时基本确定的Go 1.19版本的一些新特性，现在来看，和Go 1.19版本正式版差别不大。 泛型方面 考虑到Go 1.18泛型刚刚落地，Go 1.18版本中的泛型并不是完全版。但Go 1.19版本也没有急于实现泛型设计文档)中那些尚未实现的功能特性，而是将主要精力放在了修复Go 1.18中发现的泛型实现问题上了，目的是夯实Go泛型的底座，为Go 1.20以及后续版本实现完全版泛型奠定基础(详细内容可查看《Go 1.19新特性前瞻》一文)。 其他语法方面 无，无，无！重要的事情说三遍。 这样，Go 1.19依旧保持了Go1兼容性承诺。 正式在linux上支持龙芯架构(GOOS=linux, GOARCH=loong64) 这一点不得不提，因为这一变化都是国内龙芯团队贡献的。不过目前龙芯支持的linux kernel版本最低也是5.19，意味着龙芯在老版本linux上还无法使用Go。 go env支持CGO_CFLAGS, CGO_CPPFLAGS, CGO_CXXFLAGS, CGO_FFLAGS, CGO_LDFLAGS和GOGCCFLAGS 当你想设置全局的而非包级的CGO构建选项时，可以通过这些新加入的CGO相关环境变量进行，这样就可以避免在每个使用Cgo的Go源文件中使用cgo指示符来分别设置了。 目前这些用于CGO的go环境变量的默认值如下(以我的macos上的默认值为例)： CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2" [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/some-changes-in-go-1-19-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2022/08/22/some-changes-in-go-1-19">本文永久链接</a> &#8211; https://tonybai.com/2022/08/22/some-changes-in-go-1-19</p>
<p>我们知道Go团队在2015年重新规定了团队发布版本的节奏，将Go大版本的发布频率确定为每年两次，发布窗口定为每年的2月与8月。而实现自举的<a href="https://tonybai.com/2015/07/10/some-changes-in-go-1-5/">Go 1.5版本</a>是这一个节奏下发布的第一个版本。一般来说，Go团队都会在这两个窗口的中间位置发布版本，不过这几年也有意外，比如承载着泛型落地责任的<a href="https://tonybai.com/2022/04/20/some-changes-in-go-1-18">Go 1.18版本</a>就延迟了一个月发布。</p>
<p>就在我们以为Go 1.19版本不会很快发布的时候，美国时间2022年8月2日，<a href="https://go.dev/blog/go1.19">Go核心团队正式发布了Go 1.19版本</a>，这个时间不仅在发布窗口内而且相对于惯例还提前了。为什么呢？很简单，<strong>Go 1.19是一个“小”版本</strong>，当然这里的“小”是相对于Go 1.18那样的“大”而言的。Go 1.19版本开发周期仅有2个月左右(3~5月初)，这样Go团队压缩了添加到Go 1.19版本中的feature数量。</p>
<p>不过尽管如此，Go 1.19中依然有几个值得我们重点关注的变化点，在这篇文章中我就和大家一起来看一下。</p>
<h3>一. 综述</h3>
<p>在6月份(那时Go 1.19版本已经Freeze)，我曾写过一篇<a href="https://tonybai.com/2022/06/12/go-1-19-foresight">《Go 1.19新特性前瞻》</a>，简要介绍了当时基本确定的Go 1.19版本的一些新特性，现在来看，和Go 1.19版本正式版差别不大。</p>
<ul>
<li>泛型方面</li>
</ul>
<p>考虑到Go 1.18泛型刚刚落地，Go 1.18版本中的泛型并不是完全版。但Go 1.19版本也没有急于实现<a href="https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md">泛型设计文档</a>)中那些尚未实现的功能特性，而是将主要精力放在了修复Go 1.18中发现的<a href="https://github.com/golang/go/issues?q=is%3Aissue+label%3Agenerics+milestone%3AGo1.19">泛型实现问题</a>上了，目的是夯实Go泛型的底座，为Go 1.20以及后续版本实现完全版泛型奠定基础(详细内容可查看<a href="https://tonybai.com/2022/06/12/go-1-19-foresight">《Go 1.19新特性前瞻》</a>一文)。</p>
<ul>
<li>其他语法方面</li>
</ul>
<p>无，无，无！重要的事情说三遍。</p>
<p>这样，Go 1.19依旧保持了Go1兼容性承诺。</p>
<ul>
<li>正式在linux上支持龙芯架构(GOOS=linux, GOARCH=loong64)</li>
</ul>
<p>这一点不得不提，因为这一变化都是国内龙芯团队贡献的。不过目前龙芯支持的linux kernel版本最低也是5.19，意味着龙芯在老版本linux上还无法使用Go。</p>
<ul>
<li>go env支持CGO_CFLAGS, CGO_CPPFLAGS, CGO_CXXFLAGS, CGO_FFLAGS, CGO_LDFLAGS和GOGCCFLAGS</li>
</ul>
<p>当你想设置全局的而非包级的CGO构建选项时，可以通过这些新加入的CGO相关环境变量进行，这样就可以避免在每个使用Cgo的Go源文件中使用cgo指示符来分别设置了。</p>
<p>目前这些用于CGO的go环境变量的默认值如下(以我的macos上的默认值为例)：</p>
<pre><code>CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
GOGCCFLAGS="-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/cz/sbj5kg2d3m3c6j650z0qfm800000gn/T/go-build1672298076=/tmp/go-build -gno-record-gcc-switches -fno-common"
</code></pre>
<p>其他更具体的变化就不赘述了，大家可以移步<a href="https://tonybai.com/2022/06/12/go-1-19-foresight">《Go 1.19新特性前瞻》</a>看看。</p>
<p>下面我们重点说说Go 1.19中的两个重要变化：<strong>新版Go内存模型文档与Go运行时引入Soft memory limit</strong>。</p>
<h3>二. 修订Go内存模型文档</h3>
<p>记得当年初学Go的时候，所有Go官方文档中最难懂的一篇就属<a href="https://go.dev/ref/mem">Go内存模型文档</a>(如下图)这一篇了，相信很多gopher在初看这篇文档时一定有着和我相似的赶脚^_^。</p>
<p><img src="https://tonybai.com/wp-content/uploads/some-changes-in-go-1-19-2.png" alt="" /><br />
<center>图：老版Go内存模型文档</center></p>
<blockquote>
<p>注：查看老版Go内存模型文档的方法：godoc -http=:6060 -goroot /Users/tonybai/.bin/go1.18.3，其中godoc已经不随着go安装包分发了，需要你单独安装，命令为：go install golang.org/x/tools/cmd/godoc。</p>
</blockquote>
<p>那么，老版内存模型文档说的是啥呢？为什么要修订？搞清这两个问题，我们就大致知道新版内存模型文档的意义了。 我们先来看看什么是编程语言的内存模型。</p>
<h4>1. 什么是内存模型？</h4>
<p>提到内存模型，我们要从著名计算机科学家，2013年图灵奖得主<a href="https://www.microsoft.com/en-us/research/people/lamport/">Leslie Lamport</a>在1979发表的名为<a href="https://www.microsoft.com/en-us/research/publication/make-multiprocessor-computer-correctly-executes-multiprocess-programs/">《How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programs》</a>的论文说起。</p>
<p>在这篇文章中，Lamport给出了多处理器计算机在共享内存的情况下并发程序正确运行的条件，即多处理器要满足<strong>顺序一致性(sequentially consistent)</strong>。</p>
<p>文中提到：一个高速运行的处理器不一定按照程序指定的顺序(代码顺序)执行。如果一个处理器的执行结果(可能是乱序执行)与按照程序指定的顺序(代码顺序)执行的结果一致，那么说这个处理器是<strong>有序的(sequential)</strong>。</p>
<p>而对于一个共享内存的多处理器而言，只有满足下面条件，才能被认定是满足<strong>顺序一致性</strong>的，即具备保证并发程序正确运行的条件：</p>
<ul>
<li>任何一次执行的结果，都和所有处理器的操作按照某个顺序执行的结果一致;</li>
<li>在“某个顺序执行”中单独看每个处理器，每个处理器也都是按照程序指定的顺序(代码顺序)执行的。</li>
</ul>
<p><strong>顺序一致性就是一个典型的共享内存、多处理器的内存模型</strong>，这个模型保证了所有的内存访问都是以原子方式和按程序顺序进行的。下面是一个共享内存的顺序一致性的抽象机器模型示意图，图来自于<a href="https://www.cl.cam.ac.uk/~pes20/ppc-supplemental/test7.pdf">《A Tutorial Introduction to the ARM and POWER Relaxed Memory Models》</a> ：</p>
<p><img src="https://tonybai.com/wp-content/uploads/some-changes-in-go-1-19-4.png" alt="" /></p>
<p>根据顺序一致性，上面图中的抽象机器具有下面特点：</p>
<ul>
<li>没有本地的重新排序：每个硬件线程按照程序指定的顺序执行指令，完成每条指令（包括对共享内存的任何读或写）后再开始下一条。</li>
<li>每条写入指令对所有线程（包括进行写入的线程）都是同时可见的。</li>
</ul>
<p>从程序员角度来看，顺序一致性的内存模型是再理想不过了。所有读写操作直面内存，没有缓存，一个处理器(或硬件线程)写入内存的值，其他处理器(或硬件线程)便可以观察到。借助硬件提供的顺序一致性(SC)，我们可以实现“所写即所得”。</p>
<p>但是这样的机器真的存在吗？并没有，至少在量产的机器中并没有。为什么呢？因为顺序一致性不利于硬件和软件的性能优化。真实世界的共享内存的多处理器计算机的常见机器模型是这样的，也称为Total Store Ordering，TSO模型(图来自<a href="https://www.cl.cam.ac.uk/~pes20/ppc-supplemental/test7.pdf">《A Tutorial Introduction to the ARM and POWER Relaxed Memory Models》</a>)：</p>
<p><img src="https://tonybai.com/wp-content/uploads/some-changes-in-go-1-19-5.png" alt="" /></p>
<p>我们看到，在这种机器下，所有处理器仍连接到单个共享内存，但每个处理器的写内存操作从写入共享内存变为了先写入本处理器的写缓存队列(write buffer)，这样处理器无需因要等待写完成(write complete)而被阻塞，并且一个处理器上的读内存操作也会先查阅本处理器的写缓存队列(但不会查询其他处理器的写缓存队列)。写缓存队列的存在极大提升了处理器写内存操作的速度。</p>
<p>但也正是由于写缓存的存在，TSO模型无法满足顺序一致性，比如：“每条写入指令对所有线程（包括进行写入的线程）都是同时可见的”这一特性就无法满足，因为写入本地写缓存队列的数据在未真正写入共享内存前只对自己可见，对其他处理器(硬件线程)并不可见。</p>
<p>根据Lamport的理论，在不满足SC的多处理器机器上程序员没法开发出可以正确运行的并发程序(Data Race Free, DRF)，那么怎么办呢？处理器提供同步指令给开发者。对开发者而言，有了同步指令的非SC机器，具备了SC机器的属性。只是这一切对开发人员不是自动的/透明的了，需要开发人员熟悉同步指令，并在适当场合，比如涉及数据竞争Data Race的场景下正确使用，这大大增加了开发人员的心智负担。</p>
<p>开发人员通常不会直面硬件，这时就要求高级编程语言对硬件提供的同步指令进行封装并提供给开发人员，这就是<strong>编程语言的同步原语</strong>。而编程语言使用哪种硬件同步指令，封装出何种行为的同步原语，怎么应用这些原语，错误的应用示例等都是需要向编程语言的使用者进行说明的。而这些都将是编程语言内存模型文档的一部分。</p>
<p>如今主流的编程语言的内存模型都是<strong>顺序一致性(SC)模型</strong>，它为开发人员提供了一种理想的SC机器(虽然实际中的机器并非SC的)，程序是建构在这一模型之上的。但就像前面说的，开发人员要想实现出正确的并发程序，还必须了解编程语言封装后的同步原语以及他们的语义。<strong>只要程序员遵循并发程序的同步要求合理使用这些同步原语，那么编写出来的并发程序就能在非SC机器上跑出顺序一致性的效果</strong>。</p>
<p>知道了编程语言内存模型的含义后，接下来，我们再来看看老版Go内存模型文档究竟表述了什么。</p>
<h4>2. Go内存模型文档</h4>
<p>按照上面的说明，Go内存模型文档描述的应该是<strong>要用Go写出一个正确的并发程序所要具备的条件</strong>。</p>
<p>再具体点，就像老版内存模型文档开篇所说的那样：<strong>Go内存模型规定了一些条件，一旦满足这些条件，当在一个goroutine中读取一个变量时，Go可以保证它可以观察到不同goroutine中对同一变量的写入所产生的新值</strong>。</p>
<p>接下来，内存模型文档就基于常规的happens-before定义给出了Go提供的各种同步操作及其语义，包括：</p>
<ul>
<li>如果一个包p导入了包q，那么q的init函数的完成发生在p的任何函数的开始之前。</li>
<li>函数main.main的开始发生在所有init函数完成之后。</li>
<li>启动一个新的goroutine的go语句发生在goroutine的执行开始之前。</li>
<li>一个channel上的发送操作发生在该channel的对应接收操作完成之前。</li>
<li>一个channel的关闭发生在一个返回零值的接收之前(因为该channel已经关闭)。</li>
<li>一个无缓冲的channel的接收发生在该channel的发送操作完成之前。</li>
<li>一个容量为C的channel上的第k个接收操作发生在该channel第k+C个发送操作完成之前。</li>
<li>对于任何sync.Mutex或sync.RWMutex变量l，当n&lt;m时，第n次l.Unlock调用发生在第m次调用l.Lock()返回之前。</li>
<li>once.Do(f)中的f()调用发生在对once.Do(f)的任何一次调用返回之前。</li>
</ul>
<p>接下来，内存模型文档还定义了一些误用同步原语的例子。</p>
<p>那么新内存模型文档究竟更新了哪些内容呢？我们继续往下看。</p>
<h4>3. 修订后的内存模型文档都有哪些变化</h4>
<p><img src="https://tonybai.com/wp-content/uploads/some-changes-in-go-1-19-3.png" alt="" /><br />
<center>图：修订后的Go内存模型文档</center></p>
<p>负责更新内存模型文档的Russ Cox首先增加了<strong>Go内存模型的总体方法(overall approach)</strong>。</p>
<p>Go的总体方法在C/C++和Java/Js之间，既不像C/C++那样将存在Data race的程序定义为违法的，让编译器以未定义行为处置它，即运行时表现出任意可能的行为；又不完全像Java/Js那样尽量明确Data Race情况下各种语义，将Data race带来的影响限制在最小，使程序更为可靠。</p>
<p>Go对于一些存在data Race的情况会输出race报告并终止程序，比如多goroutine在未使用同步手段下对map的并发读写。除此之外，Go对其他存数据竞争的场景有明确的语义，这让程序更可靠，也更容易调试。</p>
<p>其次，新版Go内存模型文档增补了对这些年sync包新增的API的说明，比如： mutex.TryLock、mutex.TryRLock等。而对于sync.Cond、Map、Pool、WaitGroup等文档没有逐一描述，而是建议看API文档。</p>
<p>在老版内存模型文档中，没有对sync/atom包进行说明，新版文档增加了对atom包以及runtime.SetFinalizer的说明。</p>
<p>最后，文档除了提供不正确同步的例子，还增加了对不正确编译的例子的说明。</p>
<p>另外这里顺便提一下：Go 1.19在atomic包中引入了一些新的原子类型，包括： Bool, Int32, Int64, Uint32, Uint64, Uintptr和Pointer。这些新类型让开发人员在使用atomic包是更为方便，比如下面是Go 1.18和Go 1.19使用Uint64类型原子变量的代码对比：</p>
<p>对比Uint64的两种作法：</p>
<pre><code>// Go 1.18

var i uint64
atomic.AddUint64(&amp;i, 1)
_ = atomic.LoadUint64(&amp;i)

vs.

// Go 1.19
var i atomic.Uint64 // 默认值为0
i.Store(17) // 也可以通过Store设置初始值
i.Add(1)
_ = i.Load()
</code></pre>
<p>atomic包新增的Pointer，避免了开发人员在使用原子指针时自己使用unsafe.Pointer进行转型的麻烦。同时atomic.Pointer是一个泛型类型，如果我没记错，它是Go 1.18加入comparable预定义泛型类型之后，第一次在Go中引入基于泛型的标准库类型：</p>
<pre><code>// $GOROOT/src/sync/atomic/type.go

// A Pointer is an atomic pointer of type *T. The zero value is a nil *T.
type Pointer[T any] struct {
    _ noCopy
    v unsafe.Pointer
}

// Load atomically loads and returns the value stored in x.
func (x *Pointer[T]) Load() *T { return (*T)(LoadPointer(&amp;x.v)) }

// Store atomically stores val into x.
func (x *Pointer[T]) Store(val *T) { StorePointer(&amp;x.v, unsafe.Pointer(val)) }

// Swap atomically stores new into x and returns the previous value.
func (x *Pointer[T]) Swap(new *T) (old *T) { return (*T)(SwapPointer(&amp;x.v, unsafe.Pointer(new))) }

// CompareAndSwap executes the compare-and-swap operation for x.
func (x *Pointer[T]) CompareAndSwap(old, new *T) (swapped bool) {
    return CompareAndSwapPointer(&amp;x.v, unsafe.Pointer(old), unsafe.Pointer(new))
}
</code></pre>
<p>此外，atomic包新增的Int64和Uint64类型还有一个特质，那就是Go保证其地址可以自动对齐到8字节上(即地址可以被64整除)，即便在32位平台上亦是如此，这可是<a href="https://github.com/golang/go/issues/36606">连原生int64和uint64也尚无法做到的</a>。</p>
<p><a href="https://go101.org/">go101</a>在推特上分享了一个基于atomic Int64和Uint64的tip。利用go 1.19新增的atomic.Int64/Uint64，我们可以用下面方法保证结构体中某个字段一定是8 byte对齐的，即该字段的地址可以被64整除。</p>
<pre><code>import "sync/atomic"

type T struct {
    _ [0]atomic.Int64
    x uint64 // 保证x是8字节对齐的
}
</code></pre>
<p>前面的代码中，为何不用_ atomic.Int64呢，为何用一个空数组呢，这是因为空数组在go中不占空间，大家可以试试输出上面结构体T的size，看看是不是8。</p>
<h3>三. 引入Soft memory limit</h3>
<h4>1. 唯一GC调优选项：GOGC</h4>
<p>近几个大版本，Go GC并没有什么大的改动/优化。和其他带GC的编程语言相比，Go GC算是一个奇葩的存在了：对于开发者而言，Go 1.19版本之前，Go GC的调优参数仅有一个：<strong>GOGC</strong>(也可以通过runtime/debug.SetGCPercent调整)。</p>
<p>GOGC默认值为100，通过调整它的值，我们可以调整GC触发的时机。计算下一次触发GC的堆内存size的公式如下：</p>
<pre><code>// Go 1.18版本之前
目标堆大小 = (1+GOGC/100) * live heap // live heap为上一次GC标记后的堆上的live object的总size

// Go 1.18版本及之后
目标堆大小 = live heap + (live heap + GC roots) * GOGC / 100
</code></pre>
<blockquote>
<p>注：Go 1.18以后将GC roots(包括goroutine栈大小和全局变量中的指针对象大小)纳入目标堆大小的计算</p>
</blockquote>
<p>以Go 1.18之前的版本为例，当GOGC=100(默认值)时，如果某一次GC后的live heap为10M，那么下一次GC开启的目标堆heap size为20M，即在两次GC之间，应用程序可以分配10M的新堆对象。</p>
<p>可以说<strong>GOGC控制着GC的运行频率</strong>。当GOGC值设置的较小时，GC运行的就频繁一些，参与GC工作的cpu的比重就多一些；当GOGC的值设置的较大时，GC运行的就不那么频繁，相应的参与GC工作的cpu的比重就小一些，但要承担内存分配接近资源上限的风险。</p>
<p>这样一来，摆在开发者面前的问题就是：GOGC的值很难选，这唯一的调优选项也就成为了摆设。</p>
<p>同时，Go runtime是不关心资源limit的，只是会按照应用的需求持续分配内存，并在自身内存池不足的情况下向OS申请新的内存资源，直到内存耗尽(或到达平台给应用分配的memory limit)而被oom killed！</p>
<p>为什么有了GC，Go应用还是会因耗尽系统memory资源而被oom killed呢？我们继续往下看。</p>
<h4>2. Pacer的问题</h4>
<p>上面的触发GC的目标堆大小计算公式，在Go runtime内部被称为pacer算法，pacer中文有翻译成“起搏器”的，有译成“配速器”的。不管译成啥，总而言之它是用来<strong>控制GC触发节奏的</strong>。</p>
<p>不过pacer目前的算法是无法保证你的应用不被OOM killed的，举个例子(见下图)：</p>
<p><img src="https://tonybai.com/wp-content/uploads/some-changes-in-go-1-19-6.png" alt="" /></p>
<p>在这个例子中：</p>
<ul>
<li>一开始live heap始终平稳，净增的heap object保持0，即新分配的heap object与被清扫掉的heap object相互抵消。</li>
<li>后续在(1)处出现一次target heap的跃升(从h/2->h)，原因显然是live heap object变多了，都在用，即便触发GC也无法清除。不过此时target heap(h)是小于hard memory limit的；</li>
<li>程序继续执行，在(2)处，又出现一次target heap的跃升(从h->2h)，而live heap object也变多了，稳定在h，此时，target heap变为2h，高于hard memory limit了；</li>
<li>后续程序继续执行，当live heap object到达(3)时，实际Go的堆内存(包括未清理的)超过了hard memory limit，但由于尚未到达target heap(2h)，GC没有被执行，因此应用被oom killed。</li>
</ul>
<p>我们看到这个例子中，并非Go应用真正需要那么多内存(如果有GC及时清理，live heap object就在(3)的高度)，<strong>而是Pacer算法导致了没能及时触发GC</strong>。</p>
<p>那么如何尽可能的避免oom killed呢？我们接下来看一下Go社区给出了两个“民间偏方”。</p>
<h4>3. Go社区的GC调优方案</h4>
<p>这两个“偏方”, 一个是<a href="https://es.blog.twitch.tv/tr-tr/2019/04/10/go-memory-ballast-how-i-learnt-to-stop-worrying-and-love-the-heap/">twitch游戏公司给出的memory ballast(内存压舱石)</a>，另外一个则是<a href="https://www.uber.com/en-US/blog/how-we-saved-70k-cores-across-30-mission-critical-services/">像uber这样的大厂采用的自动GC动态调优方案</a>。当然这两个方案不光是要避免oom，更是为了优化GC，提高程序的执行效率。</p>
<p>下面我们分别简单介绍一下。先来说说twitch公司的memory ballast。twitch的Go服务运行在具有64G物理内存的VM上，通过观察运维人员发现，服务常驻的物理内存消耗仅为400多M，但Go GC的启动却十分频繁，这导致其服务响应的时间较长。twitch的工程师考虑充分利用内存，降低GC的启动频率，从而降低服务的响应延迟。</p>
<p>于是他们想到了一种方法，他们在服务的main函数初始化环节像下面这样声明了一个10G容量的大切片，并保证这个切片在程序退出前不被GC释放掉：</p>
<pre><code>func main() {
    // Create a large heap allocation of 10 GiB
    ballast := make([]byte, 10&lt;&lt;30)

    // Application execution continues
    // ...

    runtime.Keepalive(ballast)
    // ... ...
}
</code></pre>
<p>这个切片由于太大，将在堆上分配并被runtime跟踪，但这个切片并不会给应用带去实质上的物理内存消耗，这得益于os对应用进程内存的<strong>延迟簿记</strong>：只有读写的内存才会导致缺页中断并由OS为之分配物理内存。从类似top的工具来看，这10个G的字节仅会记录在VIRT/VSZ(虚拟内存)上，而不会记录在RES/RSS(常驻内存)上。</p>
<p>这样一来，根据前面Pacer算法的原理，触发GC的下一个目标堆大小就至少为20G，在Go服务分配堆内存到20G之前GC都不会被触发，所有cpu资源都会被用来处理业务，这也与twitch的实测结果一致(GC次数下降99%)。</p>
<p>一旦到了20G，由于之前观测的结果是服务仅需400多M物理内存，大量heap object会被回收，Go服务的live heap会回到400多M，但重新计算目标堆内存时，由于前面那个“压舱石”的存在，目标堆内存已经会在至少20G的水位上，就这样GC次数少了，GC少了，worker goroutine参加“劳役”的时间就少了，cpu利用率高了，服务响应的延迟也下来了。</p>
<blockquote>
<p>注：“劳役”是指worker goroutine在mallocgc内存时被runtime强制“劳役”：停下自己手头的工作，去辅助GC做heap live object的mark。</p>
</blockquote>
<p>不过使用该方案的前提是你对你的Go服务的内存消耗情况(忙闲时)有着精确的了解，这样才能结合硬件资源情况设定合理的ballast值。</p>
<p>按照<a href="https://github.com/golang/proposal/blob/master/design/48409-soft-memory-limit.md">Soft memory limit proposal</a>的说法，该方案的弊端如下：</p>
<ul>
<li>不能跨平台移植，据说Windows上不适用(压舱石的值会直接反映为应用的物理内存占用)；</li>
<li>不能保证随着Go运行时的演进而继续正常工作（比如：一旦pacer算法发生了巨大变化）；</li>
<li>开发者需要进行复杂的计算并估计运行时内存开销以选择适合的ballast大小。</li>
</ul>
<p>接下来我们再来看看自动GC动态调优方案。</p>
<p>去年12月，uber在其官方博客分享了uber内部使用的<a href="https://www.uber.com/en-US/blog/how-we-saved-70k-cores-across-30-mission-critical-services/">半自动化Go GC调优方案</a>，按uber的说法，这种方案实施后帮助uber节省了70K cpu核的算力。其背后的原理依旧是从Pacer的算法公式出发，改变原先Go服务生命周期全程保持GOGC值静态不变的作法，在每次GC时，依据容器的内存限制以及当前的live heap size动态计算并设置GOGC值，从而实现对内存不足oom-killed的保护，同时最大程度利用内存，改善Gc对cpu的占用率。</p>
<p>显然这种方案更为复杂，需要有一个专家团队来保证这种自动调优的参数的设置与方案的实现。</p>
<h4>4. 引入Soft memory limit</h4>
<p>其实Go GC pacer的问题还有很多, Go核心团队开发者Michael Knyszek提了一个<a href="https://github.com/golang/go/issues/42430">pacer问题综述的issue</a>，将这些问题做了汇总。但问题还需一个一个解决，在Go 1.19这个版本中，Michael Knyszek就带来了他的<a href="https://github.com/golang/proposal/blob/master/design/48409-soft-memory-limit.md">Soft memory limit的解决方案</a>。</p>
<p>这个方案在runtime/debug包中添加了一个名为SetMemoryLimit的函数以及GOMEMLIMIT环境变量，通过他们任意一个都可以设定Go应用的Memory limit。</p>
<p>一旦设定了Memory limit，当Go堆大小达到“Memory limit减去非堆内存后的值”时，一轮GC会被触发。即便你手动关闭了GC(GOGC=off)，GC亦是会被触发。</p>
<p>通过原理我们可以看到，这个特性最直接解决的就是oom-killed这个问题！就像前面pacer问题示意图中的那个例子，如果我们设定了一个比hard memory limit小一些的soft memory limit的值，那么在(3)那个点便不会出现oom-killed，因为在那之前soft memory limit就会触发一次GC，将一些无用的堆内存回收掉了。</p>
<p>但我们也要注意：soft memory limit不保证不会出现oom-killed，这个也很好理解。如果live heap object到达limit了，说明你的应用内存资源真的不够了，是时候扩内存条资源了，这个是GC无论如何都无法解决的问题。</p>
<p>但如果一个Go应用的live heap object超过了soft memory limit但还尚未被kill，那么此时GC会被持续触发，但为了保证在这种情况下业务依然能继续进行，soft memory limit方案保证GC最多只会使用50%的CPU算力，以保证业务处理依然能够得到cpu资源。</p>
<p>对于GC触发频率高，要降低GC频率的情况，soft memory limit的方案就是<strong>关闭GC(GOGC=off)</strong>，这样GC只有当堆内存到达soft memory limit值时才会触发，可以提升cpu利用率。不过有一种情况，<a href="https://go.dev/doc/gc-guide">Go官方的GC guide</a>中不建议你这么做，那就是当你的Go程序与其他程序共享一些有限的内存时。这时只需保留内存限制并将其设置为一个较小的合理值即可，因为它可能有助于抑制不良的瞬时行为。</p>
<p>那么多大的值是合理的soft memory limit值呢？在Go服务独占容器资源时，一个好的经验法则是留下额外的5-10%的空间，以考虑Go运行时不知道的内存来源。uber在其博客中设定的limit为资源上限的70%，也是一个不错的经验值。</p>
<h3>四. 小结</h3>
<p>也许Go 1.19因开发周期的压缩给大家带来的惊喜并不多。不过特性虽少，却都很实用，比如上面的soft memory limit，一旦用好，便可以帮助大家解决大问题。</p>
<p>而拥有正常开发周期的Go 1.20已经处于积极的开发中，从目前<a href="https://github.com/golang/go/milestone/250">里程碑</a>中规划的功能和改进来看，Go泛型语法将得到进一步的补全，向着完整版迈进，就这一点就值得大家期待了！</p>
<h3>五. 参考资料</h3>
<ul>
<li>Russ Cox内存模型系列 &#8211; https://research.swtch.com/mm</li>
<li>关于Go内存模型的讨论 &#8211; https://github.com/golang/go/discussions/47141</li>
<li>How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programs- https://www.microsoft.com/en-us/research/publication/make-multiprocessor-computer-correctly-executes-multiprocess-programs</li>
<li>A Tutorial Introduction to the ARM and POWER Relaxed Memory Models- https://www.cl.cam.ac.uk/~pes20/ppc-supplemental/test7.pdf</li>
<li>Weak Ordering &#8211; A New Definition- https://people.eecs.berkeley.edu/~kubitron/courses/cs258-S08/handouts/papers/adve-isca90.pdf</li>
<li>Foundations of the C++ Concurrency Memory Model &#8211; https://www.hpl.hp.com/techreports/2008/HPL-2008-56.pdf</li>
<li>Go GC pacer原理 &#8211;  https://docs.google.com/document/d/1wmjrocXIWTr1JxU-3EQBI6BK6KgtiFArkG47XK73xIQ/edit</li>
</ul>
<hr />
<p><a href="https://wx.zsxq.com/dweb2/index/group/51284458844544">“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}" /><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><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>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</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/08/22/some-changes-in-go-1-19/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Go 1.17中值得关注的几个变化</title>
		<link>https://tonybai.com/2021/08/17/some-changes-in-go-1-17/</link>
		<comments>https://tonybai.com/2021/08/17/some-changes-in-go-1-17/#comments</comments>
		<pubDate>Tue, 17 Aug 2021 13:21:59 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Add]]></category>
		<category><![CDATA[Array]]></category>
		<category><![CDATA[closure]]></category>
		<category><![CDATA[Compiler]]></category>
		<category><![CDATA[generics]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go-module]]></category>
		<category><![CDATA[go.mod]]></category>
		<category><![CDATA[go1.11]]></category>
		<category><![CDATA[go1.17]]></category>
		<category><![CDATA[go1.18]]></category>
		<category><![CDATA[GOARCH]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[gomodule]]></category>
		<category><![CDATA[GopherCon]]></category>
		<category><![CDATA[Gopher部落]]></category>
		<category><![CDATA[gotest]]></category>
		<category><![CDATA[Inline]]></category>
		<category><![CDATA[loong64]]></category>
		<category><![CDATA[module-graph]]></category>
		<category><![CDATA[Pointer]]></category>
		<category><![CDATA[register]]></category>
		<category><![CDATA[runtime]]></category>
		<category><![CDATA[Slice]]></category>
		<category><![CDATA[Stack]]></category>
		<category><![CDATA[stdlib]]></category>
		<category><![CDATA[time]]></category>
		<category><![CDATA[typeparameter]]></category>
		<category><![CDATA[unsafe]]></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>
		<category><![CDATA[龙芯]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=3253</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2021/08/17/some-changes-in-go-1-17 Go核心开发团队在去年GopherCon大会上给Go泛型的定调是在2022年2月份的Go 1.18版本中发布，那可是自Go诞生以来语法规范变动最大的一次，这让包括笔者在内的全世界的Gopher们都满怀期待。 不过别忘了，在Go 1.18这个“网红版本”发布前，还有一个“实力派”版本Go 1.17呢！美国当地时间2021年8月16日，Go 1.17版本在经过两个RC版本之后正式发布！并且值得庆幸的是Go 1.17版本并没有过多受到Go 1.18版本这个“网红”的影响，Go 1.17默默地加入和优化了着实不少的特性。在这一篇文章中，我们就来看看Go 1.17版本中有哪些值得关注的变化。 1. 语言特性变化 Go属于那种极简的语言，从诞生到现在语言自身特性变化很小，不会像其他主流语言那样走“你有的我也要有”的特性融合路线。因此新语言特性对于Gopher来说属于“稀缺品”，属于“供不应求”那类事物^_^。这也直接导致了每次Go新版本发布，我们都要首先看看语言特性是否有变更，每个新加入语言的特性都值得我们去投入更多关注，去深入研究。Go 1.17在语言特性层面做了两方面的小改动，下面我们来看看。 第一个是对语言类型转换规则的扩展，允许从切片到数组指针的转换，下面的代码在Go 1.17版本中是可以正常编译和运行的： // github.com/bigwhite/experiments/tree/master/go1.17-examples/lang/slice2arrayptr/main.go func slice2arrayptr() { var b = []int{11, 12, 13} var p = (*[3]int)(b) p[1] = p[1] + 10 fmt.Printf("%v\n", b) // [11 22 13] } Go通过运行时对这类切片到数组指针的转换代码做检查，如果发现越界行为，就会通过运行时panic予以处理。Go运行时实施检查的一条原则就是“转换后的数组长度不能大于原切片的长度”，注意这里是切片的长度(len)，而不是切片的容量(cap)。 第二个变动则是unsafe包增加了两个函数：Add与Slice。使用这两个函数可以让开发人员更容易地写出符合unsafe包使用的安全规则的代码。这两个函数原型如下： // $GOROOT/src/unsafe.go func Add(ptr Pointer, len IntegerType) [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/go-1.17-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2021/08/17/some-changes-in-go-1-17">本文永久链接</a> &#8211; https://tonybai.com/2021/08/17/some-changes-in-go-1-17</p>
<p>Go核心开发团队在<a href="https://mp.weixin.qq.com/s/SMT40557JgQ9FjUkswznlA">去年GopherCon大会上给Go泛型的定调是在2022年2月份的Go 1.18版本中发布</a>，那可是自<a href="https://mp.weixin.qq.com/s/woQeEQUhOLJ7KSE5rm5q6g">Go诞生</a>以来语法规范变动最大的一次，这让包括笔者在内的全世界的Gopher们都满怀期待。</p>
<p>不过别忘了，在Go 1.18这个“网红版本”发布前，还有一个“实力派”版本Go 1.17呢！美国当地时间2021年8月16日，<a href="https://blog.golang.org/go1.17">Go 1.17版本在经过两个RC版本之后正式发布</a>！并且值得庆幸的是Go 1.17版本并没有过多受到Go 1.18版本这个“网红”的影响，Go 1.17默默地加入和优化了着实不少的特性。在这一篇文章中，我们就来看看Go 1.17版本中有哪些值得关注的变化。</p>
<h3>1. 语言特性变化</h3>
<p><a href="https://www.imooc.com/read/87/article/2321">Go属于那种极简的语言</a>，从诞生到现在语言自身特性变化很小，不会像其他主流语言那样走“你有的我也要有”的特性融合路线。因此新语言特性对于Gopher来说属于“稀缺品”，属于“供不应求”那类事物^_^。这也直接导致了每次Go新版本发布，我们都要首先看看语言特性是否有变更，每个新加入语言的特性都值得我们去投入更多关注，去深入研究。Go 1.17在语言特性层面做了两方面的小改动，下面我们来看看。</p>
<p>第一个是对语言类型转换规则的扩展，允许从切片到数组指针的转换，下面的代码在Go 1.17版本中是可以正常编译和运行的：</p>
<pre><code>// github.com/bigwhite/experiments/tree/master/go1.17-examples/lang/slice2arrayptr/main.go
func slice2arrayptr() {
    var b = []int{11, 12, 13}
    var p = (*[3]int)(b)
    p[1] = p[1] + 10
    fmt.Printf("%v\n", b) // [11 22 13]
}
</code></pre>
<p>Go通过运行时对这类切片到数组指针的转换代码做检查，如果发现越界行为，就会通过运行时panic予以处理。Go运行时实施检查的一条原则就是“转换后的数组长度不能大于原切片的长度”，注意这里是切片的长度(len)，而不是切片的容量(cap)。</p>
<p>第二个变动则是unsafe包增加了两个函数：Add与Slice。使用这两个函数可以让开发人员更容易地写出符合<a href="https://pkg.go.dev/unsafe#Pointer">unsafe包使用的安全规则</a>的代码。这两个函数原型如下：</p>
<pre><code>// $GOROOT/src/unsafe.go
func Add(ptr Pointer, len IntegerType) Pointe
func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType
</code></pre>
<p>unsafe.Add允许更安全的指针运算，而unsafe.Slice允许更安全地将底层存储的指针转换为切片。</p>
<h3>2. go module的变化</h3>
<p>自<a href="https://tonybai.com/2018/11/19/some-changes-in-go-1-11/">Go 1.11版本引入go module</a>以来，每个Go大版本发布时，go module都会有不少的积极变化，这是Go核心团队与社区就go module深入互动的结果。</p>
<p>Go 1.17中go module同样有几处显著变化，其中最最重要的一个变化就是pruned module graph（修剪的module依赖图）。Go 1.17之前的版本某个module的依赖图由该module的直接依赖以及所有间接依赖组成，无论某个间接依赖是否真正为原module的构建做出贡献，这样go命令在解决依赖时会读取每个依赖的go.mod，包括那些没有被真正使用到的module，这样形成的module依赖图被称为<strong>完整module依赖图（complete module graph）</strong>。</p>
<p>Go 1.17不再使用“完整module依赖图”，而是引入了pruned module graph（修剪的module依赖图）。修剪的module依赖图就是在完整module依赖图的基础上将那些“占着茅坑不拉屎”、对构建完全没有“贡献”的间接依赖module修剪后的依赖图。使用修剪后的module依赖图进行构建将有助于避免下载或阅读那些不必要的go.mod文件，这样Go命令可以不去获取那些不相关的依赖关系，从而在日常开发中节省时间。</p>
<p>但module依赖图修剪也带来了一个副作用，那就是go.mod文件size的变大。因为Go 1.17版本后，每次go mod tidy（当go.mod中的go版本为1.17时），go命令都会对main module的依赖做一次深度扫描(deepening scan)，并将main module的所有直接和间接依赖都记录在go.mod中（之前说的版本只记录直接依赖）。考虑到内容较多，go 1.17将直接依赖和间接依赖分别放在两个不同的require块儿中。</p>
<h3>3. 编译器与运行时的变化</h3>
<p>Go 1.17增加了对Windows上64位ARM架构的支持，让开发者可以在更多设备上原生运行Go。但这个版本编译器最大的变化是在amd64架构下率先实现了从基于堆栈的调用惯例到<a href="https://go.googlesource.com/proposal/+/master/design/40724-register-calling.md">基于寄存器的调用惯例</a>的<a href="https://github.com/golang/go/issues/40724">切换</a>。</p>
<p>并且，切换到<a href="https://go.googlesource.com/go/+/refs/heads/dev.regabi/src/cmd/compile/internal-abi.md">基于寄存器的调用惯例</a>后，一组有代表性的Go包和程序的基准测试显示，Go程序的运行性能提高了约5%，二进制文件大小典型减少约2%。也就是说你的Go源码使用Go 1.17版本重新编译一下就能获得大约5%的性能提升，真希望这样的优化越多越好！对更多平台的基于寄存器调用惯例的支持将在未来的版本中出现。</p>
<p>除了改为基于寄存器的调用惯例之外，Go 1.17编译器还支持包含<a href="https://mp.weixin.qq.com/s/bldzzSw-X0YdRcmN3fPhaw">闭包</a>的函数的内联(inline)了！这样一来，一个带有闭包的函数可能会在函数被内联的每个地方产生一个不同的闭包代码指针，因此，<br />
<strong>Go函数的值不能直接比较</strong>！</p>
<p>Go编译器还在Go 1.17中引入了//go:build形式的构建约束指示符，以替代原先易错的// +build形式。</p>
<h3>4. 其他变化</h3>
<ul>
<li>保留龙芯架构GOARCH值</li>
</ul>
<p>在Go 1.17版本中，Go编译器保留了中国龙芯cpu架构的GOARCH值 &#8211; loong64。关于龙心GOARCH值选用loong64还是loongarch64还有过<a href="https://github.com/golang/go/issues/46229">一段激烈的争论</a>，最终大多数都赞同的loong64取得了最后的胜利。</p>
<ul>
<li>Go test变化</li>
</ul>
<p>Go test引入-shuffle的洗牌标志位，用以控制单元测试或benchmark的执行顺序。</p>
<p>另外T和B两个类型分别都增加了Setenv方法用于在test和benchmark执行期间设置环境变量。</p>
<ul>
<li>time包增加Time对象的GoString形式输出</li>
</ul>
<p>我们使用%#v输出一个Time对象实例时，Go 1.17之前的版本输出内容如下面：</p>
<p>Go 1.16.5输出：</p>
<pre><code>time.Time{wall:0xc03f08c0d06c9ed0, ext:83078, loc:(*time.Location)(0x11620e0)}
</code></pre>
<p>Go 1.17增加了GoString方法，该方法在Time对象以%#v格式输出时被自动调用，其输出结果如下：</p>
<pre><code>time.Date(2021, time.August, 17, 20, 29, 42, 58245000, time.Local)
</code></pre>
<h3>5. 小结</h3>
<p>除上述变化之外，Go的其他标准库随着新版本的发布也都会有大量的小改动，但每个开发人员对标准库的关注点差别很大，因此，在这个系列中不会详细做说明了，大家还是参考<a href="https://tip.golang.org/doc/go1.17">Go 1.17的发布说明文档</a>各取所需吧^_^。</p>
<p>与传统的“Go新版本值得关注的几个变化”系列有所不同，本期内容较为简单和概括，因为更多内容，我将在后续的<strong>Go 1.17新特性详解系列</strong>中针对上述值得关注的新特性做进一步说明。详解系列已经写好，不过首发在了<a href="https://t.zsxq.com/bAuvBu3">本人运营的星球“Gopher部落”</a>上了，如果你迫切想深入了解这些新特性，可以加入星球阅读。</p>
<p>本文所涉及的源码可以在<a href="https://github.com/bigwhite/experiments/tree/master/go1.17-examples/">这里</a> &#8211; https://github.com/bigwhite/experiments/tree/master/go1.17-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/202103/gopher-tribe-zsxq-card.png" alt="" /></p>
<p>Go技术专栏“<a href="https://www.imooc.com/read/87">改善Go语⾔编程质量的50个有效实践</a>”正在慕课网火热热销中！本专栏主要满足广大gopher关于Go语言进阶的需求，围绕如何写出地道且高质量Go代码给出50条有效实践建议，上线后收到一致好评！欢迎大家订<br />
阅！</p>
<p><img src="https://tonybai.com/wp-content/uploads/imooc-go-column-pgo-with-qr.jpg" 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/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>微信赞赏：<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/08/17/some-changes-in-go-1-17/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go语言对ARM架构的支持与未来[译]</title>
		<link>https://tonybai.com/2020/12/18/go-ports-until-202012/</link>
		<comments>https://tonybai.com/2020/12/18/go-ports-until-202012/#comments</comments>
		<pubDate>Fri, 18 Dec 2020 07:55:39 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[ARM]]></category>
		<category><![CDATA[arm64]]></category>
		<category><![CDATA[CPU]]></category>
		<category><![CDATA[FreeBSD]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go-build]]></category>
		<category><![CDATA[go1.16]]></category>
		<category><![CDATA[GOARCH]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GOOS]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[M1]]></category>
		<category><![CDATA[NetBSD]]></category>
		<category><![CDATA[OpenBSD]]></category>
		<category><![CDATA[plan9]]></category>
		<category><![CDATA[RISC-V]]></category>
		<category><![CDATA[Solaris]]></category>
		<category><![CDATA[SSA]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[x86]]></category>
		<category><![CDATA[x86-64]]></category>
		<category><![CDATA[交叉编译]]></category>
		<category><![CDATA[操作系统]]></category>
		<category><![CDATA[移植性]]></category>
		<category><![CDATA[苹果]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=3041</guid>
		<description><![CDATA[本文翻译自Go官方博客文章《Go on ARM and Beyond》(https://blog.golang.org/ports)。 最近业界关于非x86处理器的讨论沸沸扬扬，所以我们认为值得简单的写一篇关于Go语言对这些非x86处理器的支持情况的文章。 对我们来说，Go的可移植性一直很重要，我们不会过度去适配任何特定的操作系统或架构。Go最初的开源版本包括对两种操作系统（Linux和MacOSX）和三种架构（64位x86、32位x86和32位ARM）的支持。 多年来，我们已经增加了对更多操作系统和架构组合的支持： 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的支持。 Go 1.14（2019年2月）增加了对64位RISC-V上Linux的支持。 虽然x86-64的移植在Go的早期得到了大部分的关注，但今天我们所有的目标架构都得到了我们基于SSA的编译器后端的良好支持，并生成了优秀的代码。我们一路走来得到了许多贡献者的帮助，包括来自Amazon、ARM、Atos、IBM、Intel和MIPS的工程师。 Go支持对所有这些系统进行开箱即用的交叉编译，而且只需付出最小的努力。例如，要在一个64位Linux系统中构建一个基于32位x86的Windows应用，我们只需执行下面命令： GOARCH=386 GOOS=windows go build myapp # 编译生成myapp.exe 在过去的一年里，几家主要的厂商都宣布了用于服务器、笔记本电脑和开发者机器的新ARM64硬件。Go在这些方面适配的很好。多年来，Go一直在ARM64 Linux服务器上为Docker、Kubernetes和Go生态系统的其他部分，以及ARM64 Android和iOS设备上的移动应用提供支持。 自今年夏天苹果宣布Mac过渡到苹果芯片以来，苹果和谷歌一直在合作，以确保Go和更广泛的Go生态系统在其上运行良好，无论是在Rosetta 2下运行Go x86二进制文件，还是运行原生Go ARM64二进制文件。本周早些时候，我们发布了第一个Go 1.16测试版，其中包括了对使用M1芯片的Mac的原生支持。您可以在Go下载页面上下载并试用适用于M1 Mac和所有其他系统的Go 1.16测试版。当然，这是一个测试版，就像所有的测试版一样，它肯定有我们不知道的bug。如果你遇到任何问题，请在golang.org/issue/new上报告）。 在本地开发中使用与生产中相同的CPU架构总是很好的，这样可以消除两种环境之间的差异。如果你部署到ARM64生产服务器上，Go也可以轻松在ARM64 Linux和Mac系统上进行开发。但当然，无论你是在x86系统上工作并部署到ARM上，还是在Windows上工作并部署到Linux上，或者其他组合，在一个系统上工作并交叉编译部署到另一个系统上仍然和以前一样容易。 我们希望添加支持的下一个目标是ARM64 Windows 10系统。如果你有专业知识并愿意提供帮助，我们正在golang.org/issue/36439上协调工作。 “Gopher部落”知识星球开球了！高品质首发Go技术文章，“三天”首发阅读权，每年两期Go语言发展现状分析，每天提前1小时阅读到新鲜的Gopher日报，网课、技术专栏、图书内容前瞻，六小时内必答保证等满足你关于Go语言生态的所有需求！星球首开，福利自然是少不了的！2020年年底之前，8.8折(很吉利吧^_^)加入星球，下方图片扫起来吧！ 我的Go技术专栏：“改善Go语⾔编程质量的50个有效实践”上线了，欢迎大家订阅学习！ [...]]]></description>
			<content:encoded><![CDATA[<p>本文翻译自Go官方博客文章<a href="https://blog.golang.org/ports">《Go on ARM and Beyond》</a>(https://blog.golang.org/ports)。</p>
<p>最近业界关于非x86处理器的讨论沸沸扬扬，所以我们认为值得简单的写一篇关于Go语言对这些非x86处理器的支持情况的文章。</p>
<p>对我们来说，<a href="https://tonybai.com/2017/06/27/an-intro-about-go-portability/">Go的可移植性</a>一直很重要，我们不会过度去适配任何特定的操作系统或架构。<a href="https://opensource.googleblog.com/2009/11/hey-ho-lets-go.html">Go最初的开源版本</a>包括对两种操作系统（Linux和MacOSX）和三种架构（64位x86、32位x86和32位ARM）的支持。</p>
<p>多年来，我们已经增加了对更多操作系统和架构组合的支持：</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>虽然x86-64的移植在Go的早期得到了大部分的关注，但今天我们所有的目标架构都得到了我们<a href="https://www.youtube.com/watch?v=uTMvKVma5ms">基于SSA的编译器后端</a>的良好支持，并生成了优秀的代码。我们一路走来得到了许多贡献者的帮助，包括来自Amazon、ARM、Atos、IBM、Intel和MIPS的工程师。</p>
<p>Go支持对所有这些系统进行开箱即用的<a href="https://tonybai.com/2014/10/20/cross-compilation-with-golang/">交叉编译</a>，而且只需付出最小的努力。例如，要在一个64位Linux系统中构建一个基于32位x86的Windows应用，我们只需执行下面命令：</p>
<pre><code>GOARCH=386 GOOS=windows go build myapp  # 编译生成myapp.exe
</code></pre>
<p>在过去的一年里，几家主要的厂商都宣布了用于服务器、笔记本电脑和开发者机器的新ARM64硬件。Go在这些方面适配的很好。多年来，Go一直在ARM64 Linux服务器上为Docker、Kubernetes和Go生态系统的其他部分，以及ARM64 Android和iOS设备上的移动应用提供支持。</p>
<p>自今年夏天苹果宣布Mac过渡到苹果芯片以来，苹果和谷歌一直在合作，以确保Go和更广泛的Go生态系统在其上运行良好，无论是在Rosetta 2下运行Go x86二进制文件，还是运行原生Go ARM64二进制文件。本周早些时候，我们发布了第一个Go 1.16测试版，其中包括了对使用M1芯片的Mac的原生支持。您可以在<a href="https://golang.google.cn/dl/#go1.16beta1">Go下载页面</a>上下载并试用适用于M1 Mac和所有其他系统的Go 1.16测试版。当然，这是一个测试版，就像所有的测试版一样，它肯定有我们不知道的bug。如果你遇到任何问题，请在golang.org/issue/new上报告）。</p>
<p>在本地开发中使用与生产中相同的CPU架构总是很好的，这样可以消除两种环境之间的差异。如果你部署到ARM64生产服务器上，Go也可以轻松在ARM64 Linux和Mac系统上进行开发。但当然，无论你是在x86系统上工作并部署到ARM上，还是在Windows上工作并部署到Linux上，或者其他组合，在一个系统上工作并交叉编译部署到另一个系统上仍然和以前一样容易。</p>
<p>我们希望添加支持的下一个目标是ARM64 Windows 10系统。如果你有专业知识并愿意提供帮助，我们正在golang.org/issue/36439上协调工作。</p>
<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>”上线了，欢迎大家订阅学习！</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商用消息平台”，全面支持5GRCS消息。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1coreCPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687开启你的DO主机之路。</p>
<p>GopherDaily(Gopher每日新闻)归档仓库-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/18/go-ports-until-202012/feed/</wfw:commentRss>
		<slash:comments>1</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 1.14中值得关注的几个变化</title>
		<link>https://tonybai.com/2020/03/08/some-changes-in-go-1-14/</link>
		<comments>https://tonybai.com/2020/03/08/some-changes-in-go-1-14/#comments</comments>
		<pubDate>Sun, 08 Mar 2020 09:17:28 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[cleanup]]></category>
		<category><![CDATA[Compiler]]></category>
		<category><![CDATA[defer]]></category>
		<category><![CDATA[epoll]]></category>
		<category><![CDATA[escape-analysis]]></category>
		<category><![CDATA[fuzz-test]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go-fuzz]]></category>
		<category><![CDATA[go.mod]]></category>
		<category><![CDATA[go1.12]]></category>
		<category><![CDATA[go1.13]]></category>
		<category><![CDATA[go1.14]]></category>
		<category><![CDATA[GOARCH]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GOOS]]></category>
		<category><![CDATA[GOPATH]]></category>
		<category><![CDATA[Gopher]]></category>
		<category><![CDATA[GOROOT]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[https]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Module]]></category>
		<category><![CDATA[netpoll]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[Pointer]]></category>
		<category><![CDATA[RISC-V]]></category>
		<category><![CDATA[runtime]]></category>
		<category><![CDATA[Rust]]></category>
		<category><![CDATA[Scheduler]]></category>
		<category><![CDATA[String]]></category>
		<category><![CDATA[struct]]></category>
		<category><![CDATA[syscall]]></category>
		<category><![CDATA[Testing]]></category>
		<category><![CDATA[TIBOE]]></category>
		<category><![CDATA[Timer]]></category>
		<category><![CDATA[uintptr]]></category>
		<category><![CDATA[Unicode]]></category>
		<category><![CDATA[Unix]]></category>
		<category><![CDATA[unsafe]]></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>
		<category><![CDATA[逃逸分析]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=2854</guid>
		<description><![CDATA[可能是得益于2020年2月26日Go 1.14的发布，在2020年3月份的TIOBE编程语言排行榜上，Go重新进入TOP 10，而去年同期Go仅排行在第18位。虽然Go语言以及其他主流语言在榜单上的“上蹿下跳”让这个榜单的权威性饱受质疑:)，但Go在这样的一个时间节点能进入TOP 10，对于Gopher和Go社区来说，总还是一个不错的结果。并且在一定层度上说明：Go在努力耕耘十年后，已经在世界主流编程语言之林中牢牢占据了自己的一个位置。 图：TIOBE编程语言排行榜2020.3月榜单，Go语言重入TOP10 Go自从宣布Go1 Compatible后，直到这次的Go 1.14发布，Go的语法和核心库都没有做出不兼容的变化。这让很多其他主流语言的拥趸们觉得Go很“无趣”。但这种承诺恰恰是Go团队背后努力付出的结果，因此Go的每个发布版本都值得广大gopher尊重，每个发布版本都是Go团队能拿出的最好版本。 下面我们就来解读一下Go 1.14的变化，看看这个新版本中有哪些值得我们重点关注的变化。 一. 语言规范 和其他主流语言相比，Go语言的语法规范的变化那是极其少的（广大Gopher们已经习惯了这个节奏:)），偶尔发布一个变化，那自然是要引起广大Gopher严重关注的:)。不过事先说明：只要Go版本依然是1.x，那么这个规范变化也是backward-compitable的。 Go 1.14新增的语法变化是：嵌入接口的方法集可重叠。这个变化背后的朴素思想是这样的。看下面代码(来自这里)： type I interface { f(); String() string } type J interface { g(); String() string } type IJ interface { I; J } ----- (1) type IJ interface { f(); g(); String() string } ---- (2) 代码中已知定义的I和J两个接口的方法集中都包含有String() string这个方法。在这样的情况下，我们如果想定义一个方法集合为Union(I, J)的新接口IJ，我们在Go 1.13及之前的版本中只能使用第(2)种方式，即只能在新接口IJ中重新书写一遍所有的方法原型，而无法像第(1)种方式那样使用嵌入接口的简洁方式进行。 [...]]]></description>
			<content:encoded><![CDATA[<p>可能是得益于2020年2月26日<a href="https://blog.golang.org/go1.14">Go 1.14的发布</a>，在2020年3月份的<a href="https://tiobe.com/tiobe-index/">TIOBE编程语言排行榜</a>上，Go重新进入TOP 10，而去年同期Go仅排行在第18位。虽然<a href="https://tonybai.com/tag/go">Go语言</a>以及其他主流语言在榜单上的“上蹿下跳”让这个榜单的权威性饱受质疑:)，但Go在这样的一个时间节点能进入TOP 10，对于Gopher和Go社区来说，总还是一个不错的结果。并且在一定层度上说明：<a href="https://tonybai.com/2019/11/09/go-opensource-10-years/">Go在努力耕耘十年</a>后，已经在世界主流编程语言之林中牢牢占据了自己的一个位置。</p>
<p><img src="https://tonybai.com/wp-content/uploads/some-changes-in-go-1-14-1.png" alt="img{512x368}" /></p>
<p><center> 图：TIOBE编程语言排行榜2020.3月榜单，Go语言重入TOP10</center></p>
<p>Go自从宣布<a href="https://tip.golang.org/doc/go1compat.html">Go1 Compatible</a>后，直到这次的Go 1.14发布，Go的语法和核心库都没有做出不兼容的变化。这让很多其他主流语言的拥趸们觉得Go很“无趣”。但这种承诺恰恰是Go团队背后努力付出的结果，因此Go的每个发布版本都值得广大gopher尊重，<strong>每个发布版本都是Go团队能拿出的最好版本</strong>。</p>
<p>下面我们就来解读一下Go 1.14的变化，看看这个新版本中有哪些值得我们重点关注的变化。</p>
<h2>一. 语言规范</h2>
<p>和其他主流语言相比，Go语言的语法规范的变化那是极其少的（广大Gopher们已经习惯了这个节奏:)），偶尔发布一个变化，那自然是要引起广大Gopher严重关注的:)。不过事先说明：<strong>只要Go版本依然是1.x，那么这个规范变化也是backward-compitable的</strong>。</p>
<p>Go 1.14新增的语法变化是：<a href="https://github.com/golang/proposal/blob/master/design/6977-overlapping-interfaces.md">嵌入接口的方法集可重叠</a>。这个变化背后的朴素思想是这样的。看下面代码(来自<a href="https://github.com/golang/go/issues/6977">这里</a>)：</p>
<pre><code>type I interface { f(); String() string }
type J interface { g(); String() string }

type IJ interface { I; J }  ----- (1)
type IJ interface { f(); g(); String() string }  ---- (2)

</code></pre>
<p>代码中已知定义的I和J两个接口的方法集中都包含有<code>String() string</code>这个方法。在这样的情况下，我们如果想定义一个方法集合为Union(I, J)的新接口<code>IJ</code>，我们在Go 1.13及之前的版本中只能使用第(2)种方式，即只能在新接口<code>IJ</code>中重新书写一遍所有的方法原型，而无法像第(1)种方式那样使用嵌入接口的简洁方式进行。</p>
<p>Go 1.14通过支持<a href="https://github.com/golang/proposal/blob/master/design/6977-overlapping-interfaces.md">嵌入接口的方法集可重叠</a>解决了这个问题：</p>
<pre><code>// go1.14-examples/overlapping_interface.go
package foo

type I interface {
    f()
    String() string
}
type J interface {
    g()
    String() string
}

type IJ interface {
    I
    J
}

在go 1.13.6上运行：

$go build overlapping_interface.go
# command-line-arguments
./overlapping_interface.go:14:2: duplicate method String

但在go 1.14上运行：

$go build overlapping_interface.go

// 一切ok，无报错

</code></pre>
<p>不过对overlapping interface的支持仅限于接口定义中，如果你要在struct定义中嵌入interface，比如像下面这样：</p>
<pre><code>// go1.14-examples/overlapping_interface1.go
package main

type I interface {
    f()
    String() string
}

type implOfI struct{}

func (implOfI) f() {}
func (implOfI) String() string {
    return "implOfI"
}

type J interface {
    g()
    String() string
}

type implOfJ struct{}

func (implOfJ) g() {}
func (implOfJ) String() string {
    return "implOfJ"
}

type Foo struct {
    I
    J
}

func main() {
    f := Foo{
        I: implOfI{},
        J: implOfJ{},
    }
    println(f.String())
}

</code></pre>
<p>虽然Go编译器<strong>没有直接指出结构体Foo中嵌入的两个接口I和J存在方法的重叠</strong>，但在使用Foo结构体时，下面的编译器错误肯定还是会给出的：</p>
<pre><code>$ go run overlapping_interface1.go
# command-line-arguments
./overlapping_interface1.go:37:11: ambiguous selector f.String

</code></pre>
<p>对于结构体中嵌入的接口的方法集是否存在overlap，go编译器似乎并没有严格做“实时”检查，这个检查被延迟到为结构体实例选择method的执行者环节了，就像上面例子那样。如果我们此时让Foo结构体 override一个String方法，那么即便I和J的方法集存在overlap也是无关紧要的，因为编译器不会再模棱两可，可以正确的为Foo实例选出究竟执行哪个String方法：</p>
<pre><code>// go1.14-examples/overlapping_interface2.go

.... ....

func (Foo) String() string {
        return "Foo"
}

func main() {
        f := Foo{
                I: implOfI{},
                J: implOfJ{},
        }
        println(f.String())
}

运行该代码：

$go run overlapping_interface2.go
Foo

</code></pre>
<h2>二.  Go运行时</h2>
<h3>1. 支持异步抢占式调度</h3>
<p>在<a href="https://tonybai.com/2017/11/23/the-simple-analysis-of-goroutine-schedule-examples">《Goroutine调度实例简要分析》</a>一文中，我曾提到过这样一个例子：</p>
<pre><code>// go1.14-examples/preemption_scheduler.go
package main

import (
    "fmt"
    "runtime"
    "time"
)

func deadloop() {
    for {
    }
}

func main() {
    runtime.GOMAXPROCS(1)
    go deadloop()
    for {
        time.Sleep(time.Second * 1)
        fmt.Println("I got scheduled!")
    }
}

</code></pre>
<p>在只有一个<code>P</code>的情况下，上面的代码中deadloop所在goroutine将持续占据该<code>P</code>，使得main goroutine中的代码得不到调度(GOMAXPROCS=1的情况下)，因此我们无法看到<code>I got scheduled!</code>字样输出。这是因为Go 1.13及以前的版本的抢占是”协作式“的，只在有函数调用的地方才能插入“抢占”代码(埋点)，而deadloop没有给编译器插入抢占代码的机会。这会导致GC在等待所有goroutine停止时等待时间过长，从而<a href="https://github.com/golang/go/issues/10958">导致GC延迟</a>；甚至在一些特殊情况下，导致在STW（stop the world）时死锁。</p>
<p>Go 1.14采用了基于系统信号的异步抢占调度，这样上面的deadloop所在的goroutine也可以被抢占了：</p>
<pre><code>// 使用Go 1.14版本编译器运行上述代码

$go run preemption_scheduler.go
I got scheduled!
I got scheduled!
I got scheduled!

</code></pre>
<p>不过由于系统信号可能在代码执行到任意地方发生，在Go runtime能cover到的地方，Go runtime自然会处理好这些系统信号。但是如果你是通过<code>syscall</code>包或<code>golang.org/x/sys/unix</code>在Unix/Linux/Mac上直接进行系统调用，那么一旦在系统调用执行过程中进程收到系统中断信号，这些系统调用就会失败，并以<strong>EINTR错误</strong>返回，尤其是低速系统调用，包括：读写特定类型文件(管道、终端设备、网络设备)、进程间通信等。在这样的情况下，我们就需要自己处理<strong>EINTR错误</strong>。一个最常见的错误处理方式就是重试。对于可重入的系统调用来说，在<a href="http://man7.org/linux/man-pages/man7/signal.7.html">收到EINTR信号后的重试是安全的</a>。如果你没有自己调用syscall包，那么异步抢占调度对你已有的代码几乎无影响。</p>
<p>Go 1.14的异步抢占调度在windows/arm, darwin/arm, js/wasm, and plan9/&#42;上依然尚未支持，Go团队<a href="https://github.com/golang/go/issues/36365">计划在Go 1.15中解决掉这些问题</a>。</p>
<h3>2. defer性能得以继续优化</h3>
<p>在<a href="https://tonybai.com/2019/10/27/some-changes-in-go-1-13/">Go 1.13</a>中，defer性能得到理论上30%的提升。我们还用那个例子来看看go 1.14与go 1.13版本相比defer性能又有多少提升，同时再看看使用defer和不使用defer的对比：</p>
<pre><code>// go1.14-examples/defer_benchmark_test.go
package defer_test

import "testing"

func sum(max int) int {
    total := 0
    for i := 0; i &lt; max; i++ {
        total += i
    }

    return total
}

func foo() {
    defer func() {
        sum(10)
    }()

    sum(100)
}

func Bar() {
    sum(100)
    sum(10)
}

func BenchmarkDefer(b *testing.B) {
    for i := 0; i &lt; b.N; i++ {
        foo()
    }
}
func BenchmarkWithoutDefer(b *testing.B) {
    for i := 0; i &lt; b.N; i++ {
        Bar()
    }
}

</code></pre>
<p>我们分别用Go 1.13和Go 1.14运行上面的基准测试代码：</p>
<pre><code>Go 1.13:

$go test -bench . defer_benchmark_test.go
goos: darwin
goarch: amd64
BenchmarkDefer-8              17873574            66.7 ns/op
BenchmarkWithoutDefer-8       26935401            43.7 ns/op
PASS
ok      command-line-arguments    2.491s

Go 1.14:

$go test -bench . defer_benchmark_test.go
goos: darwin
goarch: amd64
BenchmarkDefer-8              26179819            45.1 ns/op
BenchmarkWithoutDefer-8       26116602            43.5 ns/op
PASS
ok      command-line-arguments    2.418s
</code></pre>
<p>我们看到，Go 1.14的defer性能照比Go 1.13还有大幅提升，并且已经与不使用defer的性能相差无几了，这也是Go官方鼓励大家在性能敏感的代码执行路径上也大胆使用defer的原因。</p>
<p><img src="https://tonybai.com/wp-content/uploads/some-changes-in-go-1-14-3.png" alt="img{512x368}" /></p>
<p><center>图：各个Go版本defer性能对比(图来自于https://twitter.com/janiszt/status/1215601972281253888)</center></p>
<h3>3. internal timer的重新实现</h3>
<p>鉴于go timer<a href="https://github.com/golang/go/issues/6239">长期以来性能不能令人满意</a>，Go 1.14几乎重新实现了runtime层的timer。其实现思路遵循了<a href="https://github.com/golang/go/issues/6239#issuecomment-206361959">Dmitry Vyukov几年前提出的实现逻辑</a>：将timer分配到每个P上，降低锁竞争；去掉timer thread，减少上下文切换开销；使用netpoll的timeout实现timer机制。</p>
<pre><code>// $GOROOT/src/runtime/time.go

type timer struct {
        // If this timer is on a heap, which P's heap it is on.
        // puintptr rather than *p to match uintptr in the versions
        // of this struct defined in other packages.
        pp puintptr

}

// addtimer adds a timer to the current P.
// This should only be called with a newly created timer.
// That avoids the risk of changing the when field of a timer in some P's heap,
// which could cause the heap to become unsorted.

func addtimer(t *timer) {
        // when must never be negative; otherwise runtimer will overflow
        // during its delta calculation and never expire other runtime timers.
        if t.when &lt; 0 {
                t.when = maxWhen
        }
        if t.status != timerNoStatus {
                badTimer()
        }
        t.status = timerWaiting

        addInitializedTimer(t)
}

// addInitializedTimer adds an initialized timer to the current P.
func addInitializedTimer(t *timer) {
        when := t.when

        pp := getg().m.p.ptr()
        lock(&amp;pp.timersLock)
        ok := cleantimers(pp) &amp;&amp; doaddtimer(pp, t)
        unlock(&amp;pp.timersLock)
        if !ok {
                badTimer()
        }

        wakeNetPoller(when)
}
... ...

</code></pre>
<p>这样你的程序中如果大量使用time.After、time.Tick或者在处理网络连接时大量使用SetDeadline，使用Go 1.14编译后，你的应用将得到timer性能的<a href="https://github.com/golang/go/commit/6becb033341602f2df9d7c55cc23e64b925bbee2">自然提升</a>。</p>
<p><img src="https://tonybai.com/wp-content/uploads/some-changes-in-go-1-14-2.png" alt="img{512x368}" /></p>
<p><center>图：切换到新timer实现后的各Benchmark数据</center></p>
<h2>三. Go module已经production ready了</h2>
<p>Go 1.14中带来的关于<a href="https://tonybai.com/2019/12/21/go-modules-minimal-version-selection/">go module</a>的最大惊喜就是Go module已经production ready了，这意味着关于go module的运作机制，go tool的各种命令和其参数形式、行为特征已趋稳定了。笔者从<a href="https://tonybai.com/2018/11/19/some-changes-in-go-1-11/">Go 1.11</a>引入<a href="https://tonybai.com/2018/07/15/hello-go-module/">go module</a>以来就一直关注和使用Go module，尤其是<a href="https://tonybai.com/2019/10/27/some-changes-in-go-1-13/">Go 1.13</a>中增加go module proxy的支持，使得中国大陆的gopher再也不用为获取类似<code>golang.org/x/xxx</code>路径下的module而苦恼了。</p>
<p>Go 1.14中go module的主要变动如下：</p>
<p>a) module-aware模式下对vendor的处理：如果go.mod中go version是go 1.14及以上，且当前repo顶层目录下有vendor目录，那么go工具链将默认使用vendor(即-mod=vendor)中的package，而不是module cache中的($GOPATH/pkg/mod下)。同时在这种模式下，go 工具会校验vendor/modules.txt与go.mod文件，它们需要保持同步，否则报错。</p>
<p>在上述前提下，如要非要使用module cache构建，则需要为go工具链显式传入<code>-mod=mod</code> ，比如：<code>go build -mod=mod ./...</code>。</p>
<p>b) 增加GOINSECURE，可以不再要求非得以https获取module，或者即便使用https，也不再对server证书进行校验。</p>
<p>c) 在module-aware模式下，如果没有建立go.mod或go工具链无法找到go.mod，那么你必须显式传入要处理的go源文件列表，否则go tools将需要你明确go.mod。比如：在一个没有go.mod的目录下，要编译一个hello.go，我们需要使用go build hello.go(hello.go需要显式放在命令后面），如果你执行<code>go build .</code>就会得到类似如下错误信息：</p>
<pre><code>$go build .
go: cannot find main module, but found .git/config in /Users/tonybai
    to create a module there, run:
    cd .. &amp;&amp; go mod init

</code></pre>
<p>也就是说在没有go.mod的情况下，go工具链的功能是受限的。</p>
<p>d) go module支持subversion仓库了，不过subversion使用应该很“小众”了。</p>
<p>要系统全面的了解go module的当前行为机制，建议还是通读一遍<a href="https://tip.golang.org/cmd/go/">Go command手册</a>中关于module的说明以及官方<a href="https://github.com/golang/go/wiki/Modules">go module wiki</a>。</p>
<h2>四. 编译器</h2>
<p>Go 1.14 go编译器在-race和-msan的情况下，默认会执行<code>-d=checkptr</code>，即对unsafe.Pointer的使用进行<a href="https://github.com/golang/go/issues/34964">合法性检查</a>，主要检查两项内容：</p>
<ul>
<li>当将unsafe.Pointer转型为<code>*T</code>时，T的内存对齐系数不能高于原地址的</li>
</ul>
<p>比如下面代码：</p>
<pre><code>// go1.14-examples/compiler_checkptr1.go
package main

import (
    "fmt"
    "unsafe"
)

func main() {
    var byteArray = [10]byte{'a', 'b', 'c'}
    var p *int64 = (*int64)(unsafe.Pointer(&amp;byteArray[1]))
    fmt.Println(*p)
}

</code></pre>
<p>以-race运行上述代码：</p>
<pre><code>$go run -race compiler_checkptr1.go
fatal error: checkptr: unsafe pointer conversion

goroutine 1 [running]:
runtime.throw(0x11646fd, 0x23)
    /Users/tonybai/.bin/go1.14/src/runtime/panic.go:1112 +0x72 fp=0xc00004cee8 sp=0xc00004ceb8 pc=0x106d152
runtime.checkptrAlignment(0xc00004cf5f, 0x1136880, 0x1)
    /Users/tonybai/.bin/go1.14/src/runtime/checkptr.go:13 +0xd0 fp=0xc00004cf18 sp=0xc00004cee8 pc=0x1043b70
main.main()
    /Users/tonybai/go/src/github.com/bigwhite/experiments/go1.14-examples/compiler_checkptr1.go:10 +0x70 fp=0xc00004cf88 sp=0xc00004cf18 pc=0x11283b0
runtime.main()
    /Users/tonybai/.bin/go1.14/src/runtime/proc.go:203 +0x212 fp=0xc00004cfe0 sp=0xc00004cf88 pc=0x106f7a2
runtime.goexit()
    /Users/tonybai/.bin/go1.14/src/runtime/asm_amd64.s:1373 +0x1 fp=0xc00004cfe8 sp=0xc00004cfe0 pc=0x109b801
exit status 2

</code></pre>
<p>checkptr检测到：转换后的int64类型的内存对齐系数严格程度要高于转化前的原地址(一个byte变量的地址)。int64对齐系数为8，而一个byte变量地址对齐系数仅为1。</p>
<ul>
<li>做完指针算术后，转换后的unsafe.Pointer仍应指向原先Go堆对象</li>
</ul>
<pre><code>compiler_checkptr2.go
package main

import (
    "unsafe"
)

func main() {
    var n = 5
    b := make([]byte, n)
    end := unsafe.Pointer(uintptr(unsafe.Pointer(&amp;b[0])) + uintptr(n+10))
    _ = end
}

</code></pre>
<p>运行上述代码：</p>
<pre><code>$go run  -race compiler_checkptr2.go
fatal error: checkptr: unsafe pointer arithmetic

goroutine 1 [running]:
runtime.throw(0x10b618b, 0x23)
    /Users/tonybai/.bin/go1.14/src/runtime/panic.go:1112 +0x72 fp=0xc00003e720 sp=0xc00003e6f0 pc=0x1067192
runtime.checkptrArithmetic(0xc0000180b7, 0xc00003e770, 0x1, 0x1)
    /Users/tonybai/.bin/go1.14/src/runtime/checkptr.go:41 +0xb5 fp=0xc00003e750 sp=0xc00003e720 pc=0x1043055
main.main()
    /Users/tonybai/go/src/github.com/bigwhite/experiments/go1.14-examples/compiler_checkptr2.go:10 +0x8d fp=0xc00003e788 sp=0xc00003e750 pc=0x1096ced
runtime.main()
    /Users/tonybai/.bin/go1.14/src/runtime/proc.go:203 +0x212 fp=0xc00003e7e0 sp=0xc00003e788 pc=0x10697e2
runtime.goexit()
    /Users/tonybai/.bin/go1.14/src/runtime/asm_amd64.s:1373 +0x1 fp=0xc00003e7e8 sp=0xc00003e7e0 pc=0x1092581
exit status 2

</code></pre>
<p>checkptr检测到转换后的unsafe.Pointer已经超出原先heap object: b的范围了，于是报错。</p>
<p>不过目前Go标准库依然<a href="https://github.com/golang/go/issues/34972">尚未能完全通过checkptr的检查</a>，因为有些库代码显然违反了<a href="https://tip.golang.org/pkg/unsafe/#Pointer">unsafe.Pointer的使用规则</a>。</p>
<p><a href="https://tonybai.com/2019/10/27/some-changes-in-go-1-13/">Go 1.13</a>引入了新的Escape Analysis，Go 1.14中我们可以通过<code>-m=2</code>查看详细的逃逸分析过程日志，比如：</p>
<pre><code>$go run  -gcflags '-m=2' compiler_checkptr2.go
# command-line-arguments
./compiler_checkptr2.go:7:6: can inline main as: func() { var n int; n = 5; b := make([]byte, n); end := unsafe.Pointer(uintptr(unsafe.Pointer(&amp;b[0])) + uintptr(n + 100)); _ = end }
./compiler_checkptr2.go:9:11: make([]byte, n) escapes to heap:
./compiler_checkptr2.go:9:11:   flow: {heap} = &amp;{storage for make([]byte, n)}:
./compiler_checkptr2.go:9:11:     from make([]byte, n) (non-constant size) at ./compiler_checkptr2.go:9:11
./compiler_checkptr2.go:9:11: make([]byte, n) escapes to heap

</code></pre>
<h2>五. 标准库</h2>
<p>每个Go版本，变化最多的就是标准库，这里我们挑一个可能影响后续我们编写单元测试行为方式的变化说说，那就是testing包的T和B类型都增加了自己的<a href="https://tip.golang.org/pkg/testing/#T.Cleanup">Cleanup方法</a>。我们通过代码来看一下Cleanup方法的作用：</p>
<pre><code>// go1.14-examples/testing_cleanup_test.go
package main

import "testing"

func TestCase1(t *testing.T) {

    t.Run("A=1", func(t *testing.T) {
        t.Logf("subtest1 in testcase1")

    })
    t.Run("A=2", func(t *testing.T) {
        t.Logf("subtest2 in testcase1")
    })
    t.Cleanup(func() {
        t.Logf("cleanup1 in testcase1")
    })
    t.Cleanup(func() {
        t.Logf("cleanup2 in testcase1")
    })
}

func TestCase2(t *testing.T) {
    t.Cleanup(func() {
        t.Logf("cleanup1 in testcase2")
    })
    t.Cleanup(func() {
        t.Logf("cleanup2 in testcase2")
    })
}
</code></pre>
<p>运行上面测试：</p>
<pre><code>$go test -v testing_cleanup_test.go
=== RUN   TestCase1
=== RUN   TestCase1/A=1
    TestCase1/A=1: testing_cleanup_test.go:8: subtest1 in testcase1
=== RUN   TestCase1/A=2
    TestCase1/A=2: testing_cleanup_test.go:12: subtest2 in testcase1
    TestCase1: testing_cleanup_test.go:18: cleanup2 in testcase1
    TestCase1: testing_cleanup_test.go:15: cleanup1 in testcase1
--- PASS: TestCase1 (0.00s)
    --- PASS: TestCase1/A=1 (0.00s)
    --- PASS: TestCase1/A=2 (0.00s)
=== RUN   TestCase2
    TestCase2: testing_cleanup_test.go:27: cleanup2 in testcase2
    TestCase2: testing_cleanup_test.go:24: cleanup1 in testcase2
--- PASS: TestCase2 (0.00s)
PASS
ok      command-line-arguments    0.005s
</code></pre>
<p>我们看到：</p>
<ul>
<li>
<p>Cleanup方法运行于所有测试以及其子测试完成之后。</p>
</li>
<li>
<p>Cleanup方法类似于defer，先注册的cleanup函数后执行（比如上面例子中各个case的cleanup1和cleanup2）。</p>
</li>
</ul>
<p>在拥有Cleanup方法前，我们经常像下面这样做：</p>
<pre><code>// go1.14-examples/old_testing_cleanup_test.go
package main

import "testing"

func setup(t *testing.T) func() {
    t.Logf("setup before test")
    return func() {
        t.Logf("teardown/cleanup after test")
    }
}

func TestCase1(t *testing.T) {
    f := setup(t)
    defer f()
    t.Logf("test the testcase")
}
</code></pre>
<p>运行上面测试：</p>
<pre><code>$go test -v old_testing_cleanup_test.go
=== RUN   TestCase1
    TestCase1: old_testing_cleanup_test.go:6: setup before test
    TestCase1: old_testing_cleanup_test.go:15: test the testcase
    TestCase1: old_testing_cleanup_test.go:8: teardown/cleanup after test
--- PASS: TestCase1 (0.00s)
PASS
ok      command-line-arguments    0.005s
</code></pre>
<p>有了Cleanup方法后，我们就不需要再像上面那样单独编写一个返回cleanup函数的setup函数了。</p>
<p>此次Go 1.14还将对unicode标准的支持从unicode 11 升级到 unicode 12 ，共增加了554个新字符。</p>
<h2>六. 其他</h2>
<p>超强的<a href="http://tonybai.com/2017/06/27/an-intro-about-go-portability/">可移植性</a>是Go的一个知名标签，在新平台支持方面，Go向来是“急先锋”。Go 1.14为64bit RISC-V提供了在linux上的实验性支持(GOOS=linux, GOARCH=riscv64)。</p>
<p>rust语言已经通过<a href="https://github.com/rust-fuzz/cargo-fuzz">cargo-fuzz</a>从工具层面为fuzz test提供了基础支持。Go 1.14也在这方面<a href="https://github.com/golang/go/issues/14565">做出了努力</a>，并且Go已经在向<a href="https://github.com/golang/go/issues/19109">将fuzz test变成Go test的一等公民</a>而努力。</p>
<h2>七. 小结</h2>
<p>Go 1.14的详细变更说明在<a href="https://tip.golang.org/doc/go1.14">这里</a>可以查看。整个版本的milestone对应的issue集合在<a href="https://github.com/golang/go/milestone/95?closed=1">这里</a>。</p>
<p>不过目前Go 1.14在特定版本linux内核上会出现crash的问题，当然这个问题源于这些内核的一个已知bug。在<a href="https://github.com/golang/go/issues/37436#issuecomment-591327351">这个issue</a>中有关于这个问题的详细说明，涉及到的Linux内核版本包括：5.2.x, 5.3.0-5.3.14, 5.4.0-5.4.1。<br />
本篇博客涉及的代码在<a href="https://github.com/bigwhite/experiments/tree/master/go1.14-examples">这里</a>可以下载。</p>
<hr />
<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>著名云主机服务厂商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/03/08/some-changes-in-go-1-14/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Go 1.11中值得关注的几个变化</title>
		<link>https://tonybai.com/2018/11/19/some-changes-in-go-1-11/</link>
		<comments>https://tonybai.com/2018/11/19/some-changes-in-go-1-11/#comments</comments>
		<pubDate>Mon, 19 Nov 2018 14:09:48 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[athens]]></category>
		<category><![CDATA[debugger]]></category>
		<category><![CDATA[delve]]></category>
		<category><![CDATA[dep]]></category>
		<category><![CDATA[error-handling]]></category>
		<category><![CDATA[generics]]></category>
		<category><![CDATA[Git]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go.mod]]></category>
		<category><![CDATA[go.sum]]></category>
		<category><![CDATA[go1.11]]></category>
		<category><![CDATA[go1.12]]></category>
		<category><![CDATA[GO111MODULE]]></category>
		<category><![CDATA[Go2]]></category>
		<category><![CDATA[GOARCH]]></category>
		<category><![CDATA[gocmpp]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[golang.org]]></category>
		<category><![CDATA[GOOS]]></category>
		<category><![CDATA[GOPATH]]></category>
		<category><![CDATA[Gopher]]></category>
		<category><![CDATA[GopherCon]]></category>
		<category><![CDATA[GOPROXY]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[macos]]></category>
		<category><![CDATA[Module]]></category>
		<category><![CDATA[node]]></category>
		<category><![CDATA[node.js]]></category>
		<category><![CDATA[Package]]></category>
		<category><![CDATA[RobPike]]></category>
		<category><![CDATA[vendor]]></category>
		<category><![CDATA[vgo]]></category>
		<category><![CDATA[WebAssembly]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[兼容性]]></category>
		<category><![CDATA[调试器]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=2655</guid>
		<description><![CDATA[转眼间又近年底，距8月25日Go 1.11版本正式发布已过去快三个月了。由于种种原因，Go语言发布变化系列的Go 1.11版本没能及时放出。近期网课发布上线后，个人时间压力稍缓和。又恰看到近期Go 1.12 release note的initial version已经加入到master，于是这篇文章便上升到个人Todo list的Top3的位置，我也尽一切可能的碎片时间收集素材，撰写文章内容。这个时候谈Go 1.11，总有炒“冷饭”的嫌疑，虽然这碗饭还有一定温度^_^。 一. Go 1.11版本的重要意义 在Go 1.11版本之前的Go user官方调查中，Gopher抱怨最多的三大问题如下： 包依赖管理 缺少泛型 错误处理 而Go 1.11开启了问题1：包依赖管理解决的实验。这表明了社区的声音在影响Go语言演化的过程中扮演着日益重要的角色了。 同时，Go 1.11是Russ Cox在GopherCon 2017大会上发表 “Toward Go2&#8243;之后的第一个Go版本，是为后续“Go2”的渐进落地奠定基础的一个版本。 二. Go 1.11版本变化概述 在”Go2&#8243;声音日渐响亮的今天，兼容性(compatibility)也依旧是Go team考虑的Go语言演化的第一原则，这一点通过Rob Pike在9月份的Go Sydney Meetup上的有关Go 2 Draft Specifications的Talk可以证明(油管视频)。 兼容性依然是”Go2&#8243;的第一考虑 Go 1.11也一如既往版本那样，继续遵守着Go1兼容协议，这意味使用从Go1.0到Go1.10编写的代码理论上依旧可以通过Go 1.11版本编译并正常运行。 随着Go 1.11版本的发布，一些老版本的操作系统将不再被支持，比如Windows XP、macOS 10.9.x等。不被支持不意味着完全不能用，只是Go 1.11在这些老旧os上运行时出现问题将不被官方support了。同时根据Go的release support规定，Go 1.11发布也同时意味着Go 1.9版本将和之前的older go release版本一样，官方将不再提供支持了(关键bug fix、security problem fix等)。 Go [...]]]></description>
			<content:encoded><![CDATA[<p>转眼间又近年底，距8月25日<a href="https://github.com/golang/go/releases/tag/go1.11">Go 1.11版本</a>正式发布已过去快三个月了。由于种种原因，<a href="https://tonybai.com/tag/golang">Go语言发布变化系列</a>的Go 1.11版本没能及时放出。近期<a href="https://tonybai.com/2018/10/17/imooc-course-kubernetes-practice-go-online/">网课发布上线</a>后，个人时间压力稍缓和。又恰看到近期<a href="https://github.com/golang/go/commit/02aa1aeeb1baf9bcfb8b9eeff9c92e93426ae512">Go 1.12 release note的initial version已经加入到master</a>，于是这篇文章便上升到个人Todo list的Top3的位置，我也尽一切可能的碎片时间收集素材，撰写文章内容。这个时候谈Go 1.11，总有炒“冷饭”的嫌疑，虽然这碗饭还有一定温度^_^。</p>
<h2>一. Go 1.11版本的重要意义</h2>
<p>在Go 1.11版本之前的<a href="https://blog.golang.org/survey2017-results">Go user官方调查</a>中，Gopher抱怨最多的三大问题如下：</p>
<ul>
<li>包依赖管理</li>
<li>缺少泛型</li>
<li>错误处理</li>
</ul>
<p>而Go 1.11开启了问题1：<strong>包依赖管理</strong>解决的实验。这表明了社区的声音在影响Go语言演化的过程中扮演着日益重要的角色了。</p>
<p>同时，<a href="https://github.com/golang/go/releases/tag/go1.11">Go 1.11</a>是<a href="https://research.swtch.com/">Russ Cox</a>在<a href="https://www.gophercon.com/">GopherCon</a> 2017大会上发表 <a href="https://blog.golang.org/toward-go2">“Toward Go2&#8243;</a>之后的第一个Go版本，是为后续<a href="https://github.com/golang/go/wiki/Go2">“Go2”</a>的渐进落地奠定基础的一个版本。</p>
<h2>二. Go 1.11版本变化概述</h2>
<p>在”Go2&#8243;声音日渐响亮的今天，兼容性(compatibility)也依旧是Go team考虑的Go语言演化的第一原则，这一点通过Rob Pike在9月份的<a href="https://www.meetup.com/golang-syd/">Go Sydney Meetup</a>上的有关<a href="https://blog.golang.org/go2draft">Go 2 Draft Specifications</a>的<a href="https://www.youtube.com/watch?v=RIvL2ONhFBI">Talk</a>可以证明(油管视频)。</p>
<p><img src="https://tonybai.com/wp-content/uploads/go1.11-go2-primary-concern-by-robpike.png" alt="img{512x368}" /><br />
兼容性依然是”Go2&#8243;的第一考虑</p>
<p>Go 1.11也一如既往版本那样，继续遵守着<a href="https://golang.org/doc/go1compat.html">Go1兼容协议</a>，这意味使用从Go1.0到<a href="https://tonybai.com/2018/02/17/some-changes-in-go-1-10/">Go1.10</a>编写的代码理论上依旧可以通过Go 1.11版本编译并正常运行。</p>
<p>随着Go 1.11版本的发布，一些老版本的操作系统将不再被支持，比如Windows XP、macOS 10.9.x等。不被支持不意味着完全不能用，只是Go 1.11在这些老旧os上运行时出现问题将不被官方support了。同时根据Go的<a href="https://golang.org/doc/devel/release.html">release support规定</a>，Go 1.11发布也同时意味着Go 1.9版本将和之前的older go release版本一样，官方将不再提供支持了(关键bug fix、security problem fix等)。</p>
<p>Go 1.11中为近两年逐渐兴起的<a href="https://en.wikipedia.org/wiki/RISC-V">RISC-V</a>cpu架构预留了GOARCH值：riscv和riscv64。</p>
<p>Go 1.11中为调试器增加了一个新的实验功能，那就是允许在调试过程中动态调用Go函数，比如在断点处调用String方法等。<a href="https://github.com/derekparker/delve">Delve</a> 1.1.0及以上版本可以使用该功能。</p>
<p>在运行时方面，Go 1.11使用了一个稀疏heap布局，这样就去掉了以往Go heap最大512G的限制。</p>
<p>通过Go 1.11编译的Go程序一般来说性能都会有小幅的提升。对于使用math/big包的程序或arm64架构上的Go程序而言，这次的提升尤为明显。</p>
<p>Go 1.11中最大的变化莫过于两点：</p>
<ul>
<li>module机制的实验性引入，以试图解决长久以来困扰Gopher们的包依赖问题；</li>
<li>增加对<a href="https://webassembly.org/">WebAssembly</a>的支持，这样以后Gopher们可以通过Go语言编写前端应用了。</li>
</ul>
<p>Go 1.11的change很多，这是core team和社区共同努力的结果。但在我这个系列文章中，我们只能详细关注少数重要的变化。下面我们就来稍微详细地说说go module和go support WebAssembly这两个显著的变化。</p>
<h2>三. go module</h2>
<p>在Go 1.11 beta2版本发布之前，我曾经基于当时的Go tip版本撰写了一篇 <a href="https://tonybai.com/2018/07/15/hello-go-module/">《初窥go module》</a>的文章，重点描述了go module的实现机制，包括<a href="https://research.swtch.com/vgo-import">Semantic Import Versioning</a>、<a href="https://research.swtch.com/vgo-mvs">Minimal Version Selection</a>等，因此对go module（前身为<a href="https://github.com/golang/vgo">vgo</a>)是什么以及实现机制感兴趣的小伙伴儿们可以先移步到那篇文章了解。在这里我将通过为一个<a href="https://github.com/bigwhite/gocmpp">已存在的repo</a>添加go.mod的方式来描述go module。</p>
<p>这里我们使用的是<a href="https://github.com/golang/go/releases/tag/go1.11.2">go 1.11.2版本</a>，repo为<a href="https://github.com/bigwhite/gocmpp">gocmpp</a>。注意：我们没有显式设置GO111MODULE的值，这样只有在GOPATH之外的路径下，且当前路径下有go.mod或子路径下有go.mod文件时，go compiler才进入module-aware模式(相比较于gopath模式)。</p>
<h3>1. 初始化go.mod</h3>
<p>我们先把gocmpp clone到gopath之外的一个路径下：</p>
<pre><code># git clone https://github.com/bigwhite/gocmpp.git
Cloning into 'gocmpp'...
remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Total 950 (delta 0), reused 0 (delta 0), pack-reused 949
Receiving objects: 100% (950/950), 3.85 MiB | 0 bytes/s, done.
Resolving deltas: 100% (396/396), done.
Checking connectivity... done.
</code></pre>
<p>在应用go module之前，我们先来在传统的gopath模式下build一次：</p>
<pre><code># go build
connect.go:24:2: cannot find package "github.com/bigwhite/gocmpp/utils" in any of:
    /root/.bin/go1.11.2/src/github.com/bigwhite/gocmpp/utils (from $GOROOT)
    /root/go/src/github.com/bigwhite/gocmpp/utils (from $GOPATH)
</code></pre>
<p>正如我们所料，由于处于GOPATH外面，且GO111MODULE并未显式设置，Go compiler会尝试在当前目录或子目录下查找go.mod，如果没有go.mod文件，则会采用传统gopath模式编译，即在$GOPATH/src下面找相关的import package，因此失败。</p>
<p>下面我们通过建立go.mod，将编译mode切换为module-aware mode。</p>
<p>我们通过go mod init命令来为gocmpp创建go.mod文件：</p>
<pre><code># go mod init github.com/bigwhite/gocmpp
go: creating new go.mod: module github.com/bigwhite/gocmpp

# cat go.mod
module github.com/bigwhite/gocmpp
</code></pre>
<p>我们看到，go mod init命令在当前目录下创建一个go.mod文件，内有一行内容，描述了该module为 github.com/bigwhite/gocmpp。</p>
<p>我们再来构建一下gocmpp：</p>
<pre><code># go build
go: finding golang.org/x/text/transform latest
go: finding golang.org/x/text/encoding/unicode latest
go: finding golang.org/x/text/encoding/simplifiedchinese latest
go: finding golang.org/x/text v0.3.0
go: finding golang.org/x/text/encoding latest
go: downloading golang.org/x/text v0.3.0
</code></pre>
<p>由于当前目录下有了go.mod文件，go compiler将工作在module-aware模式下，自动分析gocmpp的依赖、确定gocmpp依赖包的初始版本，并下载这些版本的依赖包缓存到特定目录下（目前是存放在$GOPATH/pkg/mod下面）</p>
<pre><code># cat go.mod
module github.com/bigwhite/gocmpp

require golang.org/x/text v0.3.0
</code></pre>
<p>我们看到go.mod中多了一行信息：<strong>“require golang.org/x/text v0.3.0&#8243;</strong>。这就是gocmpp这个module所依赖的第三方包以及经过go compiler初始分析确定使用的版本(v0.3.0)。</p>
<h3>2. 用于verify的go.sum</h3>
<p>go build后，当前目录下还多出了一个go.sum文件。</p>
<pre><code># cat go.sum
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
</code></pre>
<p>go.sum记录每个依赖库的版本和对应的内容的校验和(一个哈希值)。每当增加一个依赖项时，如果go.sum中没有，则会将该依赖项的版本和内容校验和添加到go.sum中。go命令会使用这些校验和与缓存在本地的依赖包副本元信息(比如：$GOPATH/pkg/mod/cache/download/golang.org/x/text/@v下面的v0.3.0.ziphash)进行比对校验。</p>
<p>如果我修改了$GOPATH/pkg/mod/cache/download/golang.org/x/text/@v/v0.3.0.ziphash中的值，那么当我执行下面verify命令时会报错：</p>
<pre><code># go mod verify
golang.org/x/text v0.3.0: zip has been modified (/root/go/pkg/mod/cache/download/golang.org/x/text/@v/v0.3.0.zip)
golang.org/x/text v0.3.0: dir has been modified (/root/go/pkg/mod/golang.org/x/text@v0.3.0)
</code></pre>
<p>如果没有“恶意”修改，则verify会报成功：</p>
<pre><code># go mod verify
all modules verified
</code></pre>
<h3>3. 用why解释为何依赖，给出依赖路径</h3>
<p>go.mod中的依赖项由go相关命令自动生成和维护。但是如果开发人员想知道为什么会依赖某个package，可以通过go mod why命令来查询原因。go mod why命令默认会给出一个main包到要查询的packge的最短依赖路径。如果go mod why使用 -m flag，则后面的参数将被看成是module，并给出main包到每个module中每个package的最短依赖路径（如果依赖的话）：</p>
<p>下面我们通过go mod why命令查看一下gocmpp module到 golang.org/x/oauth2和golang.org/x/exp两个包是否有依赖：</p>
<pre><code># go mod why golang.org/x/oauth2 golang.org/x/exp
go: finding golang.org/x/oauth2 latest
go: finding golang.org/x/exp latest
go: downloading golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288
go: downloading golang.org/x/exp v0.0.0-20181112044915-a3060d491354
go: finding golang.org/x/net/context/ctxhttp latest
go: finding golang.org/x/net/context latest
go: finding golang.org/x/net latest
go: downloading golang.org/x/net v0.0.0-20181114220301-adae6a3d119a
# golang.org/x/oauth2
(main module does not need package golang.org/x/oauth2)

# golang.org/x/exp
(main module does not need package golang.org/x/exp)
</code></pre>
<p>通过结尾几行的输出日志，我们看到gocmpp的main package没有对golang.org/x/oauth2和golang.org/x/exp两个包产生任何依赖。</p>
<p>我们加上-m flag再来执行一遍：</p>
<pre><code># go mod why -m golang.org/x/oauth2 golang.org/x/exp
# golang.org/x/oauth2
(main module does not need module golang.org/x/oauth2)

# golang.org/x/exp
(main module does not need module golang.org/x/exp)
</code></pre>
<p>同样是没有依赖的输出结果，但是输出日志中使用的是module，而不是package字样。说明go mod why将golang.org/x/oauth2和golang.org/x/exp视为module了。</p>
<p>我们再来查询一下对golang.org/x/text的依赖：</p>
<pre><code># go mod why golang.org/x/text
# golang.org/x/text
(main module does not need package golang.org/x/text)

# go mod why -m golang.org/x/text
# golang.org/x/text
github.com/bigwhite/gocmpp/utils
golang.org/x/text/encoding/simplifiedchinese
</code></pre>
<p>我们看到，如果-m flag不开启，那么gocmpp main package没有对golang.org/x/text的依赖路径；如果-m flag开启，则golang.org/x/text被视为module，go mod why会检查gocmpp main package到module: golang.org/x/text下面所有package是否有依赖路径。这里我们看到gocmpp main package依赖了golang.org/x/text module下面的golang.org/x/text/encoding/simplifiedchinese这个package，并给出了最短依赖路径。</p>
<h3>4. 清理go.mod和go.sum中的条目：go mod tidy</h3>
<p>经过上述操作后，我们再来看看go.mod中的内容：</p>
<pre><code># cat go.mod
module github.com/bigwhite/gocmpp

require (
    github.com/dvyukov/go-fuzz v0.0.0-20181106053552-383a81f6d048
    golang.org/x/net v0.0.0-20181114220301-adae6a3d119a // indirect
    golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288 // indirect
    golang.org/x/text v0.3.0
)
</code></pre>
<p>我们发现go.mod中require block增加了许多条目，显然我们的gocmpp并没有依赖到golang.org/x/oauth2和golang.org/x/net中的任何package。我们要清理一下go.mod，使其与gocmpp源码中的第三方依赖的真实情况保持一致，我们使用go mod tidy命令：</p>
<pre><code># go mod tidy
# cat go.mod
module github.com/bigwhite/gocmpp

require (
    github.com/dvyukov/go-fuzz v0.0.0-20181106053552-383a81f6d048
    golang.org/x/text v0.3.0
)

# cat go.sum
github.com/dvyukov/go-fuzz v0.0.0-20181106053552-383a81f6d048 h1:3O5zXlWvrRdioniMPz8pW+pGi+BNEFRtVhvj0GnknbQ=
github.com/dvyukov/go-fuzz v0.0.0-20181106053552-383a81f6d048/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

</code></pre>
<p>我们看到：执行完tidy命令后，go.mod和go.sum都变得简洁了，里面的每一个条目都是gocmpp所真实依赖的package/module的信息。</p>
<h3>5. 对依赖包的版本进行“升降级”(upgrade或downgrade)</h3>
<p>如果对go mod init初始选择的依赖包版本不甚满意，或是第三方依赖包有更新的版本发布，我们日常开发工作中都会进行对对依赖包的版本进行“升降级”(upgrade或downgrade)的操作。在go module模式下，如何来做呢？由于go.mod和go.sum是由go compiler管理的，这里不建议手工去修改go.mod中require中module的版本号。我们可以通过<a href="https://golang.org/cmd/go/#hdr-Modules__module_versions__and_more">module-aware的go get命令</a>来实现我们的目的。</p>
<p>我们先来查看一下golang.org/x/text都有哪些版本可用：</p>
<pre><code># go list -m -versions golang.org/x/text
golang.org/x/text v0.1.0 v0.2.0 v0.3.0
</code></pre>
<p>我们选择将golang.org/x/text从v0.3.0降级到v0.1.0：</p>
<pre><code># go get golang.org/x/text@v0.1.0
go: finding golang.org/x/text v0.1.0
go: downloading golang.org/x/text v0.1.0
</code></pre>
<p>降级后，我们test一下：</p>
<pre><code># go test
PASS
ok      github.com/bigwhite/gocmpp    0.003s
</code></pre>
<p>我们这时再看看go.mod和go.sum：</p>
<pre><code># cat go.mod
module github.com/bigwhite/gocmpp

require (
    github.com/dvyukov/go-fuzz v0.0.0-20181106053552-383a81f6d048
    golang.org/x/text v0.1.0
)

# cat go.sum
github.com/dvyukov/go-fuzz v0.0.0-20181106053552-383a81f6d048 h1:3O5zXlWvrRdioniMPz8pW+pGi+BNEFRtVhvj0GnknbQ=
github.com/dvyukov/go-fuzz v0.0.0-20181106053552-383a81f6d048/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
golang.org/x/text v0.1.0 h1:LEnmSFmpuy9xPmlp2JeGQQOYbPv3TkQbuGJU3A0HegU=
golang.org/x/text v0.1.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
</code></pre>
<p>go.mod中依赖的golang.org/x/text已经从v0.3.0自动变成了v0.1.0了。go.sum中也增加了golang.org/x/text v0.1.0的条目，不过v0.3.0的条目依旧存在。我们可以通过go mod tidy清理一下：</p>
<pre><code># go mod tidy
# cat go.sum
github.com/dvyukov/go-fuzz v0.0.0-20181106053552-383a81f6d048 h1:3O5zXlWvrRdioniMPz8pW+pGi+BNEFRtVhvj0GnknbQ=
github.com/dvyukov/go-fuzz v0.0.0-20181106053552-383a81f6d048/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
golang.org/x/text v0.1.0 h1:LEnmSFmpuy9xPmlp2JeGQQOYbPv3TkQbuGJU3A0HegU=
golang.org/x/text v0.1.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
</code></pre>
<p>go 1.11中的go get也是支持两套工作模式的: 一套是传统gopath mode的；一套是module-aware的。</p>
<p>如果我们在gopath之外的路径，且该路径下没有go.mod，那么go get还是回归gopath mode:</p>
<pre><code># go get golang.org/x/text@v0.1.0
go: cannot use path@version syntax in GOPATH mode
</code></pre>
<p>而module-aware的go get在前面已经演示过了，这里就不重复演示了。</p>
<p>在module-aware模式下，go get -u会更新依赖，升级到依赖的最新minor或patch release。比如：我们在gocmpp module root path下执行：</p>
<pre><code># go get -u golang.org/x/text
# cat go.mod
module github.com/bigwhite/gocmpp

require (
    github.com/dvyukov/go-fuzz v0.0.0-20181106053552-383a81f6d048
    golang.org/x/text v0.3.0 //恢复到0.3.0
)
</code></pre>
<p>我们看到刚刚降级回v0.1.0的依赖项又自动变回v0.3.0了（注意仅minor号变更）。</p>
<p>如果仅仅要升级patch号，而不升级minor号，可以使用go get -u=patch A 。比如：如果golang.org/x/text有v0.1.1版本，那么go get -u=patch golang.org/x/text会将go.mod中的text后面的版本号变为v0.1.1，而不是v0.3.0。</p>
<p>如果go get后面不接具体package，则go get仅针对于main package。</p>
<p>处于module-aware工作模式下的go get更新某个依赖(无论是升版本还是降版本)时，会自动计算并更新其间接依赖的包的版本。</p>
<h3>6. 兼容go 1.11之前版本的reproduceable build: 使用vendor</h3>
<p>处于module-aware mode下的go compiler是完全不理会vendor目录的存在的，go compiler只会使用$GOPATH/pkg/mod下(当前go mod缓存的包是放在这个位置，也许将来会更换位置)缓存的第三方包的特定版本进行编译构建。那么这样一来，对于采用go 1.11之前版本的go compiler来说，reproduceable build就失效了。</p>
<p>为此，go mod提供了vendor子命令，可以根据依赖在module顶层目录自动生成vendor目录：</p>
<pre><code># go mod vendor -v
# github.com/dvyukov/go-fuzz v0.0.0-20181106053552-383a81f6d048
github.com/dvyukov/go-fuzz/gen
# golang.org/x/text v0.3.0
golang.org/x/text/encoding/simplifiedchinese
golang.org/x/text/encoding/unicode
golang.org/x/text/transform
golang.org/x/text/encoding
golang.org/x/text/encoding/internal
golang.org/x/text/encoding/internal/identifier
golang.org/x/text/internal/utf8internal
golang.org/x/text/runes
</code></pre>
<p>gopher可以将vendor目录提交到git repo，这样老版本的go compiler就可以使用vendor进行reproduceable build了。</p>
<p>当然在module-aware mode下，go 1.11 compiler也可以使用vendor进行构建，使用下面命令即可：</p>
<pre><code>go build -mod=vendor

</code></pre>
<p>注意在上述命令中，只有位于module顶层路径的vendor才会起作用。</p>
<h3>7. 国内gopher如何适应go module</h3>
<p>对于国内gopher来说，下载go get package的经历并不是总是那么愉快！尤其是get golang.org/x/xxx路径下的package的时候。以golang.org/x/text为例，在传统的gopath mode下，我们还可以通过下载github.com/golang/text，然后在本地将路径改为golang.org/x/text的方式来获取text相关包。但是在module-aware mode下，对package的下载和本地缓存管理完全由go tool自动完成，国内的gopher们该如何应对呢？</p>
<p>两种方法：<br />
1. 用go.mod中的replace语法，将golang.org/x/text指向本地另外一个目录下已经下载好的github.com/golang/text<br />
2. 使用GOPROXY</p>
<p>方法1显然具有临时性，本地改改第三方依赖库代码，用于调试还可以；第二种方法显然是正解，我们通过一个proxy来下载那些在qiang外的package。Microsoft工程师开源的<a href="https://github.com/gomods/athens">athens项目</a>正是一个用于这个用途的go proxy工具。不过限于篇幅，这里就不展开说明了。我将在后续文章详细谈谈 go proxy的，尤其是使用athens实现go proxy的详细方案。</p>
<h2>四. 对WebAssembly的支持</h2>
<h3>1. 简介</h3>
<p>由于长期在<strong>后端</strong>浸淫，对javascript、<a href="https://webassembly.org/">WebAssembly</a>等前端的技能了解不多，因此这里对Go支持WebAssembly也就能介绍个梗概。下图是对Go支持WebAssembly的一个粗浅的理解：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go1.11-webassembly.png" alt="img{512x368}" /></p>
<p>我们看到满足WebAssembly标准要求的wasm运行于browser之上，类比于一个amd64架构的binary program运行于linux操作系统之上。我们在x86-64的linux上执行go build，实质执行的是:</p>
<pre><code>GOOS=linux GOARCH=amd64 go build ...
</code></pre>
<p>因此为了将Go源码编译为wasm，我们需要执行：</p>
<pre><code>GOOS=js GOARCH=wasm go build ...
</code></pre>
<p>同时， <em>_js.go和 *_wasm.go这样的文件也和</em>_linux.go、*_amd64.go一样，会被go compiler做特殊处理。</p>
<h3>2. 一个hello world级别的WebAssembly的例子</h3>
<p>例子来自<a href="https://github.com/golang/go/wiki/WebAssembly">Go官方Wiki</a>，代码结构如下：</p>
<pre><code>/Users/tony/test/Go/wasm/hellowasm git:(master) ✗ $tree
.
├── hellowasm.go
├── index.html
└── server.go
</code></pre>
<p>hellowasm.go是最终wasm应用对应的源码：</p>
<pre><code>// hellowasm.go

package main

import "fmt"

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

</code></pre>
<p>我们先将其编译为wasm文件main.wasm：</p>
<pre><code>$GOOS=js GOARCH=wasm go build -o main.wasm hellowasm.go
$ls -F
hellowasm.go    index.html    main.wasm*    server.go
</code></pre>
<p>接下来我们从Goroot下面copy一个javascript支持文件wasm_exec.js：</p>
<pre><code>cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
</code></pre>
<p>我们建立index.html，并在该文件中使用wasm_exec.js，并加载main.wasm：</p>
<pre><code>//index.html
&lt;html&gt;
        &lt;head&gt;
                &lt;meta charset="utf-8"&gt;
                &lt;script src="wasm_exec.js"&gt;&lt;/script&gt;
                &lt;script&gt;
                        const go = new Go();
                        WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) =&gt; {
                                go.run(result.instance);
                        });
                &lt;/script&gt;
        &lt;/head&gt;
        &lt;body&gt;&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>最后，我们建立server.go，这是一个File server：</p>
<pre><code>//server.go
package main

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

var (
    listen = flag.String("listen", ":8080", "listen address")
    dir    = flag.String("dir", ".", "directory to serve")
)

func main() {
    flag.Parse()
    log.Printf("listening on %q...", *listen)
    err := http.ListenAndServe(*listen, http.FileServer(http.Dir(*dir)))
    log.Fatalln(err)
}

</code></pre>
<p>启动该server:</p>
<pre><code>$go run server.go
2018/11/19 21:19:17 listening on ":8080"...
</code></pre>
<p>打开Chrome浏览器，右键打开Chrome的“检查”页面，访问127.0.0.1:8080，我们将在console（控制台）窗口看到下面内容：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go1.11-webassembly-browser-exec-result.png" alt="img{512x368}" /></p>
<p>我们看到”Hello, WebAssembly”字样输出到console上了！</p>
<h3>3. 使用node.js执行wasm应用</h3>
<p>wasm应用除了可以运行于支持WebAssembly的浏览器上之外，还可以通过node.js运行它。</p>
<p>我的实验环境中安装的node版本是:</p>
<pre><code>$node -v
v9.11.1
</code></pre>
<p>我们删除server.go，然后执行下面命令：</p>
<pre><code>$GOOS=js GOARCH=wasm go run -exec="$(go env GOROOT)/misc/wasm/go_js_wasm_exec" .
Hello, WebAssembly!
</code></pre>
<p>我们看到通过go_js_wasm_exec命令我们成功通过node执行了main.wasm。</p>
<p>不过每次通过go run -exec来执行，命令行太长，不易记住和使用。我们将go_js_wasm_exec放到$PATH下面，然后直接执行go run：</p>
<pre><code> $export PATH=$PATH:"$(go env GOROOT)/misc/wasm"
 $which go_js_wasm_exec
/Users/tony/.bin/go1.11.2/misc/wasm/go_js_wasm_exec
$GOOS=js GOARCH=wasm go run .
Hello, WebAssembly!

</code></pre>
<p>main.wasm同样被node执行，并且这样执行main.wasm程序的命令行长度大大缩短了！</p>
<h2>五. 小结</h2>
<p>从Go 1.11版本开始，Go语言开始驶入“语言演化”的深水区。Go语言究竟该如何演化？如何在保持语言兼容性、社区不分裂的前提下，满足社区对于错误处理、泛型等语法特性的需求，是摆在Go设计者面前的一道难题。但我相信，无论Go如何演化，Go设计者都会始终遵循Go语言安身立命的那几个根本原则，也是大多数Gopher喜欢Go的根本原因：兼容、简单、可读和高效。</p>
<hr />
<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>著名云主机服务厂商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>我的联系方式：</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; 2018, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2018/11/19/some-changes-in-go-1-11/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>
