<?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; CSP</title>
	<atom:link href="http://tonybai.com/tag/csp/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Sat, 30 May 2026 22:13:29 +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 func()，藏着这位 92 岁老人的毕生心血</title>
		<link>https://tonybai.com/2026/03/11/in-memory-of-tony-hoare/</link>
		<comments>https://tonybai.com/2026/03/11/in-memory-of-tony-hoare/#comments</comments>
		<pubDate>Wed, 11 Mar 2026 09:38:28 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[ArchitectureDesign]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[cloudnative]]></category>
		<category><![CDATA[ConcurrentProgramming]]></category>
		<category><![CDATA[CSP]]></category>
		<category><![CDATA[DataFlow]]></category>
		<category><![CDATA[deadlock]]></category>
		<category><![CDATA[EngineeringPhilosophy]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[HighConcurrency]]></category>
		<category><![CDATA[Mutex]]></category>
		<category><![CDATA[PerformanceOptimization]]></category>
		<category><![CDATA[quicksort]]></category>
		<category><![CDATA[RaceCondition]]></category>
		<category><![CDATA[SequentialProcesses]]></category>
		<category><![CDATA[SharedMemory]]></category>
		<category><![CDATA[SystemProgramming]]></category>
		<category><![CDATA[TonyHoare]]></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>
		<category><![CDATA[通信顺序进程]]></category>
		<category><![CDATA[通道]]></category>
		<category><![CDATA[顺序进程]]></category>
		<category><![CDATA[高并发]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=6021</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/03/11/in-memory-of-tony-hoare 大家好，我是Tony Bai。 在这个由代码构建的现代世界里，有些名字如同星辰般指引着航向。但遗憾的是，2026 年 3 月 5 日，其中一颗最明亮的星辰熄灭了。 图灵奖得主、快速排序（Quicksort）发明者、CSP（通信顺序进程）理论之父 Tony Hoare（托尼·霍尔）与世长辞，享年 92 岁。 也许你并不熟悉这个名字。但只要你是一个程序员，你就一定在面试时手写过他发明的快速排序；如果你是一个 Go 开发者，那你每天在键盘上敲下的每一个 go func() 和 make(chan int)，都在调用着他留给这个世界的伟大的遗产。 今天，让我们暂时放下手头的 CRUD，跨越半个世纪的时间洪流，去看看这位非典型天才，是如何用他那近乎神迹的洞察力，赐予了 Go 语言制霸云原生时代的“并发灵魂”。 被“共享内存”支配的黑暗时代 在讲 Tony Hoare 有多伟大之前，我们必须先回忆一下，在他提出那套神级理论之前，程序员们在并发编程的泥潭里经历了怎样暗无天日的挣扎。 随着多核时代的到来，程序需要同时执行多个任务。传统的思路极其简单粗暴：共享内存（Shared Memory）。 一堆线程就像一群饿狼，死死盯着同一块内存区域。为了防止数据被写乱，程序员们被迫发明了互斥锁（Mutex）、信号量（Semaphore）。你必须极其小心地、以上帝视角去加锁、读写、释放锁。 只要你稍有不慎，忘记解锁，或者加锁顺序反了，死锁（Deadlock）和竞态条件（Race Condition） 就会像幽灵一样找上门来。程序在本地跑得好好的，一上生产环境就离奇崩溃，且极难复现、极难调试。 那是一个属于并发编程的“黑暗时代”。天下程序员苦“共享内存与锁”久矣，却找不到破局之法。 从古典哲学到“六便士的赌注” 就在整个计算机科学界在锁的泥潭里打滚时，Tony Hoare 站了出来。 有趣的是，Tony 并非科班出身。他在大学修读的竟然是古典学与哲学，后来又在皇家海军服役期间接受了高强度的俄语训练。这种看似“不务正业”的跨学科背景，赋予了他极其严密的逻辑思辨能力和哲学视角的解构能力。 他年轻时有个极其经典的轶事：在一家公司打工时，老板让他实现 Shellsort（希尔排序）。Tony 完成任务后，怯生生地对老板说：“我知道一种比这快得多的算法。” 老板不屑一顾：“我跟你赌六便士（大约几毛钱），你肯定不知道！” 于是，Tony 写出了那个后来被印在全世界每一本数据结构教材里的算法——快速排序（Quicksort）。他不仅赢走了那六便士，还顺手改变了世界。 而在面对并发编程的“绝症”时，Tony 再次展现了他哲学般的降维打击能力。 惊世骇俗的 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2026/in-memory-of-tony-hoare-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/03/11/in-memory-of-tony-hoare">本文永久链接</a> &#8211; https://tonybai.com/2026/03/11/in-memory-of-tony-hoare</p>
<p>大家好，我是Tony Bai。</p>
<p>在这个由代码构建的现代世界里，有些名字如同星辰般指引着航向。但遗憾的是，2026 年 3 月 5 日，其中一颗最明亮的星辰熄灭了。</p>
<p>图灵奖得主、快速排序（Quicksort）发明者、CSP（通信顺序进程）理论之父 <a href="https://blog.computationalcomplexity.org/2026/03/tony-hoare-1934-2026.html">Tony Hoare（托尼·霍尔）与世长辞，享年 92 岁</a>。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/in-memory-of-tony-hoare-2.jpg" alt="" /></p>
<p>也许你并不熟悉这个名字。但只要你是一个程序员，你就一定在面试时手写过他发明的快速排序；<strong>如果你是一个 Go 开发者，那你每天在键盘上敲下的每一个 go func() 和 make(chan int)，都在调用着他留给这个世界的伟大的遗产。</strong></p>
<p>今天，让我们暂时放下手头的 CRUD，跨越半个世纪的时间洪流，去看看这位非典型天才，是如何用他那近乎神迹的洞察力，赐予了 Go 语言制霸云原生时代的“并发灵魂”。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/go-concurrency-mental-model-qr.png" alt="" /></p>
<h2>被“共享内存”支配的黑暗时代</h2>
<p>在讲 Tony Hoare 有多伟大之前，我们必须先回忆一下，在他提出那套神级理论之前，程序员们在并发编程的泥潭里经历了怎样暗无天日的挣扎。</p>
<p>随着多核时代的到来，程序需要同时执行多个任务。传统的思路极其简单粗暴：共享内存（Shared Memory）。</p>
<p>一堆线程就像一群饿狼，死死盯着同一块内存区域。为了防止数据被写乱，程序员们被迫发明了互斥锁（Mutex）、信号量（Semaphore）。你必须极其小心地、以上帝视角去加锁、读写、释放锁。</p>
<p>只要你稍有不慎，忘记解锁，或者加锁顺序反了，<strong>死锁（Deadlock）和竞态条件（Race Condition）</strong> 就会像幽灵一样找上门来。程序在本地跑得好好的，一上生产环境就离奇崩溃，且极难复现、极难调试。</p>
<p>那是一个属于并发编程的“黑暗时代”。天下程序员苦“共享内存与锁”久矣，却找不到破局之法。</p>
<h2>从古典哲学到“六便士的赌注”</h2>
<p>就在整个计算机科学界在锁的泥潭里打滚时，Tony Hoare 站了出来。</p>
<p>有趣的是，Tony 并非科班出身。他在大学修读的竟然是<strong>古典学与哲学</strong>，后来又在皇家海军服役期间接受了高强度的<strong>俄语</strong>训练。这种看似“不务正业”的跨学科背景，赋予了他极其严密的逻辑思辨能力和哲学视角的解构能力。</p>
<p>他年轻时有个极其经典的轶事：在一家公司打工时，老板让他实现 Shellsort（希尔排序）。Tony 完成任务后，怯生生地对老板说：“我知道一种比这快得多的算法。” 老板不屑一顾：“我跟你赌六便士（大约几毛钱），你肯定不知道！”</p>
<p>于是，Tony 写出了那个后来被印在全世界每一本数据结构教材里的算法——<strong>快速排序（Quicksort）</strong>。他不仅赢走了那六便士，还顺手改变了世界。</p>
<p>而在面对并发编程的“绝症”时，Tony 再次展现了他哲学般的降维打击能力。</p>
<h2>惊世骇俗的 CSP 理论</h2>
<p>1978 年，Tony Hoare 发表了一篇名为《通信顺序进程》（<a href="https://en.wikipedia.org/wiki/Communicating_sequential_processes">Communicating Sequential Processes</a>, 简称 <strong>CSP</strong>）的学术论文。</p>
<p>宛如一道闪电，这篇论文劈开了并发编程的混沌。</p>
<p>Tony 的哲学思维告诉他：既然共享内存那么容易出错，那我们<strong>干脆就不要共享内存了！</strong></p>
<p>在 CSP 理论中，系统被划分为多个独立的、顺序执行的黑盒（进程）。它们之间没有任何共享状态。当它们需要协作时，唯一的交互方式是通过一条极其明确的管道（Channel）来<strong>“发送和接收消息”</strong>。</p>
<p>这就像是现实生活中的流水线工人：每个人只管自己手头的活（顺序执行），做完了就通过传送带（Channel）递给下一个人。没人去抢同一个零件，自然就不需要打架（加锁）。</p>
<p>这种高度抽象的数学模型，完美地将复杂的并发控制，降维成了简单的数据流动。</p>
<h2>Go 语言与云原生的基石</h2>
<p>理论是伟大的，但在 1978 年，CSP 受限于当时的硬件架构，很难大规模工程化普及。它在学术界的象牙塔里，静静等待着一个能将它发扬光大的使者。</p>
<p>30 年后，谷歌的一间办公室里，Rob Pike、Ken Thompson 等几位大神正被 C++ 的并发折磨得痛不欲生。他们决定<a href="https://tonybai.com/2025/07/03/meet-the-go-team-2012/">创造一门新的语言</a>。</p>
<p>由于 Rob Pike 早年深受 CSP 理论启发，他将 Tony Hoare 的毕生心血，直接刻进了这门新语言的基因里。<strong>这门语言，就是 Go。</strong></p>
<p>Tony Hoare 论文里的晦涩数学模型，在 Go 语言里被具象化为了两个极其优雅的关键字：</p>
<ol>
<li><strong>顺序进程</strong>，演化成了轻量级的 <strong>Goroutine</strong> (go func())。</li>
<li><strong>通信管道</strong>，演化成了强类型的 <strong>Channel</strong> (make(chan int))。</li>
</ol>
<p>Rob Pike 更是将 CSP 的核心思想，提炼成了那句在 Go 圈子里无人不知的至理名言：</p>
<blockquote>
<p><strong>“Do not communicate by sharing memory; instead, share memory by communicating.”</strong><br />
  （不要通过共享内存来通信，而应该通过通信来共享内存。）</p>
</blockquote>
<p>让我们看一眼这被 CSP 灵魂洗礼过的代码，没有任何 sync.Mutex，没有复杂的死锁恐惧，数据的控制权随着流水的管道优雅地传递：</p>
<pre><code class="go">func main() {
    ch := make(chan int) // 创造一条 Tony Hoare 定义的通信管道

    go func() {          // 启动一个 Tony Hoare 定义的顺序进程
        ch &lt;- 42         // 通过通信转移数据
    }()

    fmt.Println(&lt;-ch)    // 完美接收，无需任何锁
}
</code></pre>
<p>Tony Hoare 也许没有预料到，他在半个世纪前写下的论文，会在今天成为支撑全球互联网的基石之一。</p>
<p>当我们谈论云原生时代的 Docker、Kubernetes、Prometheus 时，我们谈论的其实是 Go 语言；而当我们惊叹于 Go 语言能轻松扛起千万级的高并发调度时，我们真正应该感谢的，是底层那个名叫 CSP 的幽灵。</p>
<p>我们每一次扩容容器，底层的字节流都在以 Tony Hoare 所描绘的方式，有条不紊地穿梭于硅片与光纤之间。</p>
<h2>致敬宗师：最好的纪念，是传承他的思想</h2>
<p><a href="https://blog.computationalcomplexity.org/2026/03/tony-hoare-1934-2026.html">Jim Miles 在追忆 Tony 的文章</a>中提到，这位伟大的图灵奖得主极其谦逊。他曾笑着对别人说：<strong>“真正的天才不是一蹴而就的，而是在无数个日夜的深度思考中，为了一个单一问题苦苦挣扎的凡人。”</strong></p>
<p>作为普通的开发者，我们无缘与这位伟人共饮下午茶，或听他亲口讲述那六便士的赌注。但作为工程师，我们对宗师最好的纪念，就是<strong>停止写那些糟糕的、充满死锁风险的并发代码，去真正理解并传承他的设计哲学。</strong></p>
<p>今天，当你再次在 IDE 中敲下那个简短却充满魔力的 go func() 时，请在心底默默向这位智者致敬。</p>
<p>再见了，一代巨匠 Tony Hoare。</p>
<p>您的代码和算法已是不朽。您赐予计算世界的并发灵魂，将伴随着一代又一代的程序员，在无尽的服务器网络中，永不停止地运行下去。</p>
<h2>参考资料</h2>
<ul>
<li>https://en.wikipedia.org/wiki/Communicating_sequential_processes</li>
<li>https://blog.computationalcomplexity.org/2026/03/tony-hoare-1934-2026.html</li>
</ul>
<hr />
<p><strong>今日互动：</strong></p>
<p>你在平时的 Go 开发中，是更喜欢用 Channel（CSP 模型）还是更习惯用 Mutex 锁（共享内存模型）？在并发编程中踩过哪些大坑？</p>
<p>欢迎在评论区分享你的心得！</p>
<hr />
<p><strong>认知跃迁：真正驾驭 Go 的并发灵魂</strong></p>
<p>Tony Hoare 将复杂的并发问题，抽象成了极其优雅的 CSP 理论。但很多 Go 开发者，由于没有看透这层底层哲学，依然在用写 Java/C++（共享内存）的思维来写 Go，最终把 Channel 滥用得一塌糊涂，甚至引发严重的 Goroutine 泄漏。</p>
<p><strong>想要真正吃透 Go 语言的并发灵魂，靠死背语法是绝对不够的。</strong> 你必须深入理解底层调度器（G-M-P 模型）是如何运作的，必须明白何时该用 Channel，何时该退回到 Mutex。</p>
<p>如果你渴望突破并发编程的认知瓶颈，不再只做一个“会调关键字”的熟练工，而是想成为能设计出高可用、极高并发架构的 <strong>Go 资深专家</strong>——</p>
<p>我的极客时间专栏 <a href="http://gk.link/a/12yGY">Go语言进阶课</a> 正是为你量身定制。在这 30+ 讲硬核内容中，我将带你剥开语法糖，直击 Go 并发模型的底层骨架，重塑你的系统级架构审美。</p>
<p>扫描下方二维码，加入专栏。让我们用最扎实的工程实践，去向半个世纪前的伟大思想致敬！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/go-advanced-course-4.png" alt="" /></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><strong>原「Gopher部落」已重装升级为「Go &amp; AI 精进营」知识星球，快来加入星球，开启你的技术跃迁之旅吧！</strong></p>
<p>我们致力于打造一个高品质的 <strong>Go 语言深度学习</strong> 与 <strong>AI 应用探索</strong> 平台。在这里，你将获得：</p>
<ul>
<li><strong>体系化 Go 核心进阶内容:</strong> 深入「Go原理课」、「Go进阶课」、「Go避坑课」等独家深度专栏，夯实你的 Go 内功。</li>
<li><strong>前沿 Go+AI 实战赋能:</strong> 紧跟时代步伐，学习「Go+AI应用实战」、「Agent开发实战课」、「Agentic软件工程课」、「Claude Code开发工作流实战课」、「OpenClaw实战分享」等，掌握 AI 时代新技能。 </li>
<li><strong>星主 Tony Bai 亲自答疑:</strong> 遇到难题？星主第一时间为你深度解析，扫清学习障碍。</li>
<li><strong>高活跃 Gopher 交流圈:</strong> 与众多优秀 Gopher 分享心得、讨论技术，碰撞思想火花。</li>
<li><strong>独家资源与内容首发:</strong> 技术文章、课程更新、精选资源，第一时间触达。</li>
</ul>
<p>衷心希望「Go &amp; AI 精进营」能成为你学习、进步、交流的港湾。让我们在此相聚，享受技术精进的快乐！欢迎你的加入！</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; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/03/11/in-memory-of-tony-hoare/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>像 Go 创始人一样思考：用五大思维原理重学 Go 语言</title>
		<link>https://tonybai.com/2025/12/26/think-like-go-founders-relearn-go-five-principles/</link>
		<comments>https://tonybai.com/2025/12/26/think-like-go-founders-relearn-go-five-principles/#comments</comments>
		<pubDate>Fri, 26 Dec 2025 00:16:06 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[APISemantics]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[CommunicatingSequentialProcesses]]></category>
		<category><![CDATA[CompositionOverInheritance]]></category>
		<category><![CDATA[CSP]]></category>
		<category><![CDATA[Decomposition]]></category>
		<category><![CDATA[DeveloperProductivity]]></category>
		<category><![CDATA[DistributedServices]]></category>
		<category><![CDATA[ErrorAsValue]]></category>
		<category><![CDATA[ErrorHandling]]></category>
		<category><![CDATA[FirstPrinciples]]></category>
		<category><![CDATA[Founders]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[godoc]]></category>
		<category><![CDATA[gofmt]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[gomod]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[gotest]]></category>
		<category><![CDATA[ImplementationDetails]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[KenThompson]]></category>
		<category><![CDATA[LargeScaleCollaboration]]></category>
		<category><![CDATA[MindMap]]></category>
		<category><![CDATA[MultiCoreProcessors]]></category>
		<category><![CDATA[OOP]]></category>
		<category><![CDATA[Package]]></category>
		<category><![CDATA[ParetoPrinciple]]></category>
		<category><![CDATA[Reflection]]></category>
		<category><![CDATA[RobertGriesemer]]></category>
		<category><![CDATA[RobPike]]></category>
		<category><![CDATA[SharedMemory]]></category>
		<category><![CDATA[standardlibrary]]></category>
		<category><![CDATA[struct]]></category>
		<category><![CDATA[StructuralMapping]]></category>
		<category><![CDATA[syscall]]></category>
		<category><![CDATA[toolchain]]></category>
		<category><![CDATA[UnderlyingPrinciples]]></category>
		<category><![CDATA[Visualization]]></category>
		<category><![CDATA[ZoomInAndOut]]></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>
		<category><![CDATA[系统调用]]></category>
		<category><![CDATA[组合优于继承]]></category>
		<category><![CDATA[软件工程]]></category>
		<category><![CDATA[错误处理]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5598</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/12/26/think-like-go-founders-relearn-go-five-principles 大家好，我是Tony Bai。 学习一门新的编程语言时，我们常常陷入“是什么”的迷雾：goroutine 是什么？channel 是什么？interface 是什么？我们记忆语法，模仿示例，却很少追问那个更根本的问题——“为什么”？ 为什么 Go 要被设计成这个样子？ 要回答这个问题，我们需要进行一次“思想上的角色扮演”，回到 Go 语言诞生之前的那个“原点”，像它的创始人们——Rob Pike, Ken Thompson, Robert Griesemer——一样思考。他们并非在“发明”一门新语言，而是在运用一系列深刻的思维原理，为一组棘手的工程问题，构建一个全新的、逻辑自洽的解决方案。 本文，就让我们一起踏上这场“重学 Go”的旅程。我们将带上五大“精英思维原理”作为工具，去看看我们能否“重新推导出”Go 语言的核心设计，并以此重塑我们对这门语言的理解。 第一性原理 (First Principles)：追问 Go 的“为什么” 思维原理：将问题或理念，还原到其最基础、最无可辩驳的元素，并以此为基石进行重构。 这是所有深度思考的起点。在 Go 诞生的 2007 年，Google 的工程师们面临着几个无可辩驳的“基础事实”，这些事实构成了 Go 语言设计的“宇宙大爆炸”奇点： 事实一：硬件变了。 摩尔定律趋于终结，CPU 不再是变得更快，而是变得更多。多核处理器已成为标配。 事实二：网络无处不在。 软件不再是单机运行的孤岛，而是由大量通过网络进行交互的分布式服务构成。 事实三：人是昂贵的。 软件的规模和复杂性爆炸式增长，工程师的开发效率和大规模协作，已成为比机器执行效率更重要的瓶颈。当时的主流语言（如 C++），其缓慢的编译速度和极高的复杂性，正在扼杀生产力。 现在，让我们像 Go 创始人一样，从这三个基础事实出发，看看会推导出什么。 推论一：并发必须是“一等公民” 出发点 (事实一 &#38; 二)：既然硬件是多核的，系统是网络的，那么并发就不应再是一个需要通过复杂库（如 pthreads）来实现的、充满痛苦的“高级特性”。它必须成为语言的内建核心。 第一性问题：一个理想的并发模型，其最基础的元素是什么？是独立的执行单元，以及它们之间安全的通信机制。 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/think-like-go-founders-relearn-go-five-principles-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/12/26/think-like-go-founders-relearn-go-five-principles">本文永久链接</a> &#8211; https://tonybai.com/2025/12/26/think-like-go-founders-relearn-go-five-principles</p>
<p>大家好，我是Tony Bai。</p>
<p>学习一门新的编程语言时，我们常常陷入“是什么”的迷雾：goroutine 是什么？channel 是什么？interface 是什么？我们记忆语法，模仿示例，却很少追问那个更根本的问题——<strong>“为什么”</strong>？</p>
<p>为什么 Go 要被设计成这个样子？</p>
<p>要回答这个问题，我们需要进行一次“思想上的角色扮演”，回到 <a href="https://tonybai.com/2025/07/03/meet-the-go-team-2012">Go 语言诞生</a>之前的那个“原点”，像它的创始人们——Rob Pike, Ken Thompson, Robert Griesemer——一样思考。他们并非在“发明”一门新语言，而是在运用一系列深刻的<strong>思维原理</strong>，为一组棘手的工程问题，构建一个全新的、逻辑自洽的解决方案。</p>
<p>本文，就让我们一起踏上这场“重学 Go”的旅程。我们将带上五大“精英思维原理”作为工具，去看看我们能否“重新推导出”Go 语言的核心设计，并以此重塑我们对这门语言的理解。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/inside-goroutine-scheduler-qr.png" alt="img{512x368}" /></p>
<hr />
<h2>第一性原理 (First Principles)：追问 Go 的“为什么”</h2>
<blockquote>
<p><strong>思维原理</strong>：将问题或理念，还原到其最基础、最无可辩驳的元素，并以此为基石进行重构。</p>
</blockquote>
<p>这是所有深度思考的起点。在 Go 诞生的 2007 年，Google 的工程师们面临着几个无可辩驳的“基础事实”，这些事实构成了 Go 语言设计的“宇宙大爆炸”奇点：</p>
<ol>
<li><strong>事实一：硬件变了。</strong> 摩尔定律趋于终结，CPU 不再是变得更快，而是变得<strong>更多</strong>。<strong>多核处理器</strong>已成为标配。</li>
<li><strong>事实二：网络无处不在。</strong> 软件不再是单机运行的孤岛，而是由大量通过网络进行交互的<strong>分布式服务</strong>构成。</li>
<li><strong>事实三：人是昂贵的。</strong> 软件的规模和复杂性爆炸式增长，<strong>工程师的开发效率和大规模协作</strong>，已成为比机器执行效率更重要的瓶颈。当时的主流语言（如 C++），其缓慢的编译速度和极高的复杂性，正在扼杀生产力。</li>
</ol>
<p>现在，让我们像 Go 创始人一样，从这三个基础事实出发，看看会推导出什么。</p>
<h3>推论一：并发必须是“一等公民”</h3>
<ul>
<li><strong>出发点 (事实一 &amp; 二)</strong>：既然硬件是多核的，系统是网络的，那么<strong>并发</strong>就不应再是一个需要通过复杂库（如 pthreads）来实现的、充满痛苦的“高级特性”。它必须成为语言的<strong>内建核心</strong>。</li>
<li><strong>第一性问题</strong>：一个理想的并发模型，其最基础的元素是什么？是<strong>独立的执行单元</strong>，以及它们之间<strong>安全的通信机制</strong>。</li>
<li><strong>Go 的答案</strong>：
<ul>
<li><strong>goroutine</strong>：一个极其轻量级的独立执行单元，创建成本极低，让“为每一个网络请求启动一个并发任务”成为可能。</li>
<li><strong>channel</strong>：一个类型安全的、用于在 goroutine 之间传递消息的管道。这直接引出了 Go 的著名哲学：“<strong>不要通过共享内存来通信，而要通过通信来共享内存。</strong>”</li>
</ul>
</li>
</ul>
<p>当你从这个角度看时，goroutine 和 channel 就不再是两个孤立的语法，而是对“如何让并发变得简单安全”这个第一性问题，给出的一个优雅、逻辑自洽的答案。</p>
<h3>推论二：错误处理必须“显式且强制”</h3>
<ul>
<li><strong>出发点 (事实二 &amp; 三)</strong>：在由成百上千个微服务构成的分布式系统中，<strong>网络错误、服务超时、节点宕机</strong>不再是“异常”，而是<strong>“常态”</strong>。一个健壮的系统，必须严肃地对待每一个可能出错的地方。</li>
<li><strong>第一性问题</strong>：如何确保开发者不会忽略任何一个潜在的失败？</li>
<li><strong>Go 的答案</strong>：
<ul>
<li><strong>将 error 作为普通的值返回</strong>：这使得错误的处理路径，成为程序控制流中<strong>明确、可见的一部分</strong>，而不是像 try-catch 那样，可以被“隐形”地向上传播。</li>
<li><strong>多返回值</strong>：通过允许函数同时返回“结果”和“错误”，Go 解决了传统返回码“侵占返回通道”的问题，使得错误处理不再笨拙。</li>
</ul>
</li>
</ul>
<p>if err != nil 的“繁琐”，从第一性原理的角度看，恰恰是其一大优点。它是在用语法，强制开发者去构建一个“<strong>失败优先</strong>” (fail-first) 的、更具韧性的心智模型。</p>
<h3>推论三：组合必须优于继承</h3>
<ul>
<li><strong>出发点 (事实三)</strong>：在大规模的、由数千名工程师协作的代码库中，最核心的挑战是<strong>管理复杂性</strong>。</li>
<li><strong>第一性问题</strong>：构建大型软件的最佳方式是什么？是将小的、独立的、功能单一的组件，像乐高积木一样<strong>组合</strong>起来，还是构建一个复杂、脆弱的继承层次结构？</li>
<li><strong>Go 的答案</strong>：
<ul>
<li><strong>移除类和继承</strong>：从根源上杜绝了由复杂继承体系带来的脆弱基类、菱形依赖等问题。</li>
<li><strong>拥抱 struct 和 interface</strong>：Go 将世界清晰地划分为<strong>数据 (struct)</strong> 和<strong>行为 (interface)</strong>。struct 通过<strong>嵌入 (embedding)</strong> 实现状态的组合，而 interface 则通过<strong>隐式实现</strong>，实现了行为的、完全解耦的组合。</li>
</ul>
</li>
</ul>
<p>当你理解了“组合优于继承”这一软件设计的“第一性原理”时，Go 对 OOP 的“背叛”，就变成了一种远见卓识。</p>
<h3>推论四：工具链必须“快如闪电”</h3>
<ul>
<li><strong>出发点 (事实三)</strong>：工程师的时间是宝贵的。长达数十分钟的编译等待，是生产力的巨大杀手。</li>
<li><strong>第一性问题</strong>：一个编程语言的工具链，其最根本的使命是什么？是<strong>最大化地缩短从“想法”到“反馈”的循环周期</strong>。</li>
<li><strong>Go 的答案</strong>：
<ul>
<li><strong>极快的编译速度</strong>：通过简化的语法、明确的依赖管理和并发编译等技术实现。</li>
<li><strong>内置一切</strong>：将<strong>格式化 (gofmt)、测试 (go test)、文档 (go doc)、依赖管理 (包括后期加入的go mod)</strong> 等所有核心功能，全部内置到工具链中，消除了无尽的工具选型和配置的痛苦。</li>
</ul>
</li>
</ul>
<hr />
<h2>分解 (Decomposition)：拆解 Go 的“黑盒”</h2>
<blockquote>
<p><strong>思维原理</strong>：将一个庞大、复杂的系统，拆解成更小、更易于管理的独立部分，逐一理解，再看它们如何协同工作。</p>
</blockquote>
<p><strong>重学 Go 的应用</strong>：将 Go 语言本身，及其标准库，视为一个可供“解剖”的系统。</p>
<p>比如：<strong>学习 net/http</strong>：不要把它当成一个“黑盒”，而是要：</p>
<ol>
<li><strong>分解它</strong>：http.ListenAndServe 内部做了什么？它创建了一个 Server，然后调用了 Accept 循环。</li>
<li><strong>再分解</strong>：Server.Serve 内部又做了什么？它为每一个连接创建了一个新的 goroutine。</li>
<li><strong>继续分解</strong>：conn.serve 内部呢？它解析 HTTP 请求，创建一个 Request 和一个 ResponseWriter，然后调用你注册的 Handler。</li>
</ol>
<p>通过这样层层分解，你最终理解的，不再是一个模糊的“Web 服务器”，而是一系列清晰、可控的 Go 并发原语和 I/O 操作的组合。你会发现，Go 标准库本身就是学习 Go 语言最佳实践的“活教材”。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/go-network-programming-complete-guide-pr.png" alt="" /></p>
<hr />
<h2>识别关键驱动力 (帕累托法则)：抓住 Go 的 20% 核心</h2>
<blockquote>
<p><strong>思维原理</strong>：识别出系统中那 20% 的、能驱动 80% 结果的核心要素，并集中精力掌握它们。</p>
</blockquote>
<p><strong>重学 Go 的应用</strong>：Go 语言的设计，本身就充满了对“帕累托法则”的应用。它刻意保持了极小的核心特性集。要高效地学习 Go，你也应该从这些“关键驱动力”入手。</p>
<p><strong>Go 的 20% 核心是什么？</strong></p>
<ol>
<li><strong>struct 与 interface</strong>：理解 Go 如何通过<strong>数据（struct）</strong>和<strong>行为（interface）</strong>的分离与组合来构建世界。这是 Go 语言最核心的哲学。</li>
<li><strong>goroutine 与 channel</strong>：理解 Go 的 CSP 并发模型。这是 Go 在云原生时代安身立命的根本。</li>
<li><strong>error 作为值</strong>：理解 Go 的错误处理哲学。这是编写健壮 Go 程序的关键。</li>
<li><strong>package 作为编译和依赖单元</strong>：理解 Go 如何组织和管理代码。</li>
</ol>
<p>在你精通这四个“关键驱动力”之前，暂时忘掉 cgo、unsafe、反射 (reflect) 等更边缘、更复杂的特性。</p>
<hr />
<h2>结构化映射 (Structural Mapping)：绘制你的 Go “心智地图”</h2>
<blockquote>
<p><strong>思维原理</strong>：通过绘制概念图或草图，将一个理念或系统的各个部分，以及它们之间的连接关系，进行<strong>可视化</strong>。</p>
</blockquote>
<p><strong>重学 Go 的应用</strong>：在你学习 Go 的每一个核心概念时，都尝试为它画一张“地图”。</p>
<ul>
<li><strong>学习并发</strong>：画一张图，用方框代表 goroutine，用带箭头的线代表 channel 的数据流向。select 语句是什么？它就是这张图上的一个“十字路口”或“路由器”。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/2025/think-like-go-founders-relearn-go-five-principles-2.png" alt="" /></p>
<pre><code class="mermaid">graph TD
    Producer1 -- "data" --&gt; Channel1
    Producer2 -- "data" --&gt; Channel2
    Channel1 --&gt; Select{"select"}
    Channel2 --&gt; Select
    Select -- "picked data" --&gt; Consumer
</code></pre>
<ul>
<li><strong>学习类型系统</strong>：画一张图，一个 <em>http.Request 结构体在左边，一个 io.Reader 接口在右边。</em>http.Request.Body 字段，就是连接这两者的“桥梁”，因为它本身就是一个 io.ReadCloser（实现了 io.Reader）。</li>
</ul>
<p>这张“地图”，就是你在脑中构建的<strong>心智模型</strong>。一个清晰的心智模型，远比零散的语法知识更宝贵。</p>
<hr />
<h2>抽象层级切换 (Zoom In &amp; Out)：在 Go 的世界里自由穿梭</h2>
<blockquote>
<p><strong>思维原理</strong>：优秀的思考者，能够持续不断地在“宏观”与“微观”之间切换视角。</p>
</blockquote>
<p><strong>重学 Go 的应用</strong>：在阅读一段 Go 代码时，刻意练习这种“缩放”能力。</p>
<p><strong>以 fmt.Println(“hello”) 为例</strong>：</p>
<ul>
<li><strong>Zoom Out (宏观)</strong>：它是一个简单的标准库函数调用，用于向标准输出打印一行文本。这是它的<strong>API 语义</strong>。</li>
<li><strong>Zoom In (微观)</strong>：Println 内部做了什么？它接收一个 &#8230;any，通过反射判断类型，最终将字节写入一个实现了 io.Writer 的 os.Stdout。这是它的<strong>实现细节</strong>。</li>
<li><strong>再 Zoom In (硬件层面)</strong>：写入 os.Stdout 最终会触发一个<strong>系统调用 (syscall)</strong>，将数据从用户空间拷贝到内核空间，最终由操作系统和硬件来完成输出。这是它的<strong>底层原理</strong>。</li>
</ul>
<p>当你能够在这三个层级（API 语义、实现细节、底层原理）之间自如切换时，你就真正“理解”了 fmt.Println。将这种练习应用到你学习的每一个 Go 特性上。</p>
<h2>小结</h2>
<p>这些思维原理，为我们提供了一条全新的、更深刻的 Go 学习路径。它不再是一次被动的知识灌输，而是一场主动的、充满探索精神的“思想实验”。</p>
<p>当你开始用“第一性原理”去质疑，用“分解”去剖析，用“关键驱动力”去聚焦，用“结构化映射”去建模，用“抽象层级切换”去审视时，你学习的，将不再仅仅是 Go 这门语言本身，而是其背后所蕴含的、数十年来软件工程发展的智慧结晶。</p>
<p>这，正是从一名“Go 的使用者”，蜕变为一名“Go 的思考者”的开始。</p>
<hr />
<p><strong>你的“顿悟”时刻</strong></p>
<p>这五大思维原理，哪一个最让你有“醍醐灌顶”的感觉？<strong>在你的 Go 学习之路上，是否也曾有过某个瞬间，让你突然从“写代码”升维到了“设计系统”？或者，你对 Go 的某个设计（如错误处理）曾有过误解，后来才明白其良苦用心？</strong></p>
<p><strong>欢迎在评论区分享你的“顿悟时刻”或独特见解！</strong> 让我们一起在思考中进化。</p>
<p><strong>如果这篇文章为你打开了新的视角，别忘了点个【赞】和【在看】，并分享给身边热爱思考的 Gopher！</strong></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><strong>想系统学习Go，构建扎实的知识体系？</strong></p>
<p>我的新书《<a href="https://book.douban.com/subject/37499496/">Go语言第一课</a>》是你的首选。源自2.4万人好评的极客时间专栏，内容全面升级，同步至Go 1.24。首发期有专属五折优惠，不到40元即可入手，扫码即可拥有这本300页的Go语言入门宝典，即刻开启你的Go语言高效学习之旅！</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/go-primer-published-4.png" 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; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/12/26/think-like-go-founders-relearn-go-five-principles/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go 技术沉思录：Java 26 年演进史给我们带来的启示</title>
		<link>https://tonybai.com/2025/10/18/lessons-from-java-26-years-evolution/</link>
		<comments>https://tonybai.com/2025/10/18/lessons-from-java-26-years-evolution/#comments</comments>
		<pubDate>Fri, 17 Oct 2025 23:53:22 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[1.18版本]]></category>
		<category><![CDATA[26年]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[CommunicatingSequentialProcesses]]></category>
		<category><![CDATA[CSP]]></category>
		<category><![CDATA[GCShapeStenciling方案]]></category>
		<category><![CDATA[generics]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GoModules]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[io.Reader]]></category>
		<category><![CDATA[io.Writer]]></category>
		<category><![CDATA[J.U.C]]></category>
		<category><![CDATA[JAR地狱]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[java.util.concurrent]]></category>
		<category><![CDATA[Java1.4]]></category>
		<category><![CDATA[Java5]]></category>
		<category><![CDATA[Java8]]></category>
		<category><![CDATA[Java9]]></category>
		<category><![CDATA[jsonv2]]></category>
		<category><![CDATA[Modules]]></category>
		<category><![CDATA[NeilMadden]]></category>
		<category><![CDATA[NewI/O]]></category>
		<category><![CDATA[NIO]]></category>
		<category><![CDATA[ProjectJigsaw]]></category>
		<category><![CDATA[ProjectLoom]]></category>
		<category><![CDATA[StreamsAPI]]></category>
		<category><![CDATA[TonyBai]]></category>
		<category><![CDATA[typeerasure]]></category>
		<category><![CDATA[VirtualThreads]]></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>
		<category><![CDATA[虚拟线程]]></category>
		<category><![CDATA[表达力]]></category>
		<category><![CDATA[轻量级并发]]></category>
		<category><![CDATA[运行时开销]]></category>
		<category><![CDATA[非阻塞IO]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5266</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/10/18/lessons-from-java-26-years-evolution 大家好，我是Tony Bai。 历史不会简单重复，但总是惊人地相似。编程语言的演化，如同一部波澜壮阔的史诗，充满了智慧的闪光、艰难的抉择与深刻的教训。 上月，资深工程师 Neil Madden 发表了一篇引人入胜的文章《点评 26 年的 Java 变更》，以一位亲历者的视角，犀利地回顾了这门“常青”语言的演进之路。 注：Neil Madden口中的Java 26年是指自他1999年学习Java编程开始到2025年的今天。 从Gopher视角来看，这并非一篇简单的技术评论，而是一次宝贵的以史为鉴的机会。 Java 作为企业级开发的“前浪”，其三十年的漫长的发展历程就像一本厚重的教科书，记录了在引入泛型、改进 I/O、简化并发等几乎所有重大议题上的探索与挣扎。 对于 Go 语言乃至整个软件工程领域而言，这其中蕴含着超越语言本身的普适性启示。本文并非旨在对比 Go 与 Java 的优劣，而是希望作为一部“技术沉思录”，通过 Java 这个案例，与各位一同探寻编程语言演进的内在规律。 启示一：核心特性的引入，时机与设计的艺术 Java 5 (2004) &#8211; 泛型 (Generics) “as Go discovered on its attempt to speed-run Java&#8217;s mistakes all over again, if you don&#8217;t add generics [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/lessons-from-java-26-years-evolution-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/10/18/lessons-from-java-26-years-evolution">本文永久链接</a> &#8211; https://tonybai.com/2025/10/18/lessons-from-java-26-years-evolution</p>
<p>大家好，我是Tony Bai。</p>
<p>历史不会简单重复，但总是惊人地相似。编程语言的演化，如同一部波澜壮阔的史诗，充满了智慧的闪光、艰难的抉择与深刻的教训。</p>
<p>上月，资深工程师 Neil Madden 发表了一篇引人入胜的文章《<a href="https://neilmadden.blog/2025/09/12/rating-26-years-of-java-changes/">点评 26 年的 Java 变更</a>》，以一位亲历者的视角，犀利地回顾了这门“常青”语言的演进之路。</p>
<blockquote>
<p>注：Neil Madden口中的Java 26年是指自他1999年学习Java编程开始到2025年的今天。</p>
</blockquote>
<p>从Gopher视角来看，这并非一篇简单的技术评论，而是一次宝贵的以史为鉴的机会。</p>
<p>Java 作为企业级开发的“前浪”，其<a href="https://tonybai.com/2025/05/17/java-at-30">三十年的漫长的发展历程</a>就像一本厚重的教科书，记录了在引入泛型、改进 I/O、简化并发等几乎所有重大议题上的探索与挣扎。</p>
<p>对于 Go 语言乃至整个软件工程领域而言，这其中蕴含着超越语言本身的普适性启示。本文并非旨在对比 Go 与 Java 的优劣，而是希望作为一部“技术沉思录”，通过 Java 这个案例，与各位一同探寻编程语言演进的内在规律。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/go-network-programming-complete-guide-pr.png" alt="" /></p>
<h2>启示一：核心特性的引入，时机与设计的艺术</h2>
<p><strong>Java 5 (2004) &#8211; 泛型 (Generics)</strong></p>
<blockquote>
<p>“as Go discovered on its attempt to speed-run Java&#8217;s mistakes all over again, if you don&#8217;t add generics from the start then you&#8217;ll have to retrofit them later, badly.”<br />
  （正如 Go 在其“快速重蹈 Java 覆辙”的尝试中发现的那样，如果你不从一开始就加入泛型，那么日后就不得不糟糕地进行弥补。）</p>
</blockquote>
<p>Java 直到发布 8 年后才引入泛型。为了保持对海量存量代码的向后兼容性，它做出了一个影响深远的妥协：<strong>类型擦除 (type erasure)</strong>。这个决定虽然在当时解决了燃眉之急，却也带来了诸多“粗糙的边缘”，如反射处理困难、无法对泛型类型进行 instanceof 判断等，至今仍是 Java 开发者的痛点。</p>
<p>由此看来，语言核心特性的引入，是一场关于时机与设计的精妙艺术。过早引入，可能因设计不成熟而留下历史包袱；过晚引入，则必然会受到向后兼容性的掣肘，导致实现上的妥协。Java 的经验深刻地揭示了“后补”式设计的代价。</p>
<p>Go 语言在发布 12 年后才<a href="https://tonybai.com/2022/04/20/some-changes-in-go-1-18">于1.18 版本引入泛型</a>，同样面临巨大的兼容性压力。幸运的是，Go 团队得以借鉴 Java 的教训，选择了一条更艰难但更正确的道路——结合”Stenciling方案”和”Dictionaries方案”的“GC Shape Stenciling 方案”，在编译时间(二进制文件膨胀)以及运行时开销方面做了一个折中，并且没有类型擦除。这为 Go 泛型的未来发展奠定了更坚实的基础，也印证了一个原则：<strong>对于动摇语言根基的核心特性，宁愿慢，也要做对。</strong></p>
<blockquote>
<p>注：关于Go泛型实现机制的详细说明，请参见极客时间《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》的第41讲《<a href="https://time.geekbang.org/column/article/601538">驯服泛型：明确使用时机</a>》。</p>
</blockquote>
<h2>启示二：API 是语言的“遗产”，其影响远超想象</h2>
<p><strong>Java 1.4 (2002) &#8211; “New” I/O (NIO)</strong></p>
<blockquote>
<p>“Provided non-blocking I/O for the first time, but really just a horrible API&#8230; Has barely improved in 2 and a half decades.”<br />
  （首次提供了非阻塞 I/O，但 API 简直糟透了……在 25 年里几乎没有任何改进。）</p>
</blockquote>
<p>Neil 对 Java NIO 的评价毫不留情。他吐槽其 API 令人困惑，并且 inexplicably（莫名其妙地）使用 32 位有符号整数表示文件大小，将文件限制在 2GB 以内，这成为了 Java I/O 长期以来的一个“历史污点”。</p>
<p>这也印证了这样一条结论：标准库的 API 一旦发布，就成为语言最宝贵也最沉重的“遗产”。</p>
<p>一个设计精良的 API 可以赋能一代又一代的开发者，而一个糟糕的 API 则可能成为数十年都难以摆脱的枷锁。它定义了开发者与语言交互的方式，深刻地影响着生产力、代码质量和开发者的心智模型。</p>
<p>Go 语言从诞生之初就拥有一个设计极其精良的 I/O 模型。io.Reader 和 io.Writer 接口的简洁与强大，至今仍是语言设计的典范。Go 的网络库 net 基于操作系统提供的非阻塞 I/O（如 epoll），并通过 goroutine 将其巧妙地封装为<strong>同步阻塞的编程模型</strong>。这使得 Go 开发者既能享受非阻塞 I/O 的高性能，又无需陷入复杂的回调地狱。Java NIO 的“失误”深刻地提醒我们，<strong>在 API 设计上投入再多的思考也不为过。</strong></p>
<h2>启示三：将正确的并发模型内置于语言，是生产力的巨大飞跃</h2>
<p><strong>Java 5 (2004) &#8211; java.util.concurrent</strong><br />
<strong>Java 19 (2022) &#8211; 虚拟线程 (Virtual Threads)</strong></p>
<p>Neil 对 Doug Lea 的 java.util.concurrent (J.U.C) 包给予了满分盛赞，认为其设计极其出色。然而，他也指出，在苦苦挣扎于各种复杂的异步编程模型多年后，Java 才终于通过 Project Loom 引入了虚拟线程，试图在 JVM 层面实现 M:N 的轻量级并发模型。</p>
<p>并发是现代软件开发的基石。一种语言如何处理并发，直接决定了其生产力的上限。Java 的演进路径——先提供一套强大的、专家级的底层并发工具（J.U.C），然后在多年后才引入一个更高层次、更易于大众使用的并发模型（虚拟线程）——揭示了一条从“提供工具”到“提供模型”的演进规律。</p>
<p>Go 语言在这一点上扮演了“预言家”的角色。它从诞生之初就将<strong>轻量级并发 (goroutine)</strong> 和 <strong>通信 (channel)</strong> 作为语言的一等公民内置于运行时。这种 CSP (Communicating Sequential Processes) 模型，极大地简化了并发编程的心智负担。Go 的成功雄辩地证明了，<strong>将一个简单、强大的并发模型作为语言的核心特性，其带来的生产力飞跃，远非一个复杂的工具箱所能比拟。</strong></p>
<h2>启示四：警惕范围蔓延，敬畏生态兼容性</h2>
<p><strong>Java 8 (2014) &#8211; Streams API</strong><br />
<strong>Java 9 (2017) &#8211; 模块系统 (Modules)</strong></p>
<p>Neil 对 Java Streams API 和模块系统给出了惊人的低分。他认为，Streams API 为了实现“看似简单”的并行计算而过度设计，变得复杂难用。而模块系统（Project Jigsaw）虽然初衷是解决 JAR 地狱，但其引入的巨大动荡和对现有生态的破坏性，使其得不偿失。</p>
<p>语言的演进充满了诱惑。一个好的特性，可能会因为被赋予了过多不相关的目标（<strong>范围蔓延</strong>）而变得臃肿不堪。任何试图“修正”语言底层生态的重大变革，都必须对<strong>生态兼容性</strong>抱有最大的敬畏。因为语言的生命力，最终源于其繁荣的社区和生态。</p>
<p>Go 在这方面也并非一帆风順。Go Modules 在诞生之初也曾引发巨大争议，但最终凭借其相对简洁的设计和 go 命令的强大集成能力，成功地统一了 Go 的依赖管理生态，其过程虽然有阵痛，但避免了 Java 模块系统那样的“大分裂”。Java 的这两个案例，为 Go 未来的任何重大变革都敲响了警钟。</p>
<h2>小结：在巨人的肩膀上，继续沉思</h2>
<p>回顾 Java 26 年的演进史，我们看到的不是一个失败者，而是一个不断自我革新、虽有失误但仍充满生命力的“巨人”。它的每一步探索，无论是成功还是失败，都为后来的语言（尤其是 Go）提供了宝贵的“启示录”。</p>
<p>Go 的幸运在于，它诞生得更晚，可以在“巨人的肩膀上”看得更远，从而在泛型、I/O 模型和并发等核心问题上，做出了更符合时代需求的设计。</p>
<p>然而，历史的镜子也照向未来。Go 如今也面临着自己的“沉思时刻”：如何平衡语言的简洁性与日益增长的表达力需求？如何演进标准库以适应新的挑战（这方面math/v2、<a href="https://tonybai.com/2025/08/09/true-streaming-support-in-jsonv2">json/v2</a>做出了表率）？如何引入下一个可能具有破坏性的重大变革？</p>
<p>Java 的故事告诉我们，语言的演进是一场永无止境的马拉松。唯有保持谦逊，以史为鉴，并始终将开发者的真实需求和语言的内在哲学放在首位，才能在这场长跑中行稳致远。</p>
<p>资料链接：https://neilmadden.blog/2025/09/12/rating-26-years-of-java-changes/</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; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/10/18/lessons-from-java-26-years-evolution/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Gopher直通大厂，就从这第一课开始！</title>
		<link>https://tonybai.com/2025/09/03/gopher-first-lesson-to-big-factory/</link>
		<comments>https://tonybai.com/2025/09/03/gopher-first-lesson-to-big-factory/#comments</comments>
		<pubDate>Wed, 03 Sep 2025 00:52:21 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[CSP]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go1兼容性]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[gomodule]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[Go语言第一课]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[main]]></category>
		<category><![CDATA[Package]]></category>
		<category><![CDATA[Python]]></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>
		<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=5116</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/09/03/gopher-first-lesson-to-big-factory 大家好，我是Tony Bai。 很多计算机专业的同学们都在问：想进大厂，要先学好哪门编程语言？ 从应用广泛程度来说，学好Go语言肯定错不了！我们来看一下大厂们都用Go在做哪些开发： 阿里用于基础服务、网关、容器、服务框架等开发。 字节跳动用于即时通信（IM）、K8s、微服务等开发。 腾讯用于微信后台、云服务、游戏后端等开发。 滴滴用于数据平台、调度系统、消息中间件等开发。 此外，美团、百度、京东、小米等都在业务中大量使用Go语言做开发。可见，同学们只要玩转Go语言，大厂都会张开双臂欢迎你们。 大厂为何如此青睐Go语言呢？有三点重要原因： 简单易上手： Go语法简洁，学习成本低，代码易维护； 生产力与性能有效结合： Go拥有卓越的并发性能，内置调度器和非抢占式模型，保证了超高的稳定性； 使用快乐且前景广阔： 优良的开发体验，包括得心应手的工具链、丰富健壮的标准库、广泛的社区支持等。 总的来说，Go相对于C/C++，性能并没有明显差距，可维护性还更好；相对于Python，Go性能大幅领先，入门难度则相差无几。 直通大厂，同学们请看《Go 语言第一课》这本书，书中详细介绍了Go的设计哲学与核心理念，全面讲解了Go的重要语法特性。没有基础也完全不必担心，本书手把手式教学，小白立即轻松上手。 扫描上方二维码，即可五折购书(在有效期内) 现在，让我们进入课堂，开始Go语言学习的第一课吧。 Part.1 零基础起步，Go开发全掌握 本书为读者设计了一条循序渐进的学习路线，可以分为三个部分。 首先讲述Go语言的起源与设计哲学； 然后说明开发环境的搭建方法； 最后详细介绍Go的重要语法与语言特性，以及工程实施的一些细节。 初次学习Go开发的同学们一定要注意，动手实践是学习编程的不二法门，在进入第二部分学习时，就要根据书中内容同步搭建实验环境，一步一个脚印地走稳走好。 Go的设计哲学 本部分先介绍了Go语言在谷歌公司内部孵化的过程，描述了其在当今云计算时代的广泛应用。 Go的第一版官网 重点说明了Go的5个核心设计哲学： 简单： 仅有25个关键字，摒弃了诸多复杂的特性，便于快速上手； 显式： 要求代码逻辑清晰明确，避免隐式处理带来的不确定性； 组合： 通过类型嵌入提供垂直扩展能力，通过接口实现水平组合，灵活扩展功能； 并发： 原生支持并发，用户层轻量级线程，轻松支持高并发访问； 面向工程： 注重解决实际问题，围绕Go的库、工具、惯用法和软件工程方法，都为开发提供全面支持。 读者理解了Go的设计哲学就能明确它擅长的方向，澄清心中的疑问，也掌握了使用Go进行编程的指导原则。 Part.2 搭建Go开发环境 这部分先针对Windows、macOS、Linux三种主流操作系统给出了多种安装方法，包括使用安装包、使用预编译二进制包、通过源码编译，说明如何管理多个Go版本。 然后基于经典的“Hello World”示例，演示编译运行的方法，讲解Go的基本程序结构，包括包声明、导入包、main函数等内容。接着深入讲解Go包的定义、导入、初始化与编译单元。 // ch3/helloworld/main.go package main [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/gopher-first-lesson-to-big-factory-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/09/03/gopher-first-lesson-to-big-factory">本文永久链接</a> &#8211; https://tonybai.com/2025/09/03/gopher-first-lesson-to-big-factory</p>
<p>大家好，我是Tony Bai。</p>
<p>很多计算机专业的同学们都在问：想进大厂，要先学好哪门编程语言？</p>
<p>从应用广泛程度来说，学好Go语言肯定错不了！我们来看一下大厂们都用Go在做哪些开发：</p>
<blockquote>
<p>阿里用于基础服务、网关、容器、服务框架等开发。</p>
<p>字节跳动用于即时通信（IM）、K8s、微服务等开发。</p>
<p>腾讯用于微信后台、云服务、游戏后端等开发。</p>
<p>滴滴用于数据平台、调度系统、消息中间件等开发。</p>
</blockquote>
<p>此外，美团、百度、京东、小米等都在业务中大量使用Go语言做开发。可见，同学们只要玩转Go语言，大厂都会张开双臂欢迎你们。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/gopher-first-lesson-to-big-factory-2.png" alt="" /></p>
<p>大厂为何如此青睐Go语言呢？有三点重要原因：</p>
<ul>
<li><strong>简单易上手：</strong> Go语法简洁，学习成本低，代码易维护；</li>
<li><strong>生产力与性能有效结合：</strong> Go拥有卓越的并发性能，内置调度器和非抢占式模型，保证了超高的稳定性；</li>
<li><strong>使用快乐且前景广阔：</strong> 优良的开发体验，包括得心应手的工具链、丰富健壮的标准库、广泛的社区支持等。</li>
</ul>
<p>总的来说，Go相对于C/C++，性能并没有明显差距，可维护性还更好；相对于Python，Go性能大幅领先，入门难度则相差无几。</p>
<p>直通大厂，同学们请看《<a href="https://book.douban.com/subject/37499496/">Go 语言第一课</a>》这本书，书中详细介绍了Go的设计哲学与核心理念，全面讲解了Go的重要语法特性。没有基础也完全不必担心，本书手把手式教学，小白立即轻松上手。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/go-primer-published-4.png" alt="" /><br />
<center>扫描上方二维码，即可五折购书(在有效期内)</center></p>
<hr />
<p>现在，让我们进入课堂，开始Go语言学习的第一课吧。</p>
<h2>Part.1 零基础起步，Go开发全掌握</h2>
<p>本书为读者设计了一条循序渐进的学习路线，可以分为三个部分。</p>
<p>首先讲述Go语言的起源与设计哲学；</p>
<p>然后说明开发环境的搭建方法；</p>
<p>最后详细介绍Go的重要语法与语言特性，以及工程实施的一些细节。</p>
<p>初次学习Go开发的同学们一定要注意，动手实践是学习编程的不二法门，在进入第二部分学习时，就要根据书中内容同步搭建实验环境，一步一个脚印地走稳走好。</p>
<h3>Go的设计哲学</h3>
<p>本部分先介绍了Go语言在谷歌公司内部孵化的过程，描述了其在当今云计算时代的广泛应用。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/gopher-first-lesson-to-big-factory-3.png" alt="" /><br />
<center>Go的第一版官网</center></p>
<p>重点说明了Go的5个核心设计哲学：</p>
<ul>
<li><strong>简单：</strong> 仅有25个关键字，摒弃了诸多复杂的特性，便于快速上手；</li>
<li><strong>显式：</strong> 要求代码逻辑清晰明确，避免隐式处理带来的不确定性；</li>
<li><strong>组合：</strong> 通过类型嵌入提供垂直扩展能力，通过接口实现水平组合，灵活扩展功能；</li>
<li><strong>并发：</strong> 原生支持并发，用户层轻量级线程，轻松支持高并发访问；</li>
<li><strong>面向工程：</strong> 注重解决实际问题，围绕Go的库、工具、惯用法和软件工程方法，都为开发提供全面支持。</li>
</ul>
<p>读者理解了Go的设计哲学就能明确它擅长的方向，澄清心中的疑问，也掌握了使用Go进行编程的指导原则。</p>
<h2>Part.2 搭建Go开发环境</h2>
<p>这部分先针对Windows、macOS、Linux三种主流操作系统给出了多种安装方法，包括使用安装包、使用预编译二进制包、通过源码编译，说明如何管理多个Go版本。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/gopher-first-lesson-to-big-factory-4.png" alt="" /></p>
<p>然后基于经典的“Hello World”示例，演示编译运行的方法，讲解Go的基本程序结构，包括包声明、导入包、main函数等内容。接着深入讲解Go包的定义、导入、初始化与编译单元。</p>
<pre><code class="go">// ch3/helloworld/main.go
package main
import "fmt"
func main() {
    fmt.Println("hello, world")
}
</code></pre>
<p>详细讲解Go Module的核心概念，结合创世项目案例、社区共识、官方指南，给出清晰的项目布局建议。梳理了Go依赖管理的演化历程，重点讲解基于Go Module的依赖管理操作，包括添加、升级/降级、移除、替换等操作。</p>
<p>经过这部分的学习，读者可以掌握Go的编译与运行方法、项目的组织与管理，具备工程化的能力。</p>
<h2>Part.3 Go语言特性详解</h2>
<p>这部分是本书的重点，覆盖基础语法知识、并发、泛型、测试等内容；在结构上由浅入深，层层递进，读者只要坚持学练结合，就能全盘掌握Go的关键知识。</p>
<p>基础语法知识包含以下内容：</p>
<ul>
<li><strong>变量与类型：</strong> 说明变量的声明方法、变量的作用域。</li>
<li><strong>基本数据类型：</strong> 详细讲解布尔型、数值型、字符串型的特性与常用操作。</li>
<li><strong>常量：</strong> 重点讲解Go常量的创新性设计，包括无类型常量、隐式转换、实现枚举。</li>
<li><strong>复合数据类型：</strong> 讲解数组、切片、map类型、结构体的声明与操作。</li>
<li><strong>指针类型：</strong> 解释指针的概念，说明其用途与使用限制。</li>
<li><strong>控制结构：</strong> 详细介绍if、for、switch语句的用法，实现分支、循环功能。</li>
<li><strong>函数：</strong> 说明函数的声明、参数、多返回值特性，以及defer的使用与注意事项。</li>
<li><strong>错误处理：</strong> 讲解了error接口的错误处理，以及异常处理的panic机制。</li>
<li><strong>方法：</strong> 详解Go方法的声明与本质，通过类型嵌入模拟“实现继承”。</li>
<li><strong>接口：</strong> 说明接口类型的定义、实现方法与注意事项。</li>
</ul>
<p>并发是Go的“杀手锏”级高阶特性，书中详述了Go并发的原理，给出了并发实现方案，即通过channel通信实现goroutine间同步，而非共享内存。说明channel与select结合使用的惯用法。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/gopher-first-lesson-to-big-factory-5.png" alt="" /><br />
<center>CSP模型</center></p>
<p>泛型是Go 1.18版本的新增特性，解决了为不同类型编写重复代码的痛点。书中介绍了Go泛型设计演化简史，讲解泛型语法、类型参数、类型约束，并给出了代码示例。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/gopher-first-lesson-to-big-factory-6.png" alt="" /><br />
<center>接口类型的扩展定义</center></p>
<p>最后讨论Go代码的质量保障方法，介绍了Go内置的测试框架，包括单元测试、示例测试、测试覆盖率以及性能基准测试，帮助读者快速且方便地组织、编写、执行测试，并得到详尽的测试结果反馈。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/gopher-first-lesson-to-big-factory-7.png" alt="" /><br />
<center>Go测试覆盖率报告</center></p>
<h2>Part.4 作者介绍</h2>
<p><img src="https://tonybai.com/wp-content/uploads/2025/gopher-first-lesson-to-big-factory-8.png" alt="" /></p>
<p>本书作者Tony Bai（白明），资深架构师，行业经验超20年，现于汽车行业某独角兽Tier1企业担任车云平台架构组技术负责人。</p>
<p>出于对技术的追求与热爱，他发起了Gopher部落技术社群，也是tonybai.com的博主。</p>
<p>Tony Bai老师早在2011年Go语言还没发布Go 1.0稳定版本时，他就在跟随、实践。当Go在大规模生产环境中逐渐替代了C、Python，Go便成为他编写生产系统的第一语言。</p>
<p>后来，Tony Bai老师在极客时间上开设课程讲解Go语言开发，引领学员从入门到建立思维框架，走向大厂。累计2.4w名学员学习这门课程并纷纷给出高分评价。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/gopher-first-lesson-to-big-factory-9.png" alt="" /></p>
<p>如今，Tony Bai老师基于在线课程将内容整理成书，并补充了之前缺失的重要语法点（如指针、测试、泛型等），并对已有内容进行了精炼，同时更新至Go 1.24版本。</p>
<p>相信这本书会帮助更多读者轻松学会Go语言，解决实际工作问题，获得职业成功。</p>
<h2>Part.5 结语</h2>
<p>《Go 语言第一课》这本书可以说既懂新手痛点，又懂工程实战。本书从Go的设计哲学入手，然后给出保姆级的环境搭建、代码组织指南，最后通过由浅入深的语法讲解，覆盖从基础到高阶的所有核心特性。</p>
<p>本书具备三大特点。</p>
<p><strong>第一是高屋建翎</strong>，开篇即剖析Go语言的设计哲学和编程思想，帮助读者透彻理解Go的核心理念，了解Go的特长，知道如何使用以获得最佳效果。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/gopher-first-lesson-to-big-factory-10.png" alt="" /><br />
<center>精彩书摘</center></p>
<p><strong>第二是路径完整</strong>，覆盖Go入门的基础知识与概念，打通基础知识-语法特性-工程实践全流程，助力读者从新手进化为合格的Go开发工程师。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/gopher-first-lesson-to-big-factory-11.png" alt="" /><br />
<center>精彩书摘</center></p>
<p><strong>第三是保姆级讲解</strong>，搭建环境是一步一图，讲解语法时辅以大量精心设计的示例代码，简洁明了，帮助读者直观地理解和掌握重点与难点内容。书中还针对Go开发中易犯的错误给出了贴心的避坑提示。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/gopher-first-lesson-to-big-factory-12.png" alt="" /><br />
<center>精彩书摘</center></p>
<p>本书适合各个层次的读者。对于Go初学者，可以循序渐进地掌握Go编程；对于动态编程语言的开发者，可以通过本书平滑转投Go阵营；对于Go的技术爱好者，可以增进认知，培养专业开发水准。</p>
<p>现在翻开《Go 语言第一课》，开启Go开发之旅，高并发服务端、云原生应用开发，都将轻松掌控！</p>
<h2><strong>今日互动</strong></h2>
<p>说说你对Go语言的看法？</p>
<p>点击右侧链接，在<a href="https://mp.weixin.qq.com/s/pxIfuxtQN7HTXBxwYQMw3Q">原文留言区</a>参与互动，并点击在看和转发活动到朋友圈，我们将选1名读者获得赠书1本，截止时间9月15日。</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/09/03/gopher-first-lesson-to-big-factory/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go语言之父的反思：我们做对了什么，做错了什么</title>
		<link>https://tonybai.com/2024/01/07/what-we-got-right-what-we-got-wrong/</link>
		<comments>https://tonybai.com/2024/01/07/what-we-got-right-what-we-got-wrong/#comments</comments>
		<pubDate>Sun, 07 Jan 2024 11:33:46 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[Compiler]]></category>
		<category><![CDATA[CSP]]></category>
		<category><![CDATA[generics]]></category>
		<category><![CDATA[Git]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go.dev]]></category>
		<category><![CDATA[gofmt]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Gopher]]></category>
		<category><![CDATA[GopherCon]]></category>
		<category><![CDATA[GopherConAu]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[IDE]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[LLVM]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[plan9]]></category>
		<category><![CDATA[Pthread]]></category>
		<category><![CDATA[RobPike]]></category>
		<category><![CDATA[RussCox]]></category>
		<category><![CDATA[select]]></category>
		<category><![CDATA[Specification]]></category>
		<category><![CDATA[SSA]]></category>
		<category><![CDATA[svn]]></category>
		<category><![CDATA[typeparameter]]></category>
		<category><![CDATA[vet]]></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>
		<category><![CDATA[类型参数]]></category>
		<category><![CDATA[编译器]]></category>
		<category><![CDATA[自动化测试]]></category>
		<category><![CDATA[语言规范]]></category>
		<category><![CDATA[静态单赋值]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=4103</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2024/01/07/what-we-got-right-what-we-got-wrong 在《2023年Go语言盘点：稳中求新，稳中求变》和《Go测试的20个实用建议》两篇文章中，我都提到过已经退居二线的Go语言之父Rob Pike在Go开源14周年的那天亲自在GopherCon Australia 2023上发表了“What We Got Right, What We Got Wrong”的主题演讲来回顾Go诞生以来的得与失。近期Rob Pike终于将这次演进的文字稿发布了出来！GopherCon Australia也在油管上发布了这个演进的视频。Rob Pike的观点对所有Gopher都是极具参考价值的，因此在这篇博文中，我将Rob Pike的这次演讲稿翻译成中文，供大家参考(结合文字稿和视频)，我们一起来领略和学习大师的观点。 这是2023年11月10日我在悉尼GopherConAU 2023会议上的闭幕演讲（视频），那一天也是Go开源14周年的日子。本文中穿插着演示文稿中使用的幻灯片。 介绍 大家好！ 首先，我要感谢Katie和Chewy让我有幸为此次GopherConAU大会做闭幕演讲。 2009年11月10日 今天是2023年11月10日，Go作为开源项目推出14周年的纪念日。 2009年11月10日那天，加州时间下午3点（如果没记错的话），Ken Thompson、Robert Griesemer、Russ Cox、Ian Taylor、Adam Langley、Jini Kim和我满怀期待地看着网站上线。之后，全世界都知道我们在做什么了。 14年后的今天，有很多事情值得回顾。我想借此机会谈谈自那一天以来学到的一些重要经验。即使是最成功的项目，在反思之后，也会发现一些事情本可以做得更好。当然，也有一些事情事后看来似乎是成功的关键所在。 首先，我必须明确的是，这里的观点只代表我个人，不代表Go团队和Google。无论是过去还是现在，Go都是由一支专注的团队和庞大的社区付出巨大努力的结果。所以，如果你同意我的任何说法，请感谢他们。如果你不同意，请责怪我，但请保留你的意见。 鉴于本次演讲的题目，许多人可能期待我会分析语言中的优点和缺点。当然，我会做一些分析，但还会有更多内容，原因有几个。 首先，编程语言的好坏很大程度上取决于观点而不是事实，尽管许多人对Go或任何其他语言的最微不足道的功能都存在争论。 另外，关于换行符的位置、nil的工作方式、导出的大小写表示法、垃圾回收、错误处理等话题已经有了大量的讨论。这些话题肯定有值得讨论的地方，但几乎没什么是还没有被讨论过的。 但我要讨论的不仅仅是语言本身的真正原因是，语言并不是整个项目的全部。我们最初的目标不是创造一种新的编程语言，而是创造一种更好的编写软件的方式。我们对所使用的语言有意见——无论使用什么语言，每个人都是如此——但是我们遇到的基本问题与这些语言的特性没有太大关系，而是与在谷歌使用这些语言构建软件的过程有关。 T恤上的第一只Gopher 新语言的创建提供了探索其他想法的新路径，但这只是一个推动因素，而不是真正的重点。如果当时我正在工作的二进制文件不需要45分钟来构建 ，Go语言就不会出现。但那45分钟不是因为编译器慢(因为它不慢)，也不是因为它所用的语言不好(因为它也不差)。缓慢是由其他因素造成的。 我们想解决的就是这些因素：构建现代服务器软件的复杂性：控制依赖性、与人员不断变化的大型团队一起编程、可维护性、高效测试、多核CPU和网络的有效利用等等。 简而言之，Go不仅仅是一种编程语言。当然，它是一种编程语言，这是它的定义。但它的目的是帮助提供一种更好的方式来开发高质量的软件，至少与14多年前的我们的环境相比。 时至今日，这仍然是它的宗旨。Go是一个使构建生产软件更容易、更高效的项目。 几周前，当我开始准备这次演讲时，我只有一个题目，除此之外别无其他。为了激发我的思路，我在Mastodon上向人们征求意见。不少人给予了回复。我注意到了一种趋势：人们认为我们做错的事情都在语言本身，而我们做对的事情都在语言周边，比如gofmt、部署和测试等。事实上，我觉得这令人鼓舞。我们试图做的事情似乎已经产生了效果。 但值得承认的是，我们在早期并没有明确真正的目标。我们可能觉得这些目标是不言自明的。为了弥补这一缺陷，我在2013年的SPLASH会议上发表了一场题为《谷歌的Go语言：面向软件工程的语言设计》的演讲。 Go at Google 那场演讲和相关的博客文章可能是对Go语言为何而生的最好诠释。 今天的演讲是SPLASH演讲的后续，回顾了我们在构建语言之后所学到的经验教训，并且可以更广泛地应用于更大的图景。 那么&#8230;&#8230;来谈谈一些教训。 首先，当然，我们有： The Gopher [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2024/01/07/what-we-got-right-what-we-got-wrong">本文永久链接</a> &#8211; https://tonybai.com/2024/01/07/what-we-got-right-what-we-got-wrong</p>
<p>在《<a href="https://tonybai.com/2023/12/31/the-2023-review-of-go-programming-language/">2023年Go语言盘点：稳中求新，稳中求变</a>》和《<a href="https://tonybai.com/2024/01/01/go-testing-by-example/">Go测试的20个实用建议</a>》两篇文章中，我都提到过已经退居二线的<a href="https://tonybai.com/2023/12/11/simplicity/">Go语言之父Rob Pike</a>在<a href="https://tonybai.com/2023/11/11/go-opensource-14-years/">Go开源14周年</a>的那天亲自在GopherCon Australia 2023上发表了“What We Got Right, What We Got Wrong”的主题演讲来回顾Go诞生以来的得与失。近期<a href="https://commandcenter.blogspot.com/2024/01/what-we-got-right-what-we-got-wrong.html">Rob Pike终于将这次演进的文字稿发布了出来</a>！<a href="https://www.youtube.com/watch?v=yE5Tpp2BSGw">GopherCon Australia也在油管上发布了这个演进的视频</a>。Rob Pike的观点对所有Gopher都是极具参考价值的，因此在这篇博文中，我将Rob Pike的这次演讲稿翻译成中文，供大家参考(结合文字稿和视频)，我们一起来领略和学习大师的观点。</p>
<hr />
<p>这是2023年11月10日我<a href="https://www.youtube.com/watch?v=yE5Tpp2BSGw">在悉尼GopherConAU 2023会议上的闭幕演讲（视频）</a>，那一天也是<a href="https://tonybai.com/2023/11/11/go-opensource-14-years">Go开源14周年</a>的日子。本文中穿插着演示文稿中使用的幻灯片。</p>
<h2>介绍</h2>
<p>大家好！</p>
<p>首先，我要感谢Katie和Chewy让我有幸为此次GopherConAU大会做闭幕演讲。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-2.png" alt="" /><br />
<center>2009年11月10日</center></p>
<p>今天是2023年11月10日，Go作为开源项目推出14周年的纪念日。</p>
<p>2009年11月10日那天，加州时间下午3点（如果没记错的话），Ken Thompson、Robert Griesemer、Russ Cox、Ian Taylor、Adam Langley、Jini Kim和我满怀期待地看着网站上线。之后，全世界都知道我们在做什么了。</p>
<p>14年后的今天，有很多事情值得回顾。我想借此机会谈谈自那一天以来学到的一些重要经验。即使是最成功的项目，在反思之后，也会发现一些事情本可以做得更好。当然，也有一些事情事后看来似乎是成功的关键所在。</p>
<p>首先，我必须明确的是，这里的观点只代表我个人，不代表Go团队和Google。无论是过去还是现在，Go都是由一支专注的团队和庞大的社区付出巨大努力的结果。所以，如果你同意我的任何说法，请感谢他们。如果你不同意，请责怪我，但请保留你的意见。</p>
<p>鉴于本次演讲的题目，许多人可能期待我会分析语言中的优点和缺点。当然，我会做一些分析，但还会有更多内容，原因有几个。</p>
<p>首先，编程语言的好坏很大程度上取决于观点而不是事实，尽管许多人对Go或任何其他语言的最微不足道的功能都存在争论。</p>
<p>另外，关于换行符的位置、nil的工作方式、导出的大小写表示法、垃圾回收、错误处理等话题已经有了大量的讨论。这些话题肯定有值得讨论的地方，但几乎没什么是还没有被讨论过的。</p>
<p>但我要讨论的不仅仅是语言本身的真正原因是，语言并不是整个项目的全部。我们最初的目标不是创造一种新的编程语言，而是<a href="https://tonybai.com/2022/05/04/the-paper-of-go-programming-language-and-environment">创造一种更好的编写软件的方式</a>。我们对所使用的语言有意见——无论使用什么语言，每个人都是如此——但是我们遇到的基本问题与这些语言的特性没有太大关系，而是与在谷歌使用这些语言构建软件的过程有关。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-3.png" alt="" /><br />
<center>T恤上的第一只Gopher</center></p>
<p>新语言的创建提供了探索其他想法的新路径，但这只是一个推动因素，而不是真正的重点。如果当时我正在工作的二进制文件不需要45分钟来构建<br />
，Go语言就不会出现。但那45分钟不是因为编译器慢(因为它不慢)，也不是因为它所用的语言不好(因为它也不差)。缓慢是由其他因素造成的。</p>
<p>我们想解决的就是这些因素：构建现代服务器软件的复杂性：控制依赖性、与人员不断变化的大型团队一起编程、可维护性、高效测试、多核CPU和网络的有效利用等等。</p>
<p>简而言之，Go不仅仅是一种编程语言。当然，它是一种编程语言，这是它的定义。但它的目的是帮助提供一种更好的方式来开发高质量的软件，至少与14多年前的我们的环境相比。</p>
<p>时至今日，这仍然是它的宗旨。Go是一个使构建生产软件更容易、更高效的项目。</p>
<p>几周前，当我开始准备这次演讲时，我只有一个题目，除此之外别无其他。为了激发我的思路，我在Mastodon上向人们征求意见。不少人给予了回复。我注意到了一种趋势：人们认为我们做错的事情都在语言本身，而我们做对的事情都在语言周边，比如gofmt、部署和测试等。事实上，我觉得这令人鼓舞。我们试图做的事情似乎已经产生了效果。</p>
<p>但值得承认的是，我们在早期并没有明确真正的目标。我们可能觉得这些目标是不言自明的。为了弥补这一缺陷，我在2013年的SPLASH会议上发表了一场题为《<a href="https://go.dev/talks/2012/splash.article">谷歌的Go语言：面向软件工程的语言设计</a>》的演讲。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-4.png" alt="" /><br />
<center>Go at Google</center></p>
<p>那场演讲和相关的博客文章可能是对Go语言为何而生的最好诠释。</p>
<p>今天的演讲是SPLASH演讲的后续，回顾了我们在构建语言之后所学到的经验教训，并且可以更广泛地应用于更大的图景。</p>
<p>那么&#8230;&#8230;来谈谈一些教训。</p>
<p>首先，当然，我们有：</p>
<h2>The Gopher</h2>
<p>以Go Gopher吉祥物开始可能看起来是一个奇怪的起点，但Go gopher是Go成功的最早因素之一。在发布Go之前，我们就知道我们想要一个吉祥物来装饰周边商品——每个项目都需要周边商品——Renee French主动提出为我们制作一个这样的吉祥物。在这一点上，我们做得非常正确。</p>
<p>下面最早的Gopher毛绒玩具的图片：</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-5.jpeg" alt="" /><br />
<center>The Gopher</center></p>
<p>这是Gopher的照片，它的第一个原型不太成功。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-6.jpeg" alt="" /><br />
<center>Gopher和它进化程度较低的祖先</center></p>
<p>Gopher是一个吉祥物，它也是荣誉徽章，甚至是世界各地Go程序员的身份标志。此时此刻，你正在参加一个名为GopherCon的会议，这是众多GopherCon会议中的一个。拥有一个从第一天就准备好分享信息的容易识别、有趣的生物，对Go的成长至关重要。它天真又聪明——它可以构建任何东西!</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-7.jpeg" alt="" /><br />
<center>Gopher建造机器人（Renee French 绘图）</center></p>
<p>它为社区参与该项目奠定了基调，这是卓越的技术与真正的乐趣相结合的基调。最重要的是，Gopher是社区的一面旗帜，一面团结起来的旗帜，尤其是在早期，当Go还是编程界的新贵时。</p>
<p>这是几年前Gopher参加巴黎会议的照片，看看他们多兴奋！</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-8.png" alt="" /><br />
<center>巴黎的Gopher观众（Brad Fitzpatrick摄）</center></p>
<p>尽管如此，在知识共享署名许可(Creative Commons Attribution license)下发布Gopher的设计也许不是最好的选择。一方面，它鼓励人们以有趣的方式重新组合他，这反过来又有助于培养社区精神。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-9.png" alt="" /><br />
<center>Gopher model sheet</center></p>
<p>Renee创建了一个“模型表”来帮助艺术家在保持其精神原貌的同时进行艺术创作。</p>
<p>一些艺术家利用这些特征制作了自己版本的Gopher并获得了乐趣；Renee和我最喜欢的版本是日本设计师@tottie的和游戏程序员@tenntennen的：</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-10.jpeg" alt="" /><br />
<center>@tottie的Gopher</center></p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-11.png" alt="" /><br />
<center>@tenntennen 的gopher</center></p>
<p>但许可证的“归属”部分常常会导致令人沮丧的争论，或者导致Renee的创作不属于她，也不符合原作的精神。而且，说实话，这种归属往往只是不情愿地得到尊重，或者根本没有得到尊重。例如，我怀疑@tenntennen是否因他的Gopher插图被使用而获得补偿或是得到承认。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-12.png" alt="" /><br />
<center>gophervans.com: Boo!</center></p>
<p>因此，如果让我们重来一次，我们会认真思考确保吉祥物忠于其理想的最佳方法。维护吉祥物是一件很难的事，而且解决方案仍然难以捉摸。</p>
<p>但更多的是技术性的事情。</p>
<h2>做的对的事情</h2>
<p>这里有一份我认为我们在客观上做对了的事情的清单，特别是在回顾的时候。并不是每一个编程语言项目都做了这些事情，但清单中的每一件对Go的最终成功都至关重要。我会试着言简意赅，因为这些话题都已为人所熟知。</p>
<h3>1. 语言规范(Specification)</h3>
<p>我们从正式的语言规范开始。这不仅可以在编写编译器时锁定行为，还可以使多个编译器实现共存并就该行为达成一致。编译器本身并不是一个规范。你测试编译器的依据是什么？</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-13.png" alt="" /><br />
<center>Web上的Go语言规范</center></p>
<p>哦，顺便说一句，该规范的初稿是在这里编写的，位于悉尼达令港一栋建筑的18层。我们正在Go的家乡庆祝Go的生日。</p>
<h3>2. 多种实现</h3>
<p>Go有多个编译器实现，它们都实现相同的语言规范。有了规范就可以更容易地实现这一点。</p>
<p>有一天，伊恩·泰勒（Ian Taylor）发邮件通知我们，在阅读了我们的语言规范草案后，他自己编写了一个编译器，这让我们感到惊讶！</p>
<pre><code>Subject: A gcc frontend for Go
From: Ian Lance Taylor
Date: Sat, Jun 7, 2008 at 7:06 PM
To: Robert Griesemer, Rob Pike, Ken Thompson

One of my office-mates pointed me at http://.../go_lang.html .  It
seems like an interesting language, and I threw together a gcc
frontend for it.  It's missing a lot of features, of course, but it
does compile the prime sieve code on the web page.
</code></pre>
<p>这的确令人兴奋，但更多的编译器实现也随之而来了，所有这些都因正式规范的存在而成为可能。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-14.jpeg" alt="" /><br />
<center>很多编译器</center></p>
<p>拥有多个编译器帮助我们改进了语言并完善了规范，并为那些不太喜欢我们类似Plan-9的业务方式的其他人提供了替代环境。稍后会详细介绍。如今有很多兼容的实现，这很棒！</p>
<h3>3. 可移植性</h3>
<p>我们使Go应用的交叉编译变得轻而易举，程序员可以在他们喜欢的任何平台上工作，并交付到任何需要的平台。使用Go可能比使用任何其他语言更容易达成这一点。很容易将编译器视为运行它的机器的本地编译器，但没有理由这么认为。打破这个假设具有重要意义，这对许多开发者来说都是新鲜事。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-15.png" alt="" /><br />
<center>可移植性</center></p>
<h3>4. 兼容性</h3>
<p>我们努力使语言达到1.0版本的标准，然后通过兼容性保证将其固定下来，这对Go的采用产生了非常明显的影响！我不理解为什么大多数其他项目一直在抵制这样做。是的，保持强大兼容性的确需要付出成本，但它可以阻止功能特性停滞，而在这个几乎没有其他东西保持稳定的世界里，不必担心新版本的Go会破坏你的项目，这足以令人感到欣喜！</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-16.png" alt="" /><br />
<center>Go兼容性承诺</center></p>
<h3>5. 标准库</h3>
<p>尽管它的增长在某种程度上是偶然的，因为在一开始没有其他地方可以安装Go代码，但拥有一个坚实、制作精良的标准库，其中包含编写21世纪服务器代码所需的大部分内容，这是一个重大资产。在我们积累了足够的经验来理解还应该提供什么之前，它使整个社区都使用相同的工具包。这非常有效，并有助于防止出现不同版本的库，从而帮助统一社区。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-17.jpeg" alt="" /><br />
<center>标准库</center></p>
<h3>6. 工具</h3>
<p>我们确保该语言易于解析，从而支持工具构建。起初我们认为Go需要一个IDE，但易于构建工具意味着，随着时间的推移，IDE将会出现在Go上。他们和gopls一起做到了，而且他们非常棒。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-18.jpeg" alt="" /><br />
<center>工具</center></p>
<p>我们还为编译器提供了一套辅助工具，例如自动化测试、覆盖率和代码审查(code vetting)。当然还有go命令，它集成了整个构建过程，也是许多项目构建和维护其Go代码所需的一切。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-19.jpeg" alt="" /><br />
<center>快速构建</center></p>
<p>此外，Go获得了快速构建的声誉，这也没有什么坏处。</p>
<h3>7. Gofmt</h3>
<p>我将gofmt作为一个单独的项目从工具中拿出来，因为它是一个不仅在Go上而且在整个编程社区上留下了印记的工具。在Robert编写gofmt之前（顺便说一句，他从一开始就坚持这样做），自动格式化程序的质量不高，因此大多未被使用。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-20.png" alt="" /><br />
<center>gofmt谚语</center></p>
<p>gofmt的成功表明了代码自动格式化可以做得很好，今天几乎每种值得使用的编程语言都有一个标准格式化程序。我们不再为空格和换行符争论，这节省了大量时间了，这也让那些花在定义标准格式和编写这段相当困难的代码实现格式自动化上的时间显得超值。</p>
<p>此外，gofmt还使无数其他工具成为可能，例如简化器、分析器甚至是代码覆盖率工具。因为gofmt的内容成为了任何人都可以使用的库，所以你可以解析程序、编辑AST，然后打印完美的字节输出，供人类和机器使用。</p>
<p>谢谢，罗伯特。</p>
<p>不过，恭喜你就够了。接下来，我们来谈谈一些更有争议的话题。</p>
<h2>并发性</h2>
<p>并发有争议吗？嗯，在我2002年加入谷歌的那年肯定有。John Ousterhout曾说过：线程很糟糕。许多人都同意他的观点，因为线程似乎非常难以使用。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-21.png" alt="" /><br />
<center>John Ousterhout不喜欢线程</center></p>
<p>谷歌的软件几乎总是避免使用它们，可以说是彻底禁止使用，而制定这一禁令的工程师引用了Ousterhout的言论。这让我很困扰。自20世纪70年代以来，我一直在做类似的并发事情，有时候甚至没有意识到，在我看来这很强大。但经过反思，很明显Ousterhout犯了两个错误。首先，他的结论超出了他有兴趣使用线程的领域，其次，他主要是在抱怨使用笨拙的低级包如pthread之类的线程，而不是抱怨这一基本思想。</p>
<p>像这样混淆解决方案和问题是世界各地工程师常犯的错误。有时，提出的解决方案比它解决的问题更难，并且很难看到有更简单的路径。但我离题了。</p>
<p>根据经验，我知道有更好的方法来使用线程，或者无论我们选择怎么称呼它们，我甚至在Go语言出现之前就曾就此发表过演讲。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-22.png" alt="" /><br />
<center>Newsqueak中的并发</center></p>
<p>但我并不孤单，其他许多语言、论文甚至书籍都表明，并发编程可以做得很好，不仅我知道这一点。它只是还没有在主流中流行起来，Go的诞生部分地就是为了解决这个问题。在那次臭名昭著的45分钟构建中，我试图向一个非线程二进制文件添加一个线程，这非常困难，因为我们使用了错误的工具。</p>
<p>回顾过去，我认为可以公平地说，Go在让编程界相信并发是一种强大工具方面发挥了重要作用，特别是在多核网络世界中，它可以比pthread做得更好。如今，大多数主流语言都对并发提供了很好地支持。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-23.png" alt="" /><br />
<center>Google 3.0</center></p>
<p>另外，Go的并发版本在导致它出现的语言线中有些新颖，因为它使goroutine变得平淡无奇。没有协程，没有任务，没有线程，没有名称，只有goroutine。我们发明了“goroutine”这个词，因为没有适合的现有术语。时至今日，我仍然希望Unix的拼写命令可以学会它。</p>
<p>顺便说一句，因为我经常被问到，让我花一分钟时间谈谈async/await。看到async/await模型及其相关风格成为许多语言选择支持并发的方式，我有点难过，但它肯定是对pthreads的巨大改进。</p>
<p>与goroutine、channel和select相比，async/await对语言实现者来说更容易也更小，可以更容易地内建或后移植到现有平台中。但它将一些复杂性推回给了程序员，通常会导致Bob Nystrom所著名的“<a href="https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/">彩色函数</a>”。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-24.png" alt="" /><br />
<center>你的函数是什么颜色的</center></p>
<p>我认为Go表明了CSP这种不同但更古老的模型可以完美地嵌入到过程化语言中，没有这种复杂性。我甚至看到它几次作为库实现。但它的实现，如果做得好，需要显著的运行时复杂性，我可以理解为什么一些人更倾向于不在他们的系统中内置它。不管你提供什么并发模型，重要的是只提供一次，因为一个环境提供多个并发实现可能会很麻烦。Go当然通过把它放在语言中而不是库中解决了这个问题。</p>
<p>关于这些问题可能要讲整场演讲，但目前就这些吧。</p>
<p>并发的另一个价值在于，它使Go看起来像是全新的东西。如我所说，一些其他语言在之前已经支持了它，但它们从未进入主流，而Go对并发的支持是吸引初学者采用的一个主要因素，它吸引了以前没有使用过并发但对其可能性感兴趣的程序员。</p>
<p>这就是我们犯下两个大错误的地方。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-25.png" alt="" /><br />
<center>耳语的Gopher(Cooperating Sequential Processes)</center></p>
<p>首先，并发很有趣，我们很高兴拥有它，但我们设想的使用案例大多是服务器相关的，意在在net/http等关键库中完成，而不是在每个程序的所有地方完成。当许多程序员使用它时，他们努力研究它如何真正帮助他们。我们应该一开始就解释清楚，语言中的并发支持真正带到桌面的是更简单的服务器软件。这个问题空间对许多人很重要，但并非所有尝试Go的人都是如此，这点指导不足是我们的责任。</p>
<p>相关的第二点是，我们用了太长时间来澄清并行和并发之间的区别——支持在多核机器上并行执行多个计算，以及一种组织代码的方式，以便很好地执行并行计算。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-26.png" alt="" /><br />
<center>并发不是并行</center></p>
<p>无数程序员试图通过使用goroutine来并行化他们的代码以使其更快，但经常对结果中的速度降低感到困惑。仅当基础问题本质上是并行的时候，例如服务HTTP请求，并发代码才会通过并行化而变快。我们在解释这一点上做得很糟糕，结果让许多程序员感到困惑，可能还赶走了一些人。</p>
<p>为了解决这个问题，我在2012年Waza上给Heroku的开发者大会做了一个题为“<a href="https://www.youtube.com/watch?v=oV9rvDllKEg">并发不是并行</a>”的演讲。这是一次很有趣的演讲，但它应该更早发生。</p>
<p>对此表示歉意。但好处仍然存在：Go帮助普及了并发性作为构建服务器软件的一种方式。</p>
<h2>接口</h2>
<p>很明显，接口与并发都是Go中与众不同的思想。它们是Go对面向对象设计的答案，采用最初关注行为的风格，尽管新来者一直在努力使结构体承担这一角色。</p>
<p>使接口动态化，无需提前宣布哪些类型实现了它们，这困扰了一些早期评论者，并且仍然恼火一小部分人，但它对Go培育的编程风格很重要。大部分标准库都是建立在它们的基础之上的，而更广泛的主题如测试和管理依赖也高度依赖于它们慷慨的“欢迎所有人”的天性。</p>
<p>我觉得接口是Go中设计最好的部分之一。</p>
<p>除了一些早期关于接口定义中是否应该包括数据的讨论之外，它们在讨论的第一天就已经成形。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-27.png" alt="" /><br />
<center>GIF 解码器：Go接口的练习（Rob Pike和Nigel Tao 2011）</center></p>
<p>在这个问题上还有一个故事要讲。</p>
<p>在Robert和我的办公室里那著名的第一天，我们讨论了关于多态性应该怎么处理的问题。Ken和我从C语言中知道qsort可以作为一个困难的测试用例，所以我们三个人开始讨论用我们这种初具雏形的语言如何实现一个类型安全的排序例程(routine)。</p>
<p>Robert和我几乎同时产生了同样的想法：在类型上使用方法来提供排序所需的操作。这个概念很快发展成了一个想法，即值类型拥有作为方法定义的行为，一组方法可以提供函数可以操作的接口。Go的接口几乎立即就出现了。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-28.png" alt="" /><br />
<center>sort.Interface</center></p>
<p>有一点没人经常提到：Go的sort函数是作为一个在接口上操作的函数实现的。这与大多数人熟悉的面向对象编程风格不同，但这是一个非常强大的想法。</p>
<p>这个想法对我们来说非常激动人心，它可能成为一个基础的编程构造，这令我们陶醉。当Russ Cox加入时，他很快指出了I/O如何完美地融入这个想法，标准库的发展非常迅速，在很大程度上依赖于三个著名的接口：空接口(interface{})、Writer和Reader，每个接口平均包含两个第三个方法。那些微小的方法对Go来说是惯用法，无处不在。</p>
<p>接口的工作方式不仅成为Go的一个显著特性，它们也成为我们思考库、泛型和组合的方式。这是让人兴奋的事情。</p>
<p>但我们在这个问题上停止讨论可能是一个错误。</p>
<p>你看，我们之所以走上这条路，至少在一定程度上是因为我们看到泛型编程太容易鼓励一种倾向于在算法之前首先关注类型的思考方式。过早抽象而不是有机设计。容器而不是函数。</p>
<p>我们在语言中正确定义了通用容器——map，切片，数组，channel——而不给程序员访问它们所包含的泛型。这可以说是一个错误。我们相信，我认为仍然正确的是，大多数简单的编程任务可以很好地由这些类型来处理。但有一些不能，语言提供的和用户可以控制的之间的障碍肯定困扰了一些人。</p>
<p>简而言之，尽管我不会改变接口的任何工作方式，但它们以需要十多年时间才能纠正的方式影响了我们的思维。Ian Taylor从一开始就推动我们面对这个问题，但在接口作为Go编程基石的情况下，这是相当困难的。</p>
<p>评论者经常抱怨我们应该使用泛型，因为它们“很简单”，在某些语言中可能确实如此，但接口的存在意味着任何新的多态形式都必须考虑到它们。找到一种可以与语言的其余部分很好地协同工作的前进方法需要多次尝试，几次中止的实现，以及许多小时、天数和周数的讨论。最终，在Phil Wadler的带领下，我们召集了一些类型理论家来提供帮助。即使在语言中有了可靠的泛型模型，作为方法集存在的接口也仍然存在一些遗留问题。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-29.png" alt="" /><br />
<center>泛型版sort</center></p>
<p>如你所知，最终的答案是设计一个可以吸收更多多态形式的接口泛化，从“方法集合”过渡到“类型集合”。这是一个微妙但深刻的举措，大多数社区似乎都可以接受，尽管我怀疑抱怨声永远不会停止。</p>
<p>有时候要花很多年的时间来弄清楚一些事情，或者甚至弄清楚你并不能完全弄明白它。但你还是要继续前进。</p>
<p>顺便说一句，我希望我们有一个比“泛型”更好的术语，它起源于表示一种不同的数据结构中心多态风格。“参数多态”是Go提供的该功能的正确术语，这是一个准确的术语，但它难听。于是我们依然说“泛型”，尽管它不太恰当。</p>
<h2>编译器</h2>
<p>困扰编程语言社区的一件事是，早期的Go编译器是用C语言编写的。在他们看来，正确的方式是使用LLVM或类似的工具包，或者用Go语言本身编写编译器，这称为自举。我们没有做这两者中的任何一种，原因有几个。</p>
<p>首先，自举一种新语言要求至少其编译器的第一步必须用现有语言完成。对我们来说，C语言是显而易见的选择，因为Ken已经编写了C编译器，并且其内部结构可以很好地作为Go编译器的基础。此外，用自己的语言编写编译器，同时开发该语言，往往会产生一种适合编写编译器的语言，但这不是我们想要的语言。</p>
<p>早期的编译器工作良好，它可以很好地引导语言。但从某种意义上说，它有点奇怪，实际上它是一个Plan 9风格的编译器，使用旧的编译器编写思想，而不是新的思想，如<a href="https://tonybai.com/2022/10/21/understand-go-ssa-by-example/">静态单一赋值(SSA)</a>。生成的代码平庸，内部不太漂亮。但它是务实高效的，编译器代码本身体积适中，对我们来说也很熟悉，这使得我们在尝试新想法时可以快速进行更改。一个关键步骤是添加自动增长的分段堆栈。这很容易添加到我们的编译器中，但是如果我们使用像LLVM这样的工具包，考虑到ABI和垃圾收集器支持所需的更改，将这种更改集成到完整的编译器套件中是不可行的。</p>
<p>另一个工作良好的区域是交叉编译，这直接来自原始Plan 9编译器套件的工作方式。</p>
<p>按照我们的方式行事，无论多么非正统，都有助于我们快速前进。有些人对这一选择感到冒犯，但这对当时的我们来说是正确的选择。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-30.png" alt="" /><br />
<center>Go 1.5之后的Go编译器架构</center></p>
<p>对于Go 1.5版本，Russ Cox编写了一个工具，可以半自动将编译器从C转换为Go。到那时，语言已经完成，编译器导向的语言设计的担忧也就无关紧要了。有一些关于这个过程的在线演讲值得一看。我在2016年的GopherCon上做了一个关于汇编器的演讲，这在我毕生追求可移植性的过程中是一个高点。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-31.png" alt="" /><br />
<center>Go汇编器设计(GopherCon 2016) </center></p>
<p>我们从C开始做了正确的事情，但最终将编译器翻译为Go，使我们能够将Go所具有的所有优势带到其开发中，包括测试、工具、自动重写、性能分析等。当前的编译器比原始编译器干净得多，并且可以生成更好的代码。但是，当然，这就是自举的工作原理。</p>
<p>请记住，我们的目标不仅仅是一种语言，而是更多。</p>
<p>我们不寻常的做法绝不是对LLVM或语言社区中任何人的侮辱。我们只是使用了最适合我们任务的工具。当然，今天有一个LLVM托管的Go编译器，以及许多其他应该有的编译器。</p>
<h2>项目管理</h2>
<p>我们从一开始就知道，要成功，Go必须是一个开源项目。但我们也知道，在弄清楚关键的思想和有一个工作的实现之前，私下开发会更高效。头两年对澄清我们在试图实现什么，而不受干扰，是必不可少的。</p>
<p>向开源的转变是一个巨大的改变，也很具教育意义。来自社区的投入是压倒性的。与社区的接触花费了大量的时间和精力，尤其是对Ian，不知怎么他找到时间来回答任何人提出的每一个问题。但它也带来了更多。我仍然惊叹在Alex Brainman的指导下，社区完全独立完成的Windows移植的速度。那很神奇。</p>
<p>我们花了很长时间来理解转向开源项目的影响，以及如何管理它。</p>
<p>特别是，公平地说，我们花了太长时间来理解与社区合作的最佳方式。本次演讲的一个主题是我们的沟通不足——即使我们认为我们正在进行良好沟通——由于误解和不匹配的期望，大量时间被浪费了。本可以做得更好。</p>
<p>但是，随着时间的推移，我们说服了社区中的至少那一部分和我们在一起的人，我们的一些想法，虽然与常见的开源方式不同，但具有价值。最重要的是<strong>我们坚持通过强制代码审查和对细节的穷尽关注来维护高质量代码</strong>。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-32.png" alt="" /><br />
<center>Mission Control (drawing by Renee French) </center></p>
<p>一些项目的工作方式不同，它们快速接受代码，然后在提交后进行清理。Go项目则相反，力图将质量放在第一位。我相信这是更有效的方式，但它将更多的工作推回社区，如果他们不理解其价值，他们就不会感到应有的欢迎。在这方面还有很多东西要学习，但我相信现在的情况已经好多了。</p>
<p>顺便说一句，有一个历史细节不是广泛为人知的。该项目使用过4个不同的内容管理系统：SVN、Perforce、Mercurial和Git。Russ Cox做了一份艰巨的工作，保留了所有历史，所以即使今天，Git仓库也包含了在SVN中做出的最早的更改。我们都认为保留历史很有价值，我要感谢他做了这项艰苦的工作。</p>
<p>还有一点。人们经常认为谷歌会告诉Go团队该做什么。这绝对不是真的。谷歌对Go的支持非常慷慨，但它不制定议程。社区的投入要大得多。谷歌内部有一个巨大的Go代码库，团队用它来测试和验证版本，但这是通过从公共仓库导入谷歌完成的，而不是反过来。简而言之，核心Go团队由谷歌支付薪水，但他们是独立的。</p>
<h2>包管理</h2>
<p>Go的包管理开发过程做得并不好。我相信，语言本身的包设计非常出色，并且在我们讨论的第一年左右的时间里消耗了大量的时间。如果你感兴趣的话，我之前提到的SPLASH演讲详细解释了它为什么会这样工作。</p>
<p>一个关键点是使用纯字符串来指定导入语句中的路径，从而提供了我们正确认为很重要的灵活性。但从只有一个“标准库”到从网络导入代码的转变是坎坷的。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-33.png" alt="" /><br />
<center>修复云（Renee French 绘制）</center></p>
<p>有两个问题。</p>
<p>首先，我们这些Go核心团队的成员很早就熟悉Google的工作方式，包括它的monorepo(单一代码仓库)和每个人都在负责构建。但是我们没有足够的经验来使用具有大量包版本的包管理器以及尝试解决依赖关系图的非常困难的问题。直到今天，很少有人真正理解技术的复杂性，但这并不能成为我们未能从一开始就解决这些问题的借口。这尤其令人尴尬，因为我曾是一个失败项目的技术负责人，为谷歌的内部构建做类似的事情，我应该意识到我们面临的是什么。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-34.png" alt="" /><br />
<center>deps.dev</center></p>
<p>我在deps.dev上的工作是一种忏悔。</p>
<p>其次，让社区参与帮助解决依赖管理问题的初衷是好的，但当最终设计出来时，即使有大量的文档和有关理论的文章，社区中的许多人仍然感到受到了轻视。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-35.png" alt="" /><br />
<center>pkg.go.dev</center></p>
<p>这次失败给团队上了一课，让他们知道如何真正与社区互动，并且自此取得了很大的进步。</p>
<p>不过，现在事情已经解决了，新的设计在技术上非常出色，并且似乎对大多数用户来说效果很好。只是时间太长，而且道路崎岖不平。</p>
<h2>文档和示例</h2>
<p>我们事先没有得到的另一件事是文档。我们写了很多文档，并认为我们做得很好，但很快就发现社区想要的文档级别与我们的预期不同。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-36.png" alt="" /><br />
<center>修理图灵机的Gopher（Renee French 绘图）</center></p>
<p>关键缺失的一部分是最简单函数的示例。我们曾以为只需说明某个东西的功能就足够了，但我们花费了太长时间才接受到展示如何使用它的价值更大。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-37.png" alt="" /><br />
<center>可执行的例子</center></p>
<p>不过，我们已经吸取了教训。现在文档中有很多示例，大部分是由开源贡献者提供的。我们很早就做的一件事就是让它们在网络上可执行。我在2012年的Google I/O大会上做了一次演讲，展示了并发的实际应用，Andrew Gerrand 编写了一段可爱的Web goo，使得直接从浏览器运行代码片段成为可能。我怀疑这是第一次这样做，但Go是一种编译语言，很多观众以前从未见过这个技巧。然后该技术被部署到博客和在线包文档中。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-38.png" alt="" /><br />
<center>Go playground</center></p>
<p>也许更重要的是我们对Go Playground的支持，这是一个免费的开放沙箱，供人们尝试，甚至开发代码。</p>
<h2>结论</h2>
<p>我们已经走了很长一段路。</p>
<p>回顾过去，很明显很多事情都做得对，并且它们都帮助Go取得了成功。但还有很多事情可以做得更好，重要的是要承认这些问题并从中学习。对于任何托管重要开源项目的人来说，双方都有教训。</p>
<p>我希望我对这些教训及其原因的历史回顾会有所帮助，也许可以作为对那些反对我们正在做的事情和我们如何做的人的一种道歉/解释。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-39.png" alt="" /><br />
<center>GopherConAU 2023 吉祥物，作者：Renee French</center></p>
<p>但在推出 14 年后，我们终于来了。公平地说，总的来说这是一个非常好的地方。</p>
<p>很大程度上是因为通过设计和开发Go作为一种编写软件的方式（而不仅仅是作为一种编程语言）做出的决定，我们已经到达了一个新的地方。</p>
<p>我们到达这里的部分原因包括：</p>
<ul>
<li>一个强大的标准库，可实现服务器代码所需的大部分基础知识</li>
<li>并发作为该语言的“一等公民”</li>
<li>基于组合而不是继承的方法</li>
<li>澄清依赖管理的打包模型</li>
<li>集成的快速构建和测试工具</li>
<li>严格一致的代码格式</li>
<li>注重可读性而非聪明性</li>
<li>兼容性保证</li>
</ul>
<p>最重要的是，得益于令人难以置信的乐于助人且多元化的Gophers社区的支持。</p>
<p><img src="https://tonybai.com/wp-content/uploads/what-we-got-right-what-we-got-wrong-40.png" alt="" /><br />
<center>多元化的社区（@tenntennen 绘图）</center></p>
<p>也许这些问题最有趣的结果是，无论是谁编写的Go代码的外观和工作原理都是一样的，基本上没有使用该语言的不同子集的派系，并且保证随着时间的推移代码可继续编译和运行。对于主要编程语言来说，这可能是第一次。</p>
<p>我们绝对做对了。</p>
<p>谢谢。</p>
<hr />
<p><a href="https://public.zsxq.com/groups/51284458844544">“Gopher部落”知识星球</a>旨在打造一个精品Go学习和进阶社群！高品质首发Go技术文章，“三天”首发阅读权，每年两期Go语言发展现状分析，每天提前1小时阅读到新鲜的Gopher日报，网课、技术专栏、图书内容前瞻，六小时内必答保证等满足你关于Go语言生态的所有需求！2024年，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>著名云主机服务厂商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>
</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/01/07/what-we-got-right-what-we-got-wrong/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go是一门面向对象编程语言吗</title>
		<link>https://tonybai.com/2023/03/12/is-go-object-oriented/</link>
		<comments>https://tonybai.com/2023/03/12/is-go-object-oriented/#comments</comments>
		<pubDate>Sun, 12 Mar 2023 12:58:07 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Alef]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[ChatGPT]]></category>
		<category><![CDATA[CSP]]></category>
		<category><![CDATA[FAQ]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[gopl]]></category>
		<category><![CDATA[Go程序设计语言]]></category>
		<category><![CDATA[has-a]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[is-a]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Newsqueak]]></category>
		<category><![CDATA[oberon]]></category>
		<category><![CDATA[OO]]></category>
		<category><![CDATA[OOP]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[receiver]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Simula]]></category>
		<category><![CDATA[Smalltalk]]></category>
		<category><![CDATA[String]]></category>
		<category><![CDATA[struct]]></category>
		<category><![CDATA[TIOBE]]></category>
		<category><![CDATA[type-embedding]]></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=3819</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2023/03/12/is-go-object-oriented Go语言已经开源13年了，在近期TIOBE发布的2023年3月份的编程语言排行榜中，Go再次冲入前十，相较于Go在2022年底的排名提升了2个位次： 《Go语言第一课》专栏中关于Go在这两年开始飞起的“预言”也正在逐步成为现实^_^，大家学习Go的热情也在快速提升， 《Go语言第一课》专栏的学习的人数年后也快速增加，快突破2w了。 很多专栏的订阅者都是第一次接触Go，他们中的很多是来自像Java, Ruby这样的OO(面向对象)语言阵营的，他们学习Go之后的第一个问题便是：Go是一门OO语言吗？在这篇博文中，我们就来探讨一下。 一. 溯源 在公认的Go语言“圣经”《Go程序设计语言》一书中，有这样一幅Go语言与其主要的先祖编程语言的亲缘关系图： 从图中我们可以清晰看到Go语言的“继承脉络”： 从C语言那里借鉴了表达式语法、控制语句、基本数据类型、值参数传递、指针等； 从Oberon-2语言那里借鉴了package、包导入和声明的语法，而Object Oberon提供了方法声明的语法。 从Alef语言以及Newsqueak语言中借鉴了基于CSP的并发语法。 我们看到，从Go先祖溯源的情况来看，Go并没有从纯面向对象语言比如Simula、SmallTalk等那里取经。 Go诞生于2007年，开源于2009年，那正是面向对象语言和OO范式大行其道的时期。不过Go设计者们觉得经典OO的继承体系对程序设计与扩展似乎并无太多好处，还带来了较多的限制，因此在正式版本中并没有支持经典意义上的OO语法，即基于类和对象实现的封装、继承和多态这三大OO主流特性。 但这是否说明Go不是一门OO语言呢？也不是！ 带有面向对象机制的Object Oberon也是Go的先祖语言之一，虽然Object Oberon的OO语法又与我们今天常见的语法有较大差异。 就此问题，我还特意咨询了ChatGPT^_^，得到的答复如下： ChatGPT认为：Go支持面向对象，提供了对面向对象范式基本概念的支持，但支持的手段却并不是类与对象。 那么针对这个问题Go官方是否有回应呢？有的，我们来看一下。 二. 官方声音 Go官方在FAQ中就Go是否是OO语言做了简略回应： Is Go an object-oriented language? Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/is-go-object-oriented-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2023/03/12/is-go-object-oriented">本文永久链接</a> &#8211; https://tonybai.com/2023/03/12/is-go-object-oriented</p>
<p><a href="https://tonybai.com/2022/11/11/go-opensource-13-years/">Go语言已经开源13年了</a>，在近期<a href="https://www.tiobe.com/tiobe-index/">TIOBE</a>发布的2023年3月份的编程语言排行榜中，Go再次冲入前十，相较于Go在<a href="https://tonybai.com/2022/12/29/the-2022-review-of-go-programming-language">2022年底的排名</a>提升了2个位次：</p>
<p><img src="https://tonybai.com/wp-content/uploads/is-go-object-oriented-2.png" alt="" /></p>
<p><a href="http://gk.link/a/10AVZ">《Go语言第一课》专栏</a>中关于Go在这两年开始飞起的“预言”也正在逐步成为现实^_^，大家学习Go的热情也在快速提升， <a href="http://gk.link/a/10AVZ">《Go语言第一课》专栏</a>的学习的人数年后也快速增加，快突破2w了。</p>
<p>很多专栏的订阅者都是第一次接触Go，他们中的很多是来自像Java, Ruby这样的OO(面向对象)语言阵营的，他们学习Go之后的第一个问题便是：<strong>Go是一门OO语言吗</strong>？在这篇博文中，我们就来探讨一下。</p>
<h2>一. 溯源</h2>
<p>在公认的Go语言“圣经”<a href="http://www.gopl.io">《Go程序设计语言》</a>一书中，有这样一幅Go语言与其主要的先祖编程语言的亲缘关系图：</p>
<p><img src="https://tonybai.com/wp-content/uploads/is-go-object-oriented-4.png" alt="" /></p>
<p>从图中我们可以清晰看到Go语言的“继承脉络”：</p>
<ul>
<li>从<a href="https://tonybai.com/tag/c">C语言</a>那里借鉴了表达式语法、控制语句、基本数据类型、值参数传递、指针等；</li>
<li>从<a href="https://cseweb.ucsd.edu/~wgg/CSE131B/oberon2.htm">Oberon-2语言</a>那里借鉴了package、包导入和声明的语法，而Object Oberon提供了方法声明的语法。</li>
<li>从<a href="http://doc.cat-v.org/plan_9/2nd_edition/papers/alef/ref">Alef语言</a>以及<a href="https://newspeaklanguage.org">Newsqueak语言</a>中借鉴了基于<a href="https://cs.stanford.edu/people/eroberts/courses/soco/projects/2008-09/tony-hoare/csp.html">CSP</a>的并发语法。</li>
</ul>
<p>我们看到，从Go先祖溯源的情况来看，Go并没有从纯面向对象语言比如Simula、<a href="http://en.wikipedia.org/wiki/Smalltalk">SmallTalk</a>等那里取经。</p>
<p>Go诞生于2007年，开源于2009年，那正是面向对象语言和OO范式大行其道的时期。不过Go设计者们觉得经典OO的继承体系对程序设计与扩展似乎并无太多好处，还带来了较多的限制，因此在正式版本中并没有支持经典意义上的OO语法，即基于类和对象实现的封装、继承和多态这三大OO主流特性。</p>
<p>但这是否说明Go不是一门OO语言呢？也不是！ 带有面向对象机制的<a href="http://www.projectoberon.net/">Object Oberon</a>也是Go的先祖语言之一，虽然Object Oberon的OO语法又与我们今天常见的语法有较大差异。</p>
<p>就此问题，我还特意咨询了<a href="https://chat.openai.com/chat">ChatGPT</a>^_^，得到的答复如下：</p>
<p><img src="https://tonybai.com/wp-content/uploads/is-go-object-oriented-3.png" alt="" /></p>
<p>ChatGPT认为：Go支持面向对象，提供了对面向对象范式基本概念的支持，但支持的手段却并不是类与对象。</p>
<p>那么针对这个问题Go官方是否有回应呢？有的，我们来看一下。</p>
<h2>二. 官方声音</h2>
<p><a href="https://go.dev/doc/faq#Is_Go_an_object-oriented_language">Go官方在FAQ中就Go是否是OO语言做了简略回应</a>：</p>
<pre><code>Is Go an object-oriented language?

Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The concept of “interface” in Go provides a different approach that we believe is easy to use and in some ways more general. There are also ways to embed types in other types to provide something analogous—but not identical—to subclassing. Moreover, methods in Go are more general than in C++ or Java: they can be defined for any sort of data, even built-in types such as plain, “unboxed” integers. They are not restricted to structs (classes).

Also, the lack of a type hierarchy makes “objects” in Go feel much more lightweight than in languages such as C++ or Java.
</code></pre>
<p>粗略翻译过来就是：</p>
<pre><code>Go是一种面向对象的语言吗？

是，也不是。虽然Go有类型和方法，并且允许面向对象的编程风格，但却没有类型层次。Go中的“接口”概念提供了一种不同的OO实现方案，我们认为这种方案更易于使用，而且在某些方面更加通用。还有一些可以将类型嵌入到其他类型中以提供类似子类但又不等同于子类的机制。此外，Go中的方法比C++或Java中的方法更通用：Go可以为任何数据类型定义方法，甚至是内置类型，如普通的、“未装箱的”整数。Go的方法并不局限于结构体（类）。

此外，由于去掉了类型层次，Go中的“对象”比C++或Java等语言更轻巧。
</code></pre>
<p>“是，也不是”！我们看到Go官方给出了一个“对两方都无害”的中庸的回答。那么Go社区是怎么认为的呢？我们来看看Go社区的一些典型代表的观点。</p>
<h2>三. 社区声音</h2>
<p><a href="https://rakyll.org/">Jaana Dogan</a>和<a href="https://spf13.com/">Steve Francia</a>都是前Go核心团队成员，他们在加入Go团队之前对“Go是否是OO语言”这一问题也都有自己的观点论述。</p>
<p>Jaana Dogan在<a href="https://rakyll.org/typesystem/">《The Go type system for newcomers》</a>一文中给出的观点是：<strong>Go is considered as an object-oriented language even though it lacks type hierarchy</strong>，即“Go被认为是一种面向对象的语言，即使它缺少类型层次结构”。</p>
<p>而更早一些的是Steve Francia在2014年发表的文章<a href="https://spf13.com/p/is-go-an-object-oriented-language/">《Is Go an Object Oriented language?》</a>中的结论观点：<strong>Go，没有对象或继承的面向对象编程</strong>，也可称为“无对象”的OO编程模型。</p>
<p>两者表达的遣词不同，但含义却异曲同工，即<strong>Go支持面向对象编程，但却不是通过提供经典的类、对象以及类型层次来实现的</strong>。</p>
<p>那么Go究竟是以何种方式实现对OOP的支持的呢？我们继续看！</p>
<h2>四. Go的“无对象”OO编程</h2>
<p>经典OO的三大特性是封装、继承与多态，这里我们看看Go中是如何对应的。</p>
<h3>1. 封装</h3>
<p>封装就是把数据以及操作数据的方法“打包”到一个抽象数据类型中，这个类型封装隐藏了实现的细节，所有数据仅能通过导出的方法来访问和操作。 这个抽象数据类型的实例被称为<strong>对象</strong>。经典OO语言，如Java、C++等都是通过类(class)来表达封装的概念，通过类的实例来映射对象的。熟悉Java的童鞋一定记得<a href="https://book.douban.com/subject/2130190/">《Java编程思想》</a>一书的第二章的标题：“一切都是对象”。在Java中所有属性、方法都定义在一个个的class中。</p>
<p>Go语言没有class，那么封装的概念又是如何体现的呢？来自OO语言的初学者进入Go世界后，都喜欢“对号入座”，即Go中什么语法元素与class最接近！于是他们找到了struct类型。</p>
<p>Go中的struct类型中提供了对真实世界聚合抽象的能力，struct的定义中可以包含一组字段(field)，如果从OO角度来看，你也可以将这些字段视为属性，同时，我们也可以为struct类型定义方法(method)，下面例子中我们定义了一个名为Point的struct类型，它拥有一个导出方法Length：</p>
<pre><code>type Point struct {
    x, y float64
}

func (p Point) Length() float64 {
    return math.Sqrt(p.x * p.x + p.y * p.y)
}
</code></pre>
<p>我们看到，从语法形式上来看，与经典OO声明类的方法不同，Go方法声明并不需要放在声明struct类型的大括号中。Length方法与Point类型建立联系的纽带是一个被称为<strong>receiver参数</strong>的语法元素。</p>
<p>那么，struct是否就是对应经典OO中的类呢? 是，也不是！从数据聚合抽象来看，似乎是这样, struct类型可以拥有多个异构类型的、代表不同抽象能力的字段(比如整数类型int可以用来抽象一个真实世界物体的长度，string类型字段可以用来抽象真实世界物体的名字等)。</p>
<p>但从拥有方法的角度，不仅是struct类型，<strong>Go中除了内置类型的所有其他具名类型都可以拥有自己的方法</strong>，哪怕是一个底层类型为int的新类型MyInt：</p>
<pre><code>type MyInt int

func(a MyInt)Add(b int) MyInt {
    return a + MyInt(b)
}
</code></pre>
<h3>2. 继承</h3>
<p>就像前面说的，Go设计者在Go诞生伊始就重新评估了对经典OO的语法概念的支持，最终放弃了对诸如类、对象以及类继承层次体系的支持。也就是说：<strong>在Go中体现封装概念的类型之间都是“路人”，没有亲爹和儿子的关系的“牵绊”</strong>。</p>
<p>谈到OO中的继承，大家更多想到的是子类继承了父类的属性与方法实现。Go虽然没有像Java extends关键字那样的显式继承语法，但Go也另辟蹊径地对“继承”提供了支持。这种支持方式就是类型嵌入(type embedding)，看一个例子：</p>
<pre><code>type P struct {
    A int
    b string
}

func (P) M1() {
}

func (P) M2() {
}

type Q struct {
    c [5]int
    D float64
}

func (Q) M3() {
}

func (Q) M4() {
}

type T struct {
    P
    Q
    E int
}

func main() {
    var t T
    t.M1()
    t.M2()
    t.M3()
    t.M4()
    println(t.A, t.D, t.E)
}
</code></pre>
<p>我们看到类型T通过嵌入P、Q两个类型，“继承”了P、Q的导出方法(M1~M4)和导出字段(A、D)。</p>
<blockquote>
<p>关于类型嵌入的具体语法说明，大家可以温习一下<a href="https://mp.weixin.qq.com/s/nRkEe5v3GNTjxJNbYflRag">《十分钟入门Go语言》</a>或<a href="http://gk.link/a/10AVZ">《Go语言第一课》专栏</a>。</p>
</blockquote>
<p>不过实际Go中的这种“继承”机制并非经典OO中的继承，其外围类型(T)与嵌入的类型(P、Q)之间没有任何“亲缘”关系。P、Q的导出字段和导出方法只是被提升为T的字段和方法罢了，<strong>其本质是一种组合</strong>，是组合中的代理（delegate）模式的一种实现。T只是一个代理（delegate），对外它提供了它可以代理的所有方法，如例子中的M1~M4方法。当外界发起对T的M1方法的调用后，T将该调用委派给它内部的P实例来实际执行M1方法。</p>
<p>以经典OO理论话术去理解就是<strong>T与P、Q的关系不是is-a，而是has-a的关系</strong>。</p>
<h3>3. 多态</h3>
<p>经典OO中的多态是尤指<strong>运行时多态</strong>，指的是调用方法时，会根据调用方法的实际对象的类型来调用不同类型的方法实现。</p>
<p>下面是一个C++中典型多态的例子：</p>
<pre><code>#include &lt;iostream&gt;

class P {
        public:
                virtual void M() = 0;
};

class C1: public P {
        public:
                void M();
};

void C1::M() {
        std::cout &lt;&lt; "c1.M()\n";
}

class C2: public P {
        public:
                void M();
};

void C2::M() {
        std::cout &lt;&lt; "c2.M()\n";
}

int main() {
        C1 c1;
        C2 c2;
        P *p = &amp;c1;
        p-&gt;M(); // c1.M()
        p = &amp;c2;
        p-&gt;M(); // c2.M()
}
</code></pre>
<p>这段代码比较清晰，一个父类P和两个子类C1和C2。父类P有一个虚拟成员函数M，两个子类C1和C2分别重写了M成员函数。在main中，我们声明父类P的指针，然后将C1和C2的对象实例分别赋值给p并调用M成员函数，从结果来看，在运行时p实际调用的函数会根据其指向的对象实例的实际类型而分别调用C1和C2的M。</p>
<p>显然，经典OO的多态实现依托的是类型的层次关系。那么对应没有了类型层次体系的Go来说，它又是如何实现多态的呢？<strong>Go使用接口来解锁多态</strong>！</p>
<p>和经典OO语言相比，Go更强调行为聚合与一致性，而非数据。因此Go提供了对类似duck typing的支持，即基于行为集合的类型适配，但相较于ruby等动态语言，Go的静态类型机制还可以保证应用duck typing时的类型安全。</p>
<p>Go的接口类型本质就是一组方法集合(行为集合)，一个类型如果实现了某个接口类型中的所有方法，那么就可以作为动态类型赋值给接口类型。通过该接口类型变量的调用某一方法，实际调用的就是其动态类型的方法实现。看下面例子：</p>
<pre><code>type MyInterface interface {
    M1()
    M2()
    M3()
}

type P struct {
}

func (P) M1() {}
func (P) M2() {}
func (P) M3() {}

type Q int
func (Q) M1() {}
func (Q) M2() {}
func (Q) M3() {}

func main() {
    var p P
    var q Q
    var i MyInterface = p
    i.M1() // P.M1
    i.M2() // P.M2
    i.M3() // P.M3

    i = q
    i.M1() // Q.M1
    i.M2() // Q.M2
    i.M3() // Q.M3
}
</code></pre>
<p>Go这种无需类型继承层次体系、低耦合方式的多态实现，是不是用起来更轻量、更容易些呢！</p>
<h2>五. Gopher的“OO思维”</h2>
<p>到这里，来自经典OO语言阵营的小伙伴们是不是已经找到了当初在入门Go语言时“感觉到别扭”的原因了呢！这种“别扭”就在于<strong>Go对于OO支持的方式与经典OO语言的差别</strong>：秉持着经典OO思维的小伙伴一上来就要建立的继承层次体系，但Go没有，也不需要。</p>
<p>要转变为正宗的Gopher的OO思维其实也不难，那就是“<strong>prefer接口，prefer组合，将习惯了的is-a思维改为has-a思维</strong>”。</p>
<h2>六. 小结</h2>
<p>是时候给出一些结论性的观点了：</p>
<ul>
<li>Go支持OO，只是用的不是经典OO的语法和带层次的类型体系；</li>
<li>Go支持OO，只是用起来需要换种思维；</li>
<li>在Go中玩转OO的思维方式是：“优先接口、优先组合”。</li>
</ul>
<hr />
<p><a href="https://wx.zsxq.com/dweb2/index/group/51284458844544">“Gopher部落”知识星球</a>旨在打造一个精品Go学习和进阶社群！高品质首发Go技术文章，“三天”首发阅读权，每年两期Go语言发展现状分析，每天提前1小时阅读到新鲜的Gopher日报，网课、技术专栏、图书内容前瞻，六小时内必答保证等满足你关于Go语言生态的所有需求！2023年，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>著名云主机服务厂商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>微博2：https://weibo.com/u/6484441286</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; 2023, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2023/03/12/is-go-object-oriented/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>使用反射操作channel</title>
		<link>https://tonybai.com/2022/11/15/using-reflect-to-manipulate-channels/</link>
		<comments>https://tonybai.com/2022/11/15/using-reflect-to-manipulate-channels/#comments</comments>
		<pubDate>Tue, 15 Nov 2022 13:53:32 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[benchmark]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[Const]]></category>
		<category><![CDATA[CSP]]></category>
		<category><![CDATA[default]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[Make]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[receive]]></category>
		<category><![CDATA[Reflect]]></category>
		<category><![CDATA[select]]></category>
		<category><![CDATA[select-case]]></category>
		<category><![CDATA[send]]></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=3721</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2022/11/15/using-reflect-to-manipulate-channels 今年教师节极客时间送给讲师4999 SVIP卡，一直没顾过来用，上周激活后在极客时间的众多精品课和专栏中徜徉，收获颇丰。尤其是在拜读鸟窝老师的《Go并发编程实战课》 后，get到一个以前从未用过的“技能点”：使用reflect操作channel，这里整理一下，把它分享给大家。 1. channel常规语法的“限制” Go语言实现了基于CSP（Communicating Sequential Processes）理论的并发方案。方案包含两个重要元素，一个是Goroutine，它是Go应用并发设计的基本构建与执行单元；另一个就是channel，它在并发模型中扮演着重要的角色。channel既可以用来实现Goroutine间的通信，还可以实现Goroutine间的同步。 我们先来简要回顾一下有关channel的常规语法。 我们可以通过make(chan T, n)创建元素类型为T、容量为n的channel类型实例，比如： ch1 := make(chan int) // 创建一个无缓冲的channel实例ch1 ch2 := make(chan int, 5) // 创建一个带缓冲的channel实例ch2 Go提供了“&#60;-”操作符用于对channel类型变量进行发送与接收操作，下面是一些对上述channel ch1和ch2进行收发操作的代码示例： ch1 &#60;- 13 // 将整型字面值13发送到无缓冲channel类型变量ch1中 n := &#60;- ch1 // 从无缓冲channel类型变量ch1中接收一个整型值存储到整型变量n中 ch2 &#60;- 17 // 将整型字面值17发送到带缓冲channel类型变量ch2中 m := &#60;- ch2 // 从带缓冲channel类型变量ch2中接收一个整型值存储到整型变量m中 Go不仅提供了单独操作channel的语法，还提供了可以同时对多个channel进行操作的select-case语法，比如下面代码： select { [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/using-reflect-to-manipulate-channels-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2022/11/15/using-reflect-to-manipulate-channels">本文永久链接</a> &#8211; https://tonybai.com/2022/11/15/using-reflect-to-manipulate-channels</p>
<hr />
<p>今年教师节极客时间送给讲师4999 SVIP卡，一直没顾过来用，上周激活后在极客时间的众多精品课和专栏中徜徉，收获颇丰。尤其是在拜读鸟窝老师的<a href="http://gk.link/a/11OCq">《Go并发编程实战课》</a> 后，get到一个以前从未用过的“技能点”：<strong>使用reflect操作channel</strong>，这里整理一下，把它分享给大家。</p>
<h3>1. channel常规语法的“限制”</h3>
<p>Go语言实现了基于CSP（Communicating Sequential Processes）理论的并发方案。方案包含两个重要元素，一个是Goroutine，它是Go应用并发设计的基本构建与执行单元；另一个就是channel，它在并发模型中扮演着重要的角色。channel既可以用来实现Goroutine间的通信，还可以实现Goroutine间的同步。</p>
<p>我们先来简要回顾一下有关channel的常规语法。</p>
<p>我们可以通过make(chan T, n)创建元素类型为T、容量为n的channel类型实例，比如：</p>
<pre><code>ch1 := make(chan int)    // 创建一个无缓冲的channel实例ch1
ch2 := make(chan int, 5)  // 创建一个带缓冲的channel实例ch2
</code></pre>
<p>Go提供了“&lt;-”操作符用于对channel类型变量进行发送与接收操作，下面是一些对上述channel ch1和ch2进行收发操作的代码示例：</p>
<pre><code>ch1 &lt;- 13    // 将整型字面值13发送到无缓冲channel类型变量ch1中
n := &lt;- ch1  // 从无缓冲channel类型变量ch1中接收一个整型值存储到整型变量n中
ch2 &lt;- 17    // 将整型字面值17发送到带缓冲channel类型变量ch2中
m := &lt;- ch2  // 从带缓冲channel类型变量ch2中接收一个整型值存储到整型变量m中
</code></pre>
<p>Go不仅提供了单独操作channel的语法，还提供了可以同时对多个channel进行操作的select-case语法，比如下面代码：</p>
<pre><code>select {
case x := &lt;-ch1:     // 从channel ch1接收数据
  ... ...

case y, ok := &lt;-ch2: // 从channel ch2接收数据，并根据ok值判断ch2是否已经关闭
  ... ...

case ch3 &lt;- z:       // 将z值发送到channel ch3中:
  ... ...

default:             // 当上面case中的channel通信均无法实施时，执行该默认分支
}
</code></pre>
<p>我们看到：<strong>select语法中的case数量必须是固定的</strong>，我们只能把事先要交给select“监听”的channel准备好，在select语句中平铺开才可以。这就是select语句常规语法的限制，即<strong>select语法不支持动态的case集合</strong>。如果我们要监听的channel个数是不确定的，且在运行时会动态变化，那么select语法将无法满足我们的要求。</p>
<p>那怎么突破这一限制呢？鸟窝老师告诉我们用<a href="https://tonybai.com/2021/04/19/variable-operation-using-reflection-in-go">reflect包</a>。</p>
<h3>2. reflect.Select和reflect.SelectCase</h3>
<p>很多朋友可能和我一样，因为没有使用过reflect包操作channel，就会以为reflect操作channel的能力是Go新版本才提供的，但实则不然。reflect包中用于操作channel的函数Select以及其切片参数的元素类型SelectCase早在Go 1.1版本就加入到Go语言中了，有下图为证：</p>
<p><img src="https://tonybai.com/wp-content/uploads/using-reflect-to-manipulate-channels-3.png" alt="" /></p>
<p>那么如何使用这一“古老”的机制呢？我们一起来看一些例子。</p>
<p>首先我们来看<strong>第一种情况</strong>，也是最好理解的一种情况，<strong>即从一个动态的channel集合进行receive operations的select</strong>，下面是示例代码：</p>
<pre><code>// github.com/bigwhite/experiments/tree/master/reflect-operate-channel/select-recv/main.go
package main

import (
    "fmt"
    "math/rand"
    "reflect"
    "sync"
    "time"
)

func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    var rchs []chan int
    for i := 0; i &lt; 10; i++ {
        rchs = append(rchs, make(chan int))
    }

    // 创建SelectCase
    var cases = createRecvCases(rchs)

    // 消费者goroutine
    go func() {
        defer wg.Done()
        for {
            chosen, recv, ok := reflect.Select(cases)
            if ok {
                fmt.Printf("recv from channel [%d], val=%v\n", chosen, recv)
                continue
            }
            // one of the channels is closed, exit the goroutine
            fmt.Printf("channel [%d] closed, select goroutine exit\n", chosen)
            return
        }
    }()

    // 生产者goroutine
    go func() {
        defer wg.Done()
        var n int
        s := rand.NewSource(time.Now().Unix())
        r := rand.New(s)
        for i := 0; i &lt; 10; i++ {
            n = r.Intn(10)
            rchs[n] &lt;- n
        }
        close(rchs[n])
    }()

    wg.Wait()
}

func createRecvCases(rchs []chan int) []reflect.SelectCase {
    var cases []reflect.SelectCase

    // 创建recv case
    for _, ch := range rchs {
        cases = append(cases, reflect.SelectCase{
            Dir:  reflect.SelectRecv,
            Chan: reflect.ValueOf(ch),
        })
    }
    return cases
}
</code></pre>
<p>在这个例子中，我们通过createRecvCases这个函数创建一个元素类型为reflect.SelectCase的切片，之后使用reflect.Select可以监听这个切片集合，就像常规select语法那样，从有数据的recv Channel集合中随机选出一个返回。</p>
<p>reflect.SelectCase有三个字段：</p>
<pre><code>// $GOROOT/src/reflect/value.go
type SelectCase struct {
    Dir  SelectDir // direction of case
    Chan Value     // channel to use (for send or receive)
    Send Value     // value to send (for send)
}
</code></pre>
<p>其中Dir字段的值是一个“枚举”，枚举值如下：</p>
<pre><code>// $GOROOT/src/reflect/value.go
const (
    _             SelectDir = iota
    SelectSend              // case Chan &lt;- Send
    SelectRecv              // case &lt;-Chan:
    SelectDefault           // default
)
</code></pre>
<p>从常量名我们也可以看出，Dir用于标识case的类型，SelectRecv表示这是一个从channel做receive操作的case，SelectSend表示这是一个向channel做send操作的case；SelectDefault则表示这是一个default case。</p>
<p>构建好SelectCase的切片后，我们就可以将其传给reflect.Select了。Select函数的语义与select关键字语义是一致的，它会监听传入的所有SelectCase，以上面示例为例，如果所有channel都没有数据，那么reflect.Select会阻塞，直到某个channel有数据或关闭。</p>
<p>Select函数有三个返回值：</p>
<pre><code>// $GOROOT/src/reflect/value.go
func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool)
</code></pre>
<p>对于上面示例而言，如果监听的某个case有数据了，那么Select的返回值chosen中存储了该channel在cases切片中的下标，recv中存储了从channel收到的值，recvOK等价于comma, ok模式的ok，当正常接收到由send channel操作发送的值时，recvOK为true，如果channel被close了，recvOK为false。</p>
<p>上面的示例启动了两个goroutine，一个goroutine充当消费者，由reflect.Select监听一组channel，当某个channel关闭时，该goroutine退出；另外一个goroutine则是随机的向这些channel中发送数据，发送10次后，关闭其中某个channel通知消费者退出。</p>
<p>我们运行一下该示例程序，得到如下结果：</p>
<pre><code>$go run main.go
recv from channel [1], val=1
recv from channel [4], val=4
recv from channel [5], val=5
recv from channel [8], val=8
recv from channel [1], val=1
recv from channel [1], val=1
recv from channel [8], val=8
recv from channel [3], val=3
recv from channel [5], val=5
recv from channel [9], val=9
channel [9] closed, select goroutine exit
</code></pre>
<p>我们日常编码时经常会在select语句中加上default分支，以防止select完全阻塞，下面我们就来改造一下示例，让其增加对default分支的支持：</p>
<pre><code>// github.com/bigwhite/experiments/tree/master/reflect-operate-channel/select-recv-with-default/main.go

package main

import (
    "fmt"
    "math/rand"
    "reflect"
    "sync"
    "time"
)

func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    var rchs []chan int
    for i := 0; i &lt; 10; i++ {
        rchs = append(rchs, make(chan int))
    }

    // 创建SelectCase
    var cases = createRecvCases(rchs, true)

    // 消费者goroutine
    go func() {
        defer wg.Done()
        for {
            chosen, recv, ok := reflect.Select(cases)
            if cases[chosen].Dir == reflect.SelectDefault {
                fmt.Println("choose the default")
                continue
            }
            if ok {
                fmt.Printf("recv from channel [%d], val=%v\n", chosen, recv)
                continue
            }
            // one of the channels is closed, exit the goroutine
            fmt.Printf("channel [%d] closed, select goroutine exit\n", chosen)
            return
        }
    }()

    // 生产者goroutine
    go func() {
        defer wg.Done()
        var n int
        s := rand.NewSource(time.Now().Unix())
        r := rand.New(s)
        for i := 0; i &lt; 10; i++ {
            n = r.Intn(10)
            rchs[n] &lt;- n
        }
        close(rchs[n])
    }()

    wg.Wait()
}

func createRecvCases(rchs []chan int, withDefault bool) []reflect.SelectCase {
    var cases []reflect.SelectCase

    // 创建recv case
    for _, ch := range rchs {
        cases = append(cases, reflect.SelectCase{
            Dir:  reflect.SelectRecv,
            Chan: reflect.ValueOf(ch),
        })
    }

    if withDefault {
        cases = append(cases, reflect.SelectCase{
            Dir:  reflect.SelectDefault,
            Chan: reflect.Value{},
            Send: reflect.Value{},
        })
    }

    return cases
}
</code></pre>
<p>在这个示例中，我们的createRecvCases函数增加了一个withDefault布尔型参数，当withDefault为true时，返回的cases切片中将包含一个default case。我们看到，创建defaultCase时，Chan和Send两个字段需要传入空的reflect.Value。</p>
<p>在消费者goroutine中，我们通过选出的case的Dir字段是否为reflect.SelectDefault来判定是否default case被选出，其余的处理逻辑不变，我们运行一下这个示例：</p>
<pre><code>$go run main.go
recv from channel [8], val=8
recv from channel [8], val=8
choose the default
choose the default
choose the default
choose the default
choose the default
recv from channel [1], val=1
choose the default
choose the default
choose the default
recv from channel [3], val=3
recv from channel [6], val=6
choose the default
choose the default
recv from channel [0], val=0
choose the default
choose the default
choose the default
recv from channel [5], val=5
recv from channel [2], val=2
choose the default
choose the default
choose the default
recv from channel [2], val=2
choose the default
choose the default
recv from channel [2], val=2
choose the default
choose the default
channel [2] closed, select goroutine exit
</code></pre>
<p>我们看到，default case被选择的几率还是蛮大的。</p>
<p>最后，我们再来看看如何使用reflect包向channel中发送数据，看下面示例代码：</p>
<pre><code>// github.com/bigwhite/experiments/tree/master/reflect-operate-channel/select-send/main.go

package main

import (
    "fmt"
    "reflect"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    ch0, ch1, ch2 := make(chan int), make(chan int), make(chan int)
    var schs = []chan int{ch0, ch1, ch2}

    // 创建SelectCase
    var cases = createCases(schs)

    // 生产者goroutine
    go func() {
        defer wg.Done()
        for range cases {
            chosen, _, _ := reflect.Select(cases)
            fmt.Printf("send to channel [%d], val=%v\n", chosen, cases[chosen].Send)
            cases[chosen].Chan = reflect.Value{}
        }
        fmt.Println("select goroutine exit")
        return
    }()

    // 消费者goroutine
    go func() {
        defer wg.Done()
        for range schs {
            var v int
            select {
            case v = &lt;-ch0:
                fmt.Printf("recv %d from ch0\n", v)
            case v = &lt;-ch1:
                fmt.Printf("recv %d from ch1\n", v)
            case v = &lt;-ch2:
                fmt.Printf("recv %d from ch2\n", v)
            }
        }
    }()

    wg.Wait()
}

func createCases(schs []chan int) []reflect.SelectCase {
    var cases []reflect.SelectCase

    // 创建send case
    for i, ch := range schs {
        n := i + 100
        cases = append(cases, reflect.SelectCase{
            Dir:  reflect.SelectSend,
            Chan: reflect.ValueOf(ch),
            Send: reflect.ValueOf(n),
        })
    }

    return cases
}
</code></pre>
<p>在这个示例中，我们针对三个channel：ch0，ch1和ch2创建了写操作的SelectCase，每个SelectCase的Send字段都被赋予了要发送给该channel的值，这里使用了“100+下标号”。</p>
<p>生产者goroutine中有一个“与众不同”的地方，那就是每次某个写操作触发后，我都将该SelectCase中的Chan重置为一个空Value，以防止下次该channel被重新选出：</p>
<pre><code>    cases[chosen].Chan = reflect.Value{}
</code></pre>
<p>运行一下该示例，我们得到：</p>
<pre><code>$go run main.go
recv 101 from ch1
send to channel [1], val=101
send to channel [0], val=100
recv 100 from ch0
recv 102 from ch2
send to channel [2], val=102
select goroutine exit
</code></pre>
<p>通过上面的几个例子我们看到，reflect.Select有着与select等价的语义，且还支持动态增删和修改case，功能不可为不强大，现在还剩一点要care，那就是它的执行性能如何呢？我们接着往下看。</p>
<h3>3. reflect.Select的性能</h3>
<p>我们用benchmark test来对比一下常规select与reflect.Select在执行性能上的差别，下面是benchmark代码：</p>
<pre><code>// github.com/bigwhite/experiments/tree/master/reflect-operate-channel/select-benchmark/benchmark_test.go
package main

import (
    "reflect"
    "testing"
)

func createCases(rchs []chan int) []reflect.SelectCase {
    var cases []reflect.SelectCase

    // 创建recv case
    for _, ch := range rchs {
        cases = append(cases, reflect.SelectCase{
            Dir:  reflect.SelectRecv,
            Chan: reflect.ValueOf(ch),
        })
    }
    return cases
}

func BenchmarkSelect(b *testing.B) {
    var c1 = make(chan int)
    var c2 = make(chan int)
    var c3 = make(chan int)

    go func() {
        for {
            c1 &lt;- 1
        }
    }()
    go func() {
        for {
            c2 &lt;- 2
        }
    }()
    go func() {
        for {
            c3 &lt;- 3
        }
    }()

    b.ReportAllocs()
    b.ResetTimer()
    for i := 0; i &lt; b.N; i++ {
        select {
        case &lt;-c1:
        case &lt;-c2:
        case &lt;-c3:
        }
    }
}

func BenchmarkReflectSelect(b *testing.B) {
    var c1 = make(chan int)
    var c2 = make(chan int)
    var c3 = make(chan int)

    go func() {
        for {
            c1 &lt;- 1
        }
    }()
    go func() {
        for {
            c2 &lt;- 2
        }
    }()
    go func() {
        for {
            c3 &lt;- 3
        }
    }()

    chs := createCases([]chan int{c1, c2, c3})

    b.ReportAllocs()
    b.ResetTimer()

    for i := 0; i &lt; b.N; i++ {
        _, _, _ = reflect.Select(chs)
    }
}
</code></pre>
<p>运行一下该benchmark：</p>
<pre><code>$go test -bench .
goos: darwin
goarch: amd64
pkg: github.com/bigwhite/experiments/reflect-operate-channel/select-benchmark
... ...
BenchmarkSelect-8            2765396           427.8 ns/op         0 B/op          0 allocs/op
BenchmarkReflectSelect-8     1839706           806.0 ns/op       112 B/op          6 allocs/op
PASS
ok      github.com/bigwhite/experiments/reflect-operate-channel/select-benchmark    3.779s
</code></pre>
<p>我们看到：reflect.Select的执行效率相对于select还是要差的，并且在其执行过程中还要做额外的内存分配。</p>
<h3>4. 小结</h3>
<p>本文介绍了reflect.Select与SelectCase的结构以及如何使用它们在不同场景下操作channel。但大多数情况下，我们是不需要使用reflect.Select，常规select语法足以满足我们的要求。并且reflect.Select有对cases数量的约束，最大支持65536个cases，虽然这个约束对于大多数场合而言足够用了。</p>
<p>本文涉及的示例源码可以在<a href="https://github.com/bigwhite/experiments/tree/master/reflect-operate-channel">这里</a>下载。</p>
<hr />
<p><img src="https://tonybai.com/wp-content/uploads/using-reflect-to-manipulate-channels-2.png" alt="" /></p>
<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>微博2：https://weibo.com/u/6484441286</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/11/15/using-reflect-to-manipulate-channels/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>GoCN社区Go读书会第二期：《Go语言精进之路》</title>
		<link>https://tonybai.com/2022/07/07/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master/</link>
		<comments>https://tonybai.com/2022/07/07/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master/#comments</comments>
		<pubDate>Thu, 07 Jul 2022 11:40:39 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Amazon]]></category>
		<category><![CDATA[atomic]]></category>
		<category><![CDATA[benchmark]]></category>
		<category><![CDATA[B站]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Cgo]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[crypto]]></category>
		<category><![CDATA[CSP]]></category>
		<category><![CDATA[defer]]></category>
		<category><![CDATA[delve]]></category>
		<category><![CDATA[error]]></category>
		<category><![CDATA[for]]></category>
		<category><![CDATA[Function]]></category>
		<category><![CDATA[functrace]]></category>
		<category><![CDATA[fuzzing]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go-coding-in-go-way]]></category>
		<category><![CDATA[go-module]]></category>
		<category><![CDATA[go-test]]></category>
		<category><![CDATA[go.mod]]></category>
		<category><![CDATA[Go1]]></category>
		<category><![CDATA[go1.18]]></category>
		<category><![CDATA[GoCN]]></category>
		<category><![CDATA[GODEBUG]]></category>
		<category><![CDATA[GODEUG]]></category>
		<category><![CDATA[gofmt]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Gopher]]></category>
		<category><![CDATA[gopherchina]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[Go语言学习笔记]]></category>
		<category><![CDATA[Go语言精进之路]]></category>
		<category><![CDATA[Haskell]]></category>
		<category><![CDATA[http]]></category>
		<category><![CDATA[https]]></category>
		<category><![CDATA[if]]></category>
		<category><![CDATA[init]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[iota]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[leanpub]]></category>
		<category><![CDATA[Method]]></category>
		<category><![CDATA[metrics]]></category>
		<category><![CDATA[Module]]></category>
		<category><![CDATA[Package]]></category>
		<category><![CDATA[panic]]></category>
		<category><![CDATA[pitfalls]]></category>
		<category><![CDATA[pprof]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[receiver]]></category>
		<category><![CDATA[Reflect]]></category>
		<category><![CDATA[runtime]]></category>
		<category><![CDATA[RussCox]]></category>
		<category><![CDATA[select]]></category>
		<category><![CDATA[strings]]></category>
		<category><![CDATA[sync]]></category>
		<category><![CDATA[time]]></category>
		<category><![CDATA[TLS]]></category>
		<category><![CDATA[trap]]></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>
		<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>
		<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>
		<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=3610</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2022/07/07/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master 本文是2022年6月26日我在GoCN社区的Go读书会第二期《Go语言精进之路》直播的文字稿。本文对直播的内容做了重新整理与修订，供喜欢阅读文字的朋友们在收看直播后的揣摩和参考。视频控的童鞋可以关注GoCN公众号和视频号看剪辑后的视频，也可以在B站GopherChina专区下收看视频回放(https://www.bilibili.com/video/BV1p94y1R7jg)。 大家晚上好，我叫白明，是《Go语言精进之路》一书的作者，也是tonybai.com的博主，很荣幸今天参加GoCN社区Go读书会第二期，分享一下我个人在写书和读书方面的经验和体会。 今天的分享包括三方面内容： 写书的历程。一些Gopher可能比较好奇，这么厚的一套书是怎么写出来的，今天就和大家聊一聊。 《Go语言精进之路》导读。主要是把这本书的整体构思与大家聊聊，希望通过这个导读帮助读者更好地阅读和理解这套书。 我个人的读书方法与经验的简要分享。 首先和大家分享一下写书的历程。 一. 写书的历程 1. 程序员的“小目标”与写书三要素 今天收看直播的童鞋都是有追求的技术人员，可能心底都有写一本属于自己的书的小目标。这样可以把自己学习到的知识、技能和经验以比较系统的方式输出给其他人，可以帮助其他人快速学习和掌握本领域的知识、技能和经验。 当然写书还有其他好处，比如：提升名气、更容易混技术圈子、可能给你带来更好的职业发展机会，当然也会给你带来一些额外的副业收入，至于多少，还要看书籍的口碑与销量。 那怎么才能写书呢？作为“过来人”，我总结了三个要素，也是三个条件。 第一个要素是能力。 这个很容易理解。以Go为例，如果你没有在Go语言方面的知识、技能的沉淀，没有对Go语言方方面面的较为深入的理解，你很难写出一本口碑很好的书籍。尤其是那种有原创性、独到见解的著书。而不是对前人资料做系统整理摘抄的编书。编书更常见于教材、字典等。显然著书对作者水平的要求更高。 第二个要素是意愿。 写过书的同学都有体会，写书是一件辛苦活。需要你在正式工作之余付出大量业余时间伏案创作。并且对于小众技术类书籍来说，写书能带来的金钱上的收益和你付出的时长和精力不成正比。就这个问题，我曾与机械工业出版社的营销编辑老师聊过，得到的信息是：Go技术书籍的市场与Java、Python还没法比，即便是像Go语言圣经《Go程序设计语言》的销量也没法与Java、Python的头部书籍销量相比。 第三个要素是机会。 记得小时候十分羡慕那些能出书的人，觉得都是大神级的人物。不过那个时候出书的确很难，机会应该很少，你要不是在学术圈里混很难出书。如今就容易地多了，渠道也多了。每年出版社都有自己的出版计划，各个出版社的编辑老师也在根据计划在各种自媒体上、技术圈子中寻觅匹配的技术作者。 如果你有自己的思路，也可以整理出大纲，并通过某种方式联系到出版社老师，如果匹配就可以出。 另外国外流行电子自助出版，这也给很多技术作者很好的出版机会。比如国内作者老貘写的Go 101系列就是在亚马逊和leanpub上做的自助出版，效果还不错。 以上就是我总结的出书的三个要素，一旦集齐这三个要素呢，出书实际就是自然而然的一件事了。以我为例。 从能力方面来说呢，我大约从2011年开始接触和学习Go语言，算是国内较早的一批Go语言接纳者。Go语言2012年才发布1.0版本，因此那时我接触的Go时还是r60版本，还不是正式的1.0版本。从那时起就一直在跟踪Go演化，日常写一些Go项目的小程序。 Go 1.5实现自举并大幅降低GC延迟，我于是开始在一些生产环境使用Go，并逐渐将知识和经验做了沉淀，在自己的博客上不断做着Go相关内容的输出，反响也不错。 随着输出Go内容的增多，我发现以博客的形式输出，内容组织零散，于是我第一次有了将自己的Go知识系统整理并输出的意愿和想法。 我在实践Go的过程中收到很多Go初学者的提问：Go入门容易，但精进难，怎么才能像Go开发团队那样写出符合Go思维和语言惯例的高质量代码呢？这个问题引发了我的思考。在2017年GopherChina大会我以《go coding in go way》为主题，以演讲的形式尝试回答这个问题，但鉴于演讲的时长有限，很多内容没能展开，效果不甚理想。这进一步增强了我通过书籍的形式系统解答这个问题的意愿。 而当时我家大宝已经长大了，我也希望通过写书这个行动身体力行地给孩子树立一个正面的榜样。中国古语有云：言传身教，我也想践行一下。 机会就这样自然而然的来了！2018年初，机械工业出版社副总编杨福川老师在微信联系到我，和我探讨一下是否可以写一本类似于“Effective Go”的书，当时机械工业出版社华章出版社策划了Effective XXX(编写高质量XXX)系列图书，当时已经出版了C、Python等语言版本的书籍，还差Go语言的。我的出书意愿与出版社的需求甚是匹配，于是我答应的杨老师的要求，成为了这套丛书的Go版本的作者。 2. 写书的过程 我是2018下旬开始真正动笔的。 真正开始码字的时候，我才意识到，写书真不容易，要写出高质量书稿，的确需付出大量时间和汗水。每天晚上、早上都在构思、码字、写代码示例、画插图，睡眠时间很少。记得当时每周末都在奋笔疾书，陪伴家人尤其是孩子的时间很少。 另外我这个人还习惯于把一个知识点讲细讲透，这样每一节的篇幅都不小。因此，写作进展是很缓慢的，就这样，进度一再延期。好在编辑老师比较nice，考虑到书稿质量，没有狠狠催进度。 2020年11月末，我正式向出版社交了初稿，记得初稿有66条，近40w字。 又经过一年的排期、编辑、修订、排版，2021年12月下旬正式出版。 2022年1月《Go语言精进之路》正式上架到各个渠道货架。 到今天为止，出版了近六个月，这本书收获了还不错的口碑，在各个平台上的口碑都在8分以上(注：口碑分数还在动态变化，下图仅为当时的快照，不代表如今的分数)。 能获得大家的认可，让我很是欣慰，觉得写书过程付出的辛苦没有白费。 以上就是我的写书历程。总的来说一句话：写书不易，写高质量的书更难。 接下来我来进行一下《Go语言精进之路》一书的导读。 二. 《Go语言精进之路》导读 也许是“用力过猛”，《Go语言精进之路》一书写的太厚了，无法装订为一册。编辑老师建议装订为两册，即1、2册。很多同学好奇为什么不是上下册而是1、2册，这里是编辑老师的“高瞻远瞩”，目的是为后续可能的“续写”(比如第3册)留足空间，毕竟Go语言还在快速演进，目前的版本还不包含像泛型这样的新语法。不过，目前第3册还尚未列入计划。 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2022/07/07/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master">本文永久链接</a> &#8211; https://tonybai.com/2022/07/07/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master</p>
<p>本文是2022年6月26日我在<strong>GoCN社区的Go读书会第二期《Go语言精进之路》直播的文字稿</strong>。本文对直播的内容做了重新整理与修订，供喜欢阅读文字的朋友们在收看直播后的揣摩和参考。视频控的童鞋可以关注<strong>GoCN公众号和视频号</strong>看剪辑后的视频，也可以<a href="https://www.bilibili.com/video/BV1p94y1R7jg">在B站GopherChina专区下收看视频回放</a>(https://www.bilibili.com/video/BV1p94y1R7jg)。</p>
<hr />
<p>大家晚上好，我叫白明，是<a href="https://item.jd.com/13694000.html">《Go语言精进之路》</a>一书的作者，也是<a href="https://tonybai.com">tonybai.com</a>的博主，很荣幸今天参加GoCN社区Go读书会第二期，分享一下我个人在写书和读书方面的经验和体会。</p>
<p>今天的分享包括三方面内容：</p>
<ul>
<li>写书的历程。一些Gopher可能比较好奇，这么厚的一套书是怎么写出来的，今天就和大家聊一聊。</li>
<li>《Go语言精进之路》导读。主要是把这本书的整体构思与大家聊聊，希望通过这个导读帮助读者更好地阅读和理解这套书。</li>
<li>我个人的读书方法与经验的简要分享。</li>
</ul>
<p>首先和大家分享一下写书的历程。</p>
<h2>一. 写书的历程</h2>
<p><img src="https://tonybai.com/wp-content/uploads/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master-2.png" alt="" /></p>
<h3>1. 程序员的“小目标”与写书三要素</h3>
<p>今天收看直播的童鞋都是有追求的技术人员，可能心底都有写一本属于自己的书的小目标。这样可以把自己学习到的知识、技能和经验以比较系统的方式输出给其他人，可以帮助其他人快速学习和掌握本领域的知识、技能和经验。</p>
<p>当然写书还有其他好处，比如：提升名气、更容易混技术圈子、可能给你带来更好的职业发展机会，当然也会给你带来一些额外的副业收入，至于多少，还要看书籍的口碑与销量。</p>
<p>那怎么才能写书呢？作为“过来人”，我总结了三个要素，也是三个条件。</p>
<p>第一个要素是<strong>能力</strong>。</p>
<p>这个很容易理解。以Go为例，如果你没有在Go语言方面的知识、技能的沉淀，没有对Go语言方方面面的较为深入的理解，你很难写出一本口碑很好的书籍。尤其是那种有原创性、独到见解的著书。而不是对前人资料做系统整理摘抄的编书。编书更常见于教材、字典等。显然著书对作者水平的要求更高。</p>
<p>第二个要素是<strong>意愿</strong>。</p>
<p>写过书的同学都有体会，写书是一件辛苦活。需要你在正式工作之余付出大量业余时间伏案创作。并且对于小众技术类书籍来说，写书能带来的金钱上的收益和你付出的时长和精力不成正比。就这个问题，我曾与机械工业出版社的营销编辑老师聊过，得到的信息是：Go技术书籍的市场与Java、Python还没法比，即便是像Go语言圣经《Go程序设计语言》的销量也没法与Java、Python的头部书籍销量相比。</p>
<p>第三个要素是<strong>机会</strong>。</p>
<p>记得小时候十分羡慕那些能出书的人，觉得都是大神级的人物。不过那个时候出书的确很难，机会应该很少，你要不是在学术圈里混很难出书。如今就容易地多了，渠道也多了。每年出版社都有自己的出版计划，各个出版社的编辑老师也在根据计划在各种自媒体上、技术圈子中寻觅匹配的技术作者。</p>
<p>如果你有自己的思路，也可以整理出大纲，并通过某种方式联系到出版社老师，如果匹配就可以出。</p>
<p>另外国外流行电子自助出版，这也给很多技术作者很好的出版机会。比如国内作者老貘写的<a href="https://go101.org/">Go 101系列</a>就是在<a href="https://kdp.amazon.com/en_US/">亚马逊</a>和<a href="https://leanpub.com">leanpub</a>上做的自助出版，效果还不错。</p>
<p>以上就是我总结的出书的三个要素，一旦集齐这三个要素呢，出书实际就是自然而然的一件事了。以我为例。</p>
<p>从能力方面来说呢，我大约从2011年开始接触和学习Go语言，算是国内较早的一批Go语言接纳者。Go语言2012年才发布1.0版本，因此那时我接触的Go时还是r60版本，还不是正式的1.0版本。从那时起就一直在跟踪Go演化，日常写一些Go项目的小程序。</p>
<p>Go 1.5实现自举并大幅降低GC延迟，我于是开始在一些生产环境使用Go，并逐渐将知识和经验做了沉淀，在自己的博客上不断做着Go相关内容的输出，反响也不错。</p>
<p>随着输出Go内容的增多，我发现以博客的形式输出，内容组织零散，于是我第一次有了将自己的Go知识系统整理并输出的意愿和想法。</p>
<p>我在实践Go的过程中收到很多Go初学者的提问：Go入门容易，但精进难，怎么才能像Go开发团队那样写出符合Go思维和语言惯例的高质量代码呢？这个问题引发了我的思考。在2017年GopherChina大会我以<a href="https://tonybai.com/2017/04/20/go-coding-in-go-way/">《go coding in go way》</a>为主题，以演讲的形式尝试回答这个问题，但鉴于演讲的时长有限，很多内容没能展开，效果不甚理想。这进一步增强了我通过书籍的形式系统解答这个问题的意愿。</p>
<p>而当时我家<a href="https://daughter.tonybai.com">大宝</a>已经长大了，我也希望通过写书这个行动身体力行地<strong>给孩子树立一个正面的榜样</strong>。中国古语有云：<strong>言传身教</strong>，我也想践行一下。</p>
<p>机会就这样自然而然的来了！2018年初，机械工业出版社副总编杨福川老师在微信联系到我，和我探讨一下是否可以写一本类似于“Effective Go”的书，当时机械工业出版社华章出版社策划了Effective XXX(编写高质量XXX)系列图书，当时已经出版了C、Python等语言版本的书籍，还差Go语言的。我的出书意愿与出版社的需求甚是匹配，于是我答应的杨老师的要求，成为了这套丛书的Go版本的作者。</p>
<h3>2. 写书的过程</h3>
<p>我是2018下旬开始真正动笔的。</p>
<p>真正开始码字的时候，我才意识到，写书真不容易，要写出高质量书稿，的确需付出大量时间和汗水。每天晚上、早上都在构思、码字、写代码示例、画插图，睡眠时间很少。记得当时每周末都在奋笔疾书，陪伴家人尤其是孩子的时间很少。</p>
<p>另外我这个人还习惯于把一个知识点讲细讲透，这样每一节的篇幅都不小。因此，写作进展是很缓慢的，就这样，进度一再延期。好在编辑老师比较nice，考虑到书稿质量，没有狠狠催进度。</p>
<p>2020年11月末，我正式向出版社交了初稿，记得初稿有66条，近40w字。</p>
<p>又经过一年的排期、编辑、修订、排版，2021年12月下旬正式出版。</p>
<p>2022年1月《Go语言精进之路》正式上架到各个渠道货架。</p>
<p>到今天为止，出版了近六个月，这本书收获了还不错的口碑，在各个平台上的口碑都在8分以上(注：口碑分数还在动态变化，下图仅为当时的快照，不代表如今的分数)。</p>
<p><img src="https://tonybai.com/wp-content/uploads/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master-5.jpg" alt="" /><br />
<img src="https://tonybai.com/wp-content/uploads/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master-6.png" alt="" /></p>
<p>能获得大家的认可，让我很是欣慰，觉得写书过程付出的辛苦没有白费。</p>
<p>以上就是我的写书历程。总的来说一句话：<strong>写书不易，写高质量的书更难</strong>。</p>
<p>接下来我来进行一下《Go语言精进之路》一书的导读。</p>
<h2>二. 《Go语言精进之路》导读</h2>
<p><img src="https://tonybai.com/wp-content/uploads/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master-3.png" alt="" /></p>
<p>也许是“用力过猛”，《Go语言精进之路》一书写的太厚了，无法装订为一册。编辑老师建议装订为两册，即1、2册。很多同学好奇为什么不是上下册而是1、2册，这里是编辑老师的“高瞻远瞩”，目的是为后续可能的“续写”(比如第3册)留足空间，毕竟Go语言还在快速演进，目前的版本还不包含像<a href="https://tonybai.com/2022/04/20/some-changes-in-go-1-18">泛型这样的新语法</a>。不过，目前第3册还尚未列入计划。</p>
<p>本套书共分为10个部分，66个主题。第一册包含了前7个部分，后3部分在第二册中。</p>
<h3>1. 整体写作思路</h3>
<p>整套书围绕着两个前后关联的思路循序展开。</p>
<p>第一个思路我叫它：<strong>精进之路，思维先行</strong>。</p>
<p>第二个思路称为：<strong>践行哲学，遵循惯例，认清本质，理解原理</strong>。</p>
<p>我们先来看看第一个思路。</p>
<h3>2. 精进之路，思维先行</h3>
<p>收看直播的童鞋都不止学过一门编程语言。大家可能都有过这样的经历：你已经精通A语言，然后在学习B语言的时候用A语言的思维去写B代码，你会觉得写出的B代码很别扭，写出的代码总是感觉不是很地道，总觉得不是那种高质量的B语言代码。</p>
<p>其实，不仅学习编程语言是这样，学自然语言也是一样。最典型的一个例子，大家都学过十几年的英语，但毕业后能用地道的英语表达自己观点的人却不多，为什么呢？那就是我们总用中文的思维方式去组织英语的句子，去说英语，这样再怎么努力也很难上一个层次。</p>
<p>其实，很多语言大师早就意识到了这一点。下面是我收集的这些大师的关于语言与思维的论点，这里和大家分享一下：</p>
<blockquote>
<p>“语言决定思维方式” &#8211; 萨丕尔假说</p>
<p>“我的语言之局限，即我的世界之局限” &#8211;  路德维希·维特根斯坦，语言哲学的奠基人</p>
<p>“不能改变你思维方式的语言，不值得学习” &#8211; Alan Perlis（首届ACM图灵奖得主)</p>
</blockquote>
<p>我们看到：无论是自然语言界的大师，还是IT界的大佬，他们的观点异曲同工。总之一句话：<strong>语言要精进，思维要先行</strong>。</p>
<h3>3. Part1：进入Go语言编程思维导引</h3>
<p>正是因为意识到语言与思维的紧密关系，我在书的第一部分就安排了Go语言编程思维导引，希望大家意识到Go编程思维在语言精进之路上的重要性。</p>
<p>一门编程语言的思维也不是与生俱来的，而是在演进中逐步形成的。所以在这一部分，我安排了Go诞生与演进、Go设计哲学：简单、组合、并发、面向工程。这样做的目的是让大家一起了解Go语言设计者在设计Go语言时的所思所想，让读者站在语言设计者的高度理解Go语言与众不同的设计，认同Go语言的设计理念。因为这些是<strong>Go编程语言思维形成的“土壤”</strong>。</p>
<p>这一部分最后一节是Go编程思维举例导引，书中给出了C, Haskell和Go程序员在面对同一个问题时，首先考虑到的思维方式以及不同思维下代码设计方式的差异。</p>
<p>知道Go编程思维的重要性后，我们应该怎么做呢？</p>
<h3>4. 怎么学习Go编程思维？</h3>
<p>学习的本质是一种模仿。<strong>要学习Go思维，就要去模仿Go团队、Go社区的优秀项目和代码，看看他们怎么做的</strong>。这套书后面的部分讲的就是这个。而“践行哲学，遵循惯例，认清本质，理解原理”就是对后面内容的写作思路的概要性总结。</p>
<ul>
<li>践行哲学</li>
</ul>
<p>把Go设计哲学用于自己的项目的设计实践中，而不是仅停留在口头知道上。</p>
<ul>
<li>遵循惯例</li>
</ul>
<p>遵循Go团队的一些语言惯例，比如“comma，ok”、使用复合字面值初始化等，使用这些惯例你可以让你的代码显得很地道，别人一看就懂。</p>
<ul>
<li>认清本质</li>
</ul>
<p>为了更高效地利用语言机制，我们要认清一些语言机制背后的本质，比如切片、字符串在运行时的表示，这样一来既能帮助开发人员正确使用这些语法元素，同时也能避免入坑。</p>
<ul>
<li>理解原理</li>
</ul>
<p>Go带有运行时。运行时全程参与Go应用生命周期，因此，只有对Goroutine调度、GC等原理做适当了解，才能更好的发挥Go的威力。</p>
<p>这套书的part2-part10 就是基于对Go团队、Go社区优秀实践与惯例的梳理，用系统化的思路构建出来并循序渐进呈现给大家的。</p>
<h3>5. Part2 – 项目基础：布局、代码风格与命名</h3>
<p>这部门的内容是每个gopher在开启一个Go项目时都要考虑的事情。</p>
<ul>
<li>项目布局</li>
</ul>
<p>我见过很多Gopher问项目布局的事情，因为Go官方没有给出标准布局。本书讲解了Go项目的结构布局的演进历程以及Go社区的事实标准，希望能给大家提供足够的参考信息。</p>
<ul>
<li>代码风格</li>
</ul>
<p>针对Go代码风格，由于代码风格在Go中已经弱化，所以这里主要还是带大家理解gofmt存在的意义和使用方法。</p>
<ul>
<li>命名惯例</li>
</ul>
<p>关于命名，我不知道大家是否觉得命名难，但对我来说是挺难的，我总是绞尽脑汁在想用啥名(手动允悲)。所以我的原则是“代码未动，命名先行”。 对于Go中变量、标识符等的命名惯例这样的“关键的问题”，我使用了“笨方法”：我统计了Go标准库、Docker库、k8s库的命名情况，并分门别类给出不同语法元素的命名惯例，具体内容大家可以看书了解 。</p>
<h3>6. Part3 – 语法基础：声明、类型、语句与控制结构</h3>
<p>第三部分讲的很基础，但内容还是要高于基础的。</p>
<ul>
<li>一致的变量声明</li>
</ul>
<p>我们知道Go提供多种变量声明方式，但是在不同位置该用哪种声明方式可读性好又不容易造坑呢(尤其要注意短变量声明)？书中给出了系统阐述。</p>
<ul>
<li>无类型常量与iota</li>
</ul>
<p>大家都用过常量，但很多人对于无类型常量与有类型常量区别不了解，书中帮你做了总结。还有，很多人用过iota，但却不理解iota的真正含义以及它能帮你做啥。书中对iota的语义做了说明，对常见用途做了梳理。</p>
<ul>
<li>零值可用</li>
</ul>
<p>Go提倡零值可用，也内置了有很多零值可用类型，用起来很爽，比如：切片(不全是，仅在append时是零值可用，当用下标访问时，不具备零值可用)、sync包中的Mutex、RDMutex等</p>
<p>其实类比于线程（thread），goroutine也是一种零值可用的“类型”，只是Go没有goroutine这个类型罢了。</p>
<p>如果我们是包的设计者，如果提供零值可用的类型，可以提升包的使用者的体验。</p>
<ul>
<li>复合字面值来初始化</li>
</ul>
<p>使用复合字面值对相应的变量进行初始化是一个Go语言的惯例， Go虽然提供了new和make，但日常很少用，尤其是new。</p>
<ul>
<li>切片、字符串、map的原理、惯用法与坑</li>
</ul>
<p>Go是带有runtime的语言，语法层面展示的很多语法元素和runtime层真实的表示并不一致。要想高效利用这些类型，如果不了解runtime层表示还真不行。有时候还有很严重的“坑”。懂了，自然就能绕过坑。</p>
<ul>
<li>包导入</li>
</ul>
<p>Go源文件的import语句后面跟着的是包名还是包路径？Go编译是不是必须要有依赖项的源码才可以，只有.a是否可以？这些问题书中都有系统说明</p>
<ul>
<li>代码块与作用域</li>
</ul>
<p>代码块与作用域是Go语言的基础概念，虽然基础，如果理解不好，也是有“坑”的，比如最常见的变量遮蔽等。一旦理解透了，还可以帮你解决意想不到的语法问题和执行语义错误问题。</p>
<ul>
<li>控制语句</li>
</ul>
<p>Go倡导“一个问题只有一种解决方法”。Go针对每种控制语句仅提供一种语法形式。虽然仅有一种形式，用不好，一样容器掉坑。本套书总结了Go控制语句的惯用法与使用注意事项。</p>
<h3>7. Part4 – 语法基础：函数与方法</h3>
<p>我们日常编写的Go代码逻辑都在函数或方法中，函数/方法是Go程序逻辑的基本承载单元。</p>
<ul>
<li>init函数</li>
</ul>
<p>init函数是包初始化过程中执行的函数，它有很多特殊用途。并且其初始化顺序对程序执行语义也有影响，这方面要搞清楚。书中对init函数的常见用途做了梳理，比如database/sql包的驱动自注册模式等。</p>
<ul>
<li>成为“一等公民” </li>
</ul>
<p>在Go中，函数成为了“一等公民”。函数成为一等公民后可以像变量一样，被作为参数传递到函数中、作为返回值从函数中返回、作为右值赋值给其他变量等，书中系统讲解了这个特性都有哪些性质和特殊应用，比如函数式编程等。</p>
<ul>
<li>defer语句的惯用法与坑</li>
</ul>
<p>defer就是帮你简化代码逻辑的，书中总结了defer语句的应用模式。以及使用defer的注意事项，比如函数求值时机、使用开销等。</p>
<ul>
<li>变长参数函数</li>
</ul>
<p>Go支持变长参数函数。大家可以没有意识到：变长参数函数是我们日常用的最多的一类函数，比如append函数、fmt.Printf系列、log包中提供的按日志严重级别输出日志的函数等。</p>
<p>但变长参数函数可能也是我们自己设计与实现较少的一类函数形式。 变长参数函数能帮我们做什么呢？书中讲解了变长参数函数的常见用途，比如实现功能选项模式等。</p>
<ul>
<li>方法的本质、receiver参数类型选择、方法集合</li>
</ul>
<p>方法的本质其实是函数，弄清楚方法的本质可以帮助我们解决很多难题，书中以实例方式帮助大家理解这一点。</p>
<p>方法receiver参数类型的选择也是Go初学者的常见困惑，这里书中给出三个原则，参照这三个原则，receiver类型选择就不是问题了。</p>
<p>怎么确定一个类型是否实现接口？我们需要看类型的方法集合。那么确定一个类型方法集合就十分重要，尤其是那些包括类型嵌入的类型的方法集合，书中对这块内容做了系统的讲解。</p>
<h3>8. Part5 – 语法核心：接口</h3>
<ul>
<li>接口的内部表示</li>
</ul>
<p>接口是Go语言中的重要语法。Russ Cox曾说过：“如果要从Go语言中挑选出一个特性放入其他语言，我会选择接口”。可见接口的重要性。不过，用好接口类型的前提是理解接口在runtime层的表示，这一节会详细说明空接口与非空接口的内部表示。</p>
<ul>
<li>接口的设计惯例</li>
</ul>
<p>我们应该设计什么样的接口呢？ 大接口有何弊端？小接口有何优势？多小的接口算是合理的呢？这些在本节都有说明。</p>
<ul>
<li>接口与组合</li>
</ul>
<p>组合是Go的设计哲学，Go是关于组合的语言。接口在面向组合编程时将发挥重要作用。这里我将提到Go的两种组合方式：垂直组合和水平组合。其中接口类型在水平组合中起到的关键性的作用。书中还讲解了通过接口进行水平组合的几种模式：包裹模式、适配器函数、中间件等。</p>
<p>很多初学者告诉我，他们做了一段时间Go编码了，但还没有自己设计过接口，我建议这样的同学好好读读这一部分。</p>
<h3>9. Part6 – 语法核心：并发编程</h3>
<ul>
<li>并发设计vs并行设计</li>
</ul>
<p>学习并发编程首先要搞懂并发与并行的概念，书中用了一个很形象的机场安检的例子，来告诉大家并发与并行的区别。并发关乎结构，并行关注执行</p>
<ul>
<li>并发原语的原理与应用模式</li>
</ul>
<p>Go实现了csp模型，提供了goroutine、channel、select并发原语。</p>
<p>理解go并发编程。首先要深入理解基于goroutine的并发模型与调度方式。书中对这方面做了深入浅出的讲解，不涉及太多代码，相信大家都能看懂。</p>
<p>书中还对比了go并发模型，一种是csp，一种是传统的基于共享内存方式，并列举了Go并发的常见模式，比如创建、取消、超时、管道模式等。</p>
<p>另外，channel作为goroutine间通信的标准原语，有很多玩法，这里列举了常见的模式和使用注意事项。</p>
<ul>
<li>低级同步原语(sync和atomic)</li>
</ul>
<p>虽然有了CSP模型的并发原语，极大简化并发编程，但是sync包和原子操作也不能忘记，很多性能敏感的临界区还需要sync包/atomic这样的低级同步原语来同步。</p>
<h3>10. Part7 – 错误处理</h3>
<p>单独将错误处理拎出来，是因为很多人尤其是来自java的童鞋，习惯了try-catch-finally的结构化错误处理，看到go的错误处理就让其头疼。</p>
<p>Go语言十分重视错误处理，但它也的确有着相对保守的设计和显式处理错误的惯例。</p>
<p>本部分涵盖常见Go错误处理的策略、避免if err != nil写太多的方案，更为重要的是panic与错误处理的差别。我见过太多将panic用作正常处理的同学了。尤其是来自java阵营的童鞋。</p>
<h3>11. Part8 – 编程实践：测试、调试与性能剖析</h3>
<p>本部分聚焦编码之外的Go工具链工程实践。</p>
<ul>
<li>Go测试惯例与组织形式 </li>
</ul>
<p>这部分首先和大家聊聊go test包的组织形式，包括是选择包内测试还是包外测试？何时采用符合go惯例的表驱动的测试用例组织形式？如何管理测试依赖的外部数据文件等。</p>
<ul>
<li>模糊测试(fuzzing test)。</li>
</ul>
<p>这里的模糊测试并非基于go 1.18的原生<a href="https://tonybai.com/2021/12/01/first-class-fuzzing-in-go-1-18">fuzzing test</a>进行，写书的时候go 1.18版本尚未发布，而是基于德米特里-维尤科夫的<a href="http://tonybai.com/2015/12/08/go-fuzz-intro/">go-fuzz工具</a>。</p>
<ul>
<li>性能基准测试、度量数据与pprof性能剖析</li>
</ul>
<p>Go原生提供性能基准测试。这一节讲解了如何做性能基准测试、如何编写串行与并行的测试、性能基准测试结果比较工具以及如何排除额外干扰，让结果更准确等方面内容。在讲解pprof性能剖析工具时，我使用一个实例进行剖析讲解，这样理解起来更为直观。</p>
<ul>
<li>Go调试</li>
</ul>
<p>说到Go调试，我们日常使用最多的估计还是print大法。但在print大法之外，其实有一个事实标准的Go调试工具，它就是delve。在这一节中，我讲解了delve的工作原理以及使用delve如何实现并发调试、coredump调试以及在线挂接(attach)进程的调试。</p>
<h3>12. Part9 – 标准库、反射与cgo</h3>
<p>go是自带电池，开箱即用的语言，拥有高质量的标准库。在国外有些Gopher甚至倡导仅依赖标准库实现go应用。</p>
<ul>
<li>高频使用的标准库包（net、http、strings、time、crypto等)</li>
</ul>
<p>在这一节，我对高频使用的标准库包的原理和使用进行拆解分析，net、http、标准库io模型、strings、time、crypto等以帮助大家更高效的运用标准库。</p>
<ul>
<li>reflect包使用的三大法则</li>
</ul>
<p>reflect包为go提供了反射能力，书中对反射的实现原理做了讲解，重点是reflect使用的三大法则。</p>
<ul>
<li>cgo使用</li>
</ul>
<p>cgo不是go，但是cgo机制是使用go与c交互的唯一手段。书中对cgo的用法与约束做了详细讲解，尤其是在cgo开启的情况下如何做静态编译值得大家细读。</p>
<ul>
<li>unsafe包的安全使用法则</li>
</ul>
<p>事实证明unsafe包很有用，但要做到安全使用unsafe包，尤其是unsafe.Pointer，需要遵循一定的安全使用法则。书中对此做了举例详细说明。</p>
<p>反射、cgo、unsafe算是高级话题，要透彻理解，需要多阅读几遍书中内容并结合实践。</p>
<h3>13. Part10 – 工程实践</h3>
<ul>
<li>go module</li>
</ul>
<p>go module在<a href="https://tonybai.com/2018/11/19/some-changes-in-go-1-11/">go 1.11版本</a>中引入go，在<a href="https://tonybai.com/2021/02/25/some-changes-in-go-1-16">go 1.16版本</a>中成为go官方默认构建模式。go程序员入门go，精进go都跨不过go module这道坎儿。书中对go module构建模式做了超级系统的讲解：从go构建模式演进历史、go module的概念、原理、惯例、升降级major版本的操作，到使用注意事项等。不过这里还有有一些瑕疵，那就是go module这一节放置的位置太靠后了，应该往往前面提提。如果后面有修订版，可以考虑这么做。</p>
<ul>
<li>自定义go包导入路径</li>
</ul>
<p>书中还给出了一个自定义go包导入路径的一种实现方案，十分适合组织内部的私有仓库，有兴趣的同学可以重点看看。</p>
<ul>
<li>go命令的使用模式详解</li>
</ul>
<p>这一节将go命令分门别类地进行详细说明。包括：</p>
<pre><code>- 获取与安装的go get/go install
- go包检视的go list
- go包构建的go build
- 运行与诊断的GODEBUG、GOGC等环境变量的功用
- 代码静态检查与重构
- 文档查看
- go代码生成go generate
</code></pre>
<ul>
<li>Go常见的“坑”</li>
</ul>
<p>这一节将Go常见的“坑”进行了一次检阅。我这里将坑分为“语法类”和“标准库类”，并借鉴了央视五套天下足球top10节目，对每个坑的“遇坑指数”与“坑害指数”做了点评。</p>
<h3>14. 具备完整的示例代码与勘误表</h3>
<p>这套书拥有具备完整的示例代码与<a href="https://github.com/bigwhite/GoProgrammingFromBeginnerToMaster">勘误表</a>，它们都被持续维护，让大家没有读书的后顾之忧。</p>
<h2>三. 读书的实践与体会</h2>
<p><img src="https://tonybai.com/wp-content/uploads/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master-4.png" alt="" /></p>
<p>下面我再分享一下我个人是怎么读书的，包括go技术书籍的读书历程，以及关于读书的一些实践体会。</p>
<p>读书是千人千面的事，没有固定标准的。我的读书方法也不见得适合诸位。大家听听即可，觉得还不错，能借鉴上就最好了。</p>
<p>今天收看直播估计以gopher为主，所以首先说说Go语言书籍的阅读历程</p>
<h3>1. Go语言书籍阅读历程：先外后内</h3>
<p>对于IT技术类图书，初期还是要看原版的。这个没办法，因为it编程技术绝大多数来自国外。</p>
<p>我读的第一本Go技术书就是《the way to go》，至今这本书也没有引入国内。这是一本Go语言百科全书，大多数内容如今仍适用。唯一不足是该书成书于Go 1.0发布之前，使用的好像是r60版本，有少部分内容已经不适用。</p>
<p>后来Go 1.0发布后，我还陆续读过Addison-Wesley出版的《programming in go》和《The Go Programming Language Phrasebook》，两本书都还不错。</p>
<p>2015年末的布莱恩.克尼根和go核心团队的多诺万联合编写的《The Go Programming Language》，国内称之为Go圣经的书出版了，这让外文go技术书籍达到了巅峰，后来虽然也有go书籍书籍陆续出版，但都无法触及go圣经的地位。</p>
<p>说完外文图书，我再来说说中文Go图书的阅读历程。</p>
<p>我读过的第一本中文Go书籍是2012年许式伟老师的《Go语言编程》，很佩服许老师的眼光和魄力，七牛云很早就在生产用go。</p>
<p>第二本中文Go书籍是雨痕老师的《go学习笔记》，这也是国内第一本深入到go底层原理的书籍(后半部分)，遗憾的是书籍停留在go 1.5(还是go 1.6)的实现上，没有随Go版本演进而持续更新。</p>
<p>柴大和曹大合著的《go高级编程》也是一本不错的go技术书籍，如果你要深入学习cgo和go汇编，建议阅读此书。</p>
<p>后面的《Go语言底层原理剖析》和《Go语言设计与实现》也都是以深入了解Go运行机制为目标的书籍，口碑都很好，对这方面内容感兴趣的gopher，可以任意挑一本学习。</p>
<h3>2. 自己的读书方法</h3>
<p>我的读书方法其实不复杂，主要分为精读和泛读。</p>
<ul>
<li>阅读方式：好书精读，闲书泛读</li>
</ul>
<p>好书，集中一大段时间内进行阅读。 闲书(不烧脑)，通常是 碎片化阅读。</p>
<ul>
<li>精读方法：摘录+脑图+行动清单</li>
</ul>
<p>摘录就是将书中的观点和细节摘录出来，放到读书笔记，最好能用自己的语言重新描述出来，这样印象深刻，理解更为透彻。</p>
<p>脑图，概括书的思维脉络，防止读完就忘记。 通过脑图，我至少看着脉络能想起来。</p>
<p>行动清单：如果没有能输出行动清单，那这本书对你来说意义就不大。 什么是好书，好书就是那种看完后很迫切的想基于书中的观点做点什么。行动清单将有助于我在后续的行动中反复理解书中内容，提高知识的消化率和理解深度。</p>
<ul>
<li>泛读方法：碎片化+听书</li>
</ul>
<p>泛读主要是碎片化快读或听书，主要是坐地铁，坐公交，散步时。开车时在保证安全的前提下，可以用听书的方式。</p>
<h2>四. 小结</h2>
<p>本次分享了三块内容，这里小结一下：</p>
<ul>
<li>写书历程和写书三要素：能力 + 意愿 + 机会；</li>
<li>Go精进之路导读：思维先行，践行哲学，遵循惯例，认清本质，理解原理；</li>
<li>读书方法：选高质量图书精读(脑图+细节摘录+行动清单）。</li>
</ul>
<h2>五. Q&amp;A</h2>
<ul>
<li>在实际开发中有没有什么优雅的处理error的方法？</li>
</ul>
<p>建议看《Go语言精进之路》第一册第七部分中关于error处理的内容。</p>
<ul>
<li>是否在工作中使用过六边形架构以及依赖注入的处理经验?</li>
</ul>
<p>暂没有使用过六边形架构，生产中没有使用过Go第三方依赖注入的方案。</p>
<ul>
<li>后面会有泛型和模糊测试的补充么？</li>
</ul>
<p>从书籍内容覆盖全面性的角度而言，我个人有补充上述内容的想法，但还要看现在这套书的销售情况以及出版社的计划。目前还没列入个人工作计划。</p>
<ul>
<li>作者总结一系列go方法论、惯例等很实用，这种有逻辑的思考和见解是怎么形成的？</li>
</ul>
<p>没有特意考虑过是怎么形成的。个人平时喜欢多问自己几个为什么，形成让自己信服的工作和学习逻辑。(文字稿补充：同理心、多总结、多复盘、多输出)。</p>
<p>学习Go惯例、方法论，可以多多看Go语言开源项目自身的代码评审，看看Go contributor写代码的思路和如何评审其他贡献者的代码的。(文字稿补充：在这一过程中，潜移默化的感受Go编程思维)。</p>
<ul>
<li>如何阅读大型go项目的源码？</li>
</ul>
<p>我个人的方法就是自上而下。先拆分结构，然后找入口。如果是一个可执行的go程序，还是从入口层层的向后看。然后通过一些工具，比如我个人之前开发的<a href="https://tonybai.com/2020/12/10/a-kind-of-thinking-about-how-to-trace-function-call-chain">函数调用跟踪工具</a>，查看程序执行过程中的函数调用次序。</p>
<p>更细节的内容，还是要深入到代码中去查看。</p>
<ul>
<li>对Go项目中的一些设计模式的看法？如何使用设计模式，使用时注意哪些事项？</li>
</ul>
<p>设计模式在go语言中并不是一个经常拿出来提的东西。我之前的一个观点：在其他语言中，需要大家通过一些额外细心的设计构建出来的设计模式，在Go语言中是自然而然就有的东西。</p>
<p>我在自己的日常编码过程中，不会太多从如何应用设计模式的角度思考，而是按照go设计哲学，去考虑并发设计、组合的设计，而不是非要套用那23个经典设计模式。</p>
<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/07/07/gocn-community-go-book-club-issue2-go-programming-from-beginner-to-master/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go语言数据竞争检测与数据竞争模式</title>
		<link>https://tonybai.com/2022/06/21/data-race-detection-and-pattern-in-go/</link>
		<comments>https://tonybai.com/2022/06/21/data-race-detection-and-pattern-in-go/#comments</comments>
		<pubDate>Tue, 21 Jun 2022 14:42:08 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AddressSanitizer]]></category>
		<category><![CDATA[arxiv]]></category>
		<category><![CDATA[Build]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[closure]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[CSP]]></category>
		<category><![CDATA[data-race]]></category>
		<category><![CDATA[defer]]></category>
		<category><![CDATA[DmitryVyukov]]></category>
		<category><![CDATA[err]]></category>
		<category><![CDATA[error]]></category>
		<category><![CDATA[function-literal]]></category>
		<category><![CDATA[GCC]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go1.1]]></category>
		<category><![CDATA[go1.19]]></category>
		<category><![CDATA[gobuild]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[gorun]]></category>
		<category><![CDATA[gotest]]></category>
		<category><![CDATA[gotools]]></category>
		<category><![CDATA[LeakSanitizer]]></category>
		<category><![CDATA[LLVM]]></category>
		<category><![CDATA[map]]></category>
		<category><![CDATA[MemorySanitizer]]></category>
		<category><![CDATA[Mutex]]></category>
		<category><![CDATA[panic]]></category>
		<category><![CDATA[paper]]></category>
		<category><![CDATA[race]]></category>
		<category><![CDATA[race-detector]]></category>
		<category><![CDATA[runtime]]></category>
		<category><![CDATA[select]]></category>
		<category><![CDATA[ShadowCell]]></category>
		<category><![CDATA[sync]]></category>
		<category><![CDATA[Thread]]></category>
		<category><![CDATA[ThreadSanitizer]]></category>
		<category><![CDATA[TSan]]></category>
		<category><![CDATA[uber]]></category>
		<category><![CDATA[Valgrind]]></category>
		<category><![CDATA[waitgroup]]></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=3597</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2022/06/21/data-race-detection-and-pattern-in-go uber，就是那个早早退出中国打车市场的优步，是Go语言早期接纳者，也是Go技术栈的“重度用户”。uber内部的Go代码仓库有5000w+行Go代码，有2100个Go实现的独立服务，这样的Go应用规模在世界范围内估计也是Top3了吧。 uber不仅用Go，还经常输出它们使用Go的经验与教训，uber工程博客就是这些高质量Go文章的载体，这些文章都值得想“深造”的gopher们反复阅读和体会。 近期该博客发布了两篇有关Go并发数据竞争的文章，一篇为《Dynamic Data Race Detection in Go Code》，另一篇为《Data Race Patterns in Go》。这两篇文章也源于uber工程师发表在arxiv上的预印版论文《A Study of Real-World Data Races in Golang》。 感慨一下：不得不佩服国外工程师的这种“下得了厨房，还上得了厅堂”的研发能力，这也是我在团队中为大家树立的目标。 这里和大家过一下这两篇精简版的博客文章，希望我们都能有收获。 一. Go内置data race detector 我们知道：并发程序不好开发，更难于调试。并发是问题的滋生地，即便Go内置并发并提供了基于CSP并发模型的并发原语(goroutine、channel和select)，实际证明，现实世界中，Go程序带来的并发问题并没有因此减少(手动允悲)。“没有银弹”再一次应验！ 不过Go核心团队早已意识到了这一点，在Go 1.1版本中就为Go工具增加了race detector，通过在执行go工具命令时加入-race，该detector可以发现程序中因对同一变量的并发访问(至少一个访问是写操作)而引发潜在并发错误的地方。Go标准库也是引入race detector后的受益者。race detector曾帮助Go标准库检测出42个数据竞争问题。 race detector基于Google一个团队开发的工具Thread Sanitizer(TSan)(除了thread sanitizer，google还有一堆sanitizer，比如：AddressSanitizer, LeakSanitizer, MemorySanitizer等)。第一版TSan的实现发布于2009年，其使用的检测算法“源于”老牌工具Valgrind。出世后，TSan就帮助Chromium浏览器团队找出近200个潜在的并发问题，不过第一版TSan有一个最大的问题，那就是慢！。 因为有了成绩，开发团队决定重写TSan，于是就有了v2版本。与V1版本相比，v2版本有几个主要变化： 编译期注入代码(instrumentation)； 重新实现运行时库，并内置到编译器(LLVM和GCC)中； 除了可以做数据竞争(data race)检测外，还可以检测死锁、加锁状态下的锁释放等问题； 与V1版本相比，v2版本性能提升约20倍； 支持Go语言。 那么TSan v2究竟是怎么工作的呢？我们继续往下看。 二. ThreadSanitizer v2版本工作原理 根据Thread Sanitizer [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/data-race-detection-and-pattern-in-go-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2022/06/21/data-race-detection-and-pattern-in-go">本文永久链接</a> &#8211; https://tonybai.com/2022/06/21/data-race-detection-and-pattern-in-go</p>
<p>uber，就是那个早早退出中国打车市场的优步，是Go语言早期接纳者，也是Go技术栈的“重度用户”。<a href="https://eng.uber.com/data-race-patterns-in-go/">uber内部的Go代码仓库有5000w+行Go代码</a>，有2100个Go实现的独立服务，这样的Go应用规模在世界范围内估计也是Top3了吧。</p>
<p>uber不仅用Go，还经常输出它们使用Go的经验与教训，<a href="https://eng.uber.com/">uber工程博客</a>就是这些高质量Go文章的载体，这些文章都值得想“深造”的gopher们反复阅读和体会。</p>
<p>近期该博客发布了两篇有关Go并发数据竞争的文章，一篇为<a href="https://eng.uber.com/dynamic-data-race-detection-in-go-code/">《Dynamic Data Race Detection in Go Code》</a>，另一篇为<a href="https://eng.uber.com/data-race-patterns-in-go/">《Data Race Patterns in Go》</a>。这两篇文章也源于uber工程师发表在arxiv上的预印版论文<a href="https://arxiv.org/pdf/2204.00764.pdf">《A Study of Real-World Data Races in Golang》</a>。</p>
<blockquote>
<p>感慨一下：不得不佩服国外工程师的这种“下得了厨房，还上得了厅堂”的研发能力，这也是我在团队中为大家树立的目标。</p>
</blockquote>
<p>这里和大家过一下这两篇精简版的博客文章，希望我们都能有收获。</p>
<hr />
<h3>一. Go内置data race detector</h3>
<p>我们知道：并发程序不好开发，更难于调试。并发是问题的滋生地，即便Go内置并发并提供了基于CSP并发模型的并发原语(goroutine、channel和select)，实际证明，<a href="https://songlh.github.io/paper/go-study.pdf">现实世界中，Go程序带来的并发问题并没有因此减少</a>(手动允悲)。<strong>“没有银弹”再一次应验</strong>！</p>
<p>不过Go核心团队早已意识到了这一点，在<a href="https://go.dev/doc/go1.1#race">Go 1.1版本</a>中就为Go工具增加了race detector，通过在执行go工具命令时加入-race，该detector可以发现程序中因对同一变量的并发访问(至少一个访问是写操作)而引发潜在并发错误的地方。Go标准库也是引入race detector后的受益者。race detector曾<a href="https://go.dev/blog/race-detector">帮助Go标准库检测出42个数据竞争问题</a>。</p>
<p>race detector基于Google一个团队开发的工具<a href="https://github.com/google/sanitizers">Thread Sanitizer(TSan)</a>(除了thread sanitizer，google还有一堆sanitizer，比如：AddressSanitizer, LeakSanitizer, MemorySanitizer等)。第一版TSan的实现发布于2009年，其使用的检测算法“源于”老牌工具Valgrind。出世后，TSan就帮助Chromium浏览器团队找出近200个潜在的并发问题，不过第一版TSan有一个最大的问题，那就是<strong>慢！</strong>。</p>
<p>因为有了成绩，开发团队决定重写TSan，于是就有了v2版本。与V1版本相比，v2版本有几个主要变化：</p>
<ul>
<li>编译期注入代码(instrumentation)；</li>
<li>重新实现运行时库，并内置到编译器(LLVM和GCC)中；</li>
<li>除了可以做数据竞争(data race)检测外，还可以检测死锁、加锁状态下的锁释放等问题；</li>
<li>与V1版本相比，v2版本性能提升约20倍；</li>
<li>支持Go语言。</li>
</ul>
<p>那么TSan v2究竟是怎么工作的呢？我们继续往下看。</p>
<h3>二. ThreadSanitizer v2版本工作原理</h3>
<p>根据<a href="https://github.com/google/sanitizers/wiki/ThreadSanitizerAlgorithm">Thread Sanitizer wiki上对v2版算法的描述</a>，Thread Sanitizer分为两部分：<strong>注入代码与运行时库</strong>。</p>
<h4>1. 注入代码</h4>
<p>第一部分是在编译阶段配合编译器在源码中注入代码。那么<strong>在什么位置注入什么代码呢</strong>？前面说过Thread Sanitizer会跟踪程序中的每次内存访问，因此TSan会在每次内存访问的地方注入代码，当然下面的情况除外：</p>
<ul>
<li>肯定不会出现数据竞争的内存访问</li>
</ul>
<p>比如：全局常量的读访问、函数中对已被证明不会<a href="https://tonybai.com/2021/05/24/understand-go-escape-analysis-by-example/">逃逸到堆</a>上的内存的访问；</p>
<ul>
<li>冗余访问：写入某个内存位置之前发生的读操作</li>
<li>&#8230; &#8230;</li>
</ul>
<p>那么注入的什么代码呢？下面是一个在函数foo内写内存操作的例子：</p>
<p><img src="https://tonybai.com/wp-content/uploads/data-race-detection-and-pattern-in-go-2.png" alt="" /></p>
<p>我们看到对地址p的写操作前注入了&#95;&#95;tsan_write4函数，函数foo的入口和出口分别注入了&#95;&#95;tsan_func_entry和 &#95;&#95;tsan_func_exit。而对于需要注入代码的内存读操作，注入代码则是&#95;&#95;tsan_read4；原子内存操作使用&#95;&#95;tsan_atomic进行注入&#8230;。</p>
<h4>2. TSan运行时库</h4>
<p>一旦在编译期注入代码完毕，构建出带有TSan的Go程序，那么在Go程序运行阶段，起到数据竞争检测作用的就是Tsan运行时库了。TSan是如何检测到有数据竞争的呢？</p>
<p>TSan的检测借助了一个称为<strong>Shadow Cell</strong>的概念。什么是Shadow Cell呢？一个Shadow Cell本身是一个8字节的内存单元，它代表一个对某个内存地址的读/写操作的<strong>事件</strong>，即每次对某内存块的写或读操作都会生成一个Shadow Cell。显然Shadow Cell作为内存读写事件的记录者，其本身存储了与此事件相关的信息，如下图：</p>
<p><img src="https://tonybai.com/wp-content/uploads/data-race-detection-and-pattern-in-go-3.png" alt="" /></p>
<p>我们看到，每个Shadow Cell记录了线程ID、时钟时间、操作访问内存的位置(偏移)和长度以及该内存访问事件的操作属性(是否是写操作)。<strong>针对每个应用程序的8字节内存，TSan都会对应有一组(N个)Shadow Cell</strong>，如下图：</p>
<p><img src="https://tonybai.com/wp-content/uploads/data-race-detection-and-pattern-in-go-4.png" alt="" /></p>
<p>N可以取2、4和8。N的取值直接影响TSan带来的开销以及data race检测的“精度”。</p>
<h4>3. 检测算法</h4>
<p>有了代码注入，也有了记录内存访问事件的Shadow Cell，那么TSan是通过什么逻辑检测data race的呢？我们结合<a href="http://gcc.gnu.org/wiki/cauldron2012?action=AttachFile&amp;do=get&amp;target=kcc.pdf">Google大神Dmitry Vyukov在一次speak中举的例子</a>来看一下检测算法是怎么运作的：</p>
<p>我们以N=8为例(即8个Shadow Cell用于跟踪和校验一个应用的8字节内存块)，下面是初始情况，假设此时尚没有对该8字节应用内存块的读写操作：</p>
<p><img src="https://tonybai.com/wp-content/uploads/data-race-detection-and-pattern-in-go-5.png" alt="" /></p>
<p>现在，一个线程T1向该块内存的前两个字节进行了写操作，写操作会生成第一个Shadow Cell，如下图所示：</p>
<p><img src="https://tonybai.com/wp-content/uploads/data-race-detection-and-pattern-in-go-6.png" alt="" /></p>
<p>这里我们结合图中的Shadow Cell说说Pos字段。Pos字段描述的是写/读操作访问的8字节内存单元的起始偏移与长度，比如这里的<strong>0:2</strong>代表的就是起始字节为第一个字节，长度为2个字节。此时Shadow Cell窗口只有一个Shadow Cell，不存在race的可能。</p>
<p>接下来，一个线程T2又针对该块内存的后四个字节进行了一次读操作，读操作会生成第二个Shadow Cell，如下图所示：</p>
<p><img src="https://tonybai.com/wp-content/uploads/data-race-detection-and-pattern-in-go-7.png" alt="" /></p>
<p>此次读操作涉及的字节与第一个Shadow Cell没有交集，不存在data race的可能。</p>
<p>再接下来，一个线程T3针对该块内存的前四个字节进行了一次写操作，写操作会生成第三个Shadow Cell，如下图所示：</p>
<p><img src="https://tonybai.com/wp-content/uploads/data-race-detection-and-pattern-in-go-8.png" alt="" /></p>
<p>我们看到T1和T3两个线程对该内存块的访问有重叠区域，且T1为写操作，那么这种情况就有可能存在data race。而TSan的race检测算法本质上就是一个状态机，每当发生一次内存访问，都会走一遍状态机。状态机的逻辑也很简单，就是遍历这块内存对应的Shadow Cell窗口中的所有Cell，用最新的Cell与已存在的Cell逐一比对，如果存在race，则给出warning。</p>
<p>像这个例子中T1的write与T3的read区域重叠，如果Shallow Cell1的时钟E1没有happens-before Shadow Cell的时钟E3，那么就存在data race的情况。happens-before如何判定，我们可以从tsan的实现中找到端倪：</p>
<pre><code>https://code.woboq.org/gcc/libsanitizer/tsan/tsan_rtl.cc.html

static inline bool HappensBefore(Shadow old, ThreadState *thr) {
    return thr-&gt;clock.get(old.TidWithIgnore()) &gt;= old.epoch();
}
</code></pre>
<p>在这个例子中，对应一个8字节应用内存的一组Shadow Cell的数量为N=8，但内存访问是高频事件，因此很快Shadow Cell窗口就会写满，那么新的Shadow Cell存储在哪里呢？在这种情况下，TSan算法会随机删除一个old Shadow Cell，并将新Shadow Cell写入。这也印证了前面提到的：N值的选取会在一定程度上影响到TSan的检测精度。</p>
<p>好了，初步了解了TSan v2的检测原理后，我们再回到uber的文章，看看uber是在何时部署race检测的。</p>
<h3>三. 何时部署一个动态的Go数据竞争检测器</h3>
<p>通过前面对TSan原理的简单描述我们也可以看出，-race带来的数据竞争检测对程序运行性能和开销的影响还是蛮大的。Go官方文档<a href="https://go.dev/doc/articles/race_detector">《Data Race Detector》</a>一文中给出使用-race构建的Go程序相较于正常构建的Go程序，运行时其内存开销是后者的5-10倍，执行时间是2-20倍。但我们知道race detector只能在程序运行时才能实施数据竞争问题的检测。因此，Gopher在使用-race都会比较慎重，尤其是在生产环境中。 2013年，Dmitry Vyukov和Andrew Gerrand联合撰写的介绍Go race detector的文章<a href="https://go.dev/blog/race-detector">“introducing the go race detector”</a>中也直言：<strong>在生产环境一直开着race detector是不实际的</strong>。他们推荐两个使用race detector的时机：一个是在测试执行中开启race detector，尤其是集成测试和压力测试场景下；另外一个则是在生产环境下开启race detector，但具体操作是：仅在众多服务实例中保留一个带有race detector的服务实例，但有多少流量打到这个实例上，你自己看着办^_^。</p>
<p>那么，uber内部是怎么做的呢？前面提到过：uber内部有一个包含5000w+行代码的单一仓库，在这个仓库中有10w+的单元测试用例。uber在部署race detector的时机上遇到两个问题：</p>
<ul>
<li>由于-race探测结果的不确定性，使得针对每个pr进行race detect的效果不好。</li>
</ul>
<p>比如：某个pr存在数据竞争，但race detector执行时没有检测到；后来的没有data race的PR在执行race detect时可能会因前面的pr中的data race而被检测出问题，这就可能影响该pr的顺利合入，影响相关开发人员的效率。</p>
<p>同时，将已有的5000w+代码中的所有data race情况都找出来本身也是不可能的事情。</p>
<ul>
<li>race detector的开销会影响到SLA(我理解是uber内部的CI流水线也有时间上的SLA(给开发者的承诺)，每个PR跑race detect，可能无法按时跑完)，并且提升硬件成本</li>
</ul>
<p>针对上述这两个问题，给出的部署策略是“事后检测”，即每隔一段时间，取出一版代码仓库的快照，然后在-race开启的情况下，把所有单元测试用例跑一遍。好吧，似乎没有什么新鲜玩意。很多公司可能都是这么做的。</p>
<p>发现data race问题，就发报告给相应开发者。这块uber工程师做了一些工作，通过data race检测结果信息找出最可能引入该bug的作者，并将报告发给他。</p>
<p>不过有一个数据值得大家参考：在没有data race检测的情况下，uber内部跑完所有单元测试的时间p95位数是25分钟，而在启用data race后，这个时间增加了4倍，约为100分钟。</p>
<p>uber工程师在2021年中旬实施的上述实验，在这一实验过程中，他们找到了产生data race的主要代码模式，后续他们可能会针对这些模式制作静态代码分析工具，以更早、更有效地帮助开发人员捕捉代码中的data race问题。接下来，我们就来看看这些代码模式。</p>
<h3>四. 常见的数据竞争模式都有哪些</h3>
<p><a href="https://eng.uber.com/data-race-patterns-in-go/">uber工程师总结了7类数据竞争模式</a>，我们逐一看一下。</p>
<h4>1. 闭包的“锅”</h4>
<p><a href="https://tonybai.com/2021/08/09/when-variables-captured-by-closures-are-recycled-in-go">Go语言原生提供了对闭包(closure)的支持</a>。在Go语言中，闭包就是<a href="https://tip.golang.org/ref/spec#Function_literals">函数字面值</a>。闭包可以引用其包裹函数(surrounding function)中定义的变量。然后，这些变量在包裹函数和函数字面值之间共享，只要它们可以被访问，这些变量就会继续存在。</p>
<p>不过不知道大家是否意识到了Go闭包对其包裹函数中的变量的捕捉方式都是通过引用的方式。而不像C++等语言那样可以选择通过值方式(by value)还是引用方式(by reference)进行捕捉。引用的捕捉方式意味着一旦闭包在一个新的goroutine中执行，那么两个goroutine对被捕捉的变量的访问就很大可能形成数据竞争。“不巧的”的是在Go中闭包常被用来作为一个goroutine的执行函数。</p>
<p>uber文章中给出了三个与这种无差别的通过引用方式对变量的捕捉方式导致的数据竞争模式的例子：</p>
<ul>
<li>例子1</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/data-race-detection-and-pattern-in-go-9.png" alt="" /></p>
<p>这第一个例子中，每次循环都基于一个闭包函数创建一个新的goroutine，这些goroutine都捕捉了外面的循环变量job，这就在多个goroutine之间建立起对job的竞争态势。</p>
<ul>
<li>例子2</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/data-race-detection-and-pattern-in-go-10.png" alt="" /></p>
<p>例子2中闭包与变量声明作用域的结合共同造就了新goroutine中的err变量就是外部Foo函数的返回值err。这就会造成err值成为两个goroutine竞争的“焦点”。</p>
<ul>
<li>例子3</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/data-race-detection-and-pattern-in-go-11.png" alt="" /></p>
<p>例子3中，<a href="https://tonybai.com/2022/05/20/solving-problems-in-generic-function-implementation-using-named-return-values">具名返回值变量</a>result被作为新goroutine执行函数的闭包所捕获，导致了两个goroutine在result这个变量上产生数据竞争。</p>
<h4>2. 切片的“锅”</h4>
<p>切片是Go内置的复合数据类型，与传统数组相比，切片具备动态扩容的能力，并且在传递时传递的是“切片描述符”，开销小且固定，这让其在Go语言中得到了广泛的应用。但灵活的同时，切片也是Go语言中“挖坑”最多的数据类型之一，大家在使用切片时务必认真细致，稍不留神就可能犯错。</p>
<p>下面是一个在切片变量上形成数据竞争的例子：</p>
<p><img src="https://tonybai.com/wp-content/uploads/data-race-detection-and-pattern-in-go-12.png" alt="" /></p>
<p>从这份代码来看，开发人员虽然对被捕捉的切片变量myResults通过mutex做了同步，但在后面创建新goroutine时，在传入切片时却因没有使用mutex保护。不过例子代码似乎有问题，传入的myResults似乎没有额外的使用。</p>
<h4>3. map的“锅”</h4>
<p>map是Go另外一个最常用的内置复合数据类型， 对于go入学者而言，由map导致的问题可能仅次于切片。go map并非goroutine-safe的，go禁止对map变量的并发读写。但由于是内置hash表类型，map在go编程中得到了十分广泛的应用。</p>
<p><img src="https://tonybai.com/wp-content/uploads/data-race-detection-and-pattern-in-go-13.png" alt="" /></p>
<p>上面例子就是一个并发读写map的例子，不过与slice不同，go在map实现中内置了对并发读写的检测，即便不加入-race，一旦发现也会抛出panic。</p>
<h4>4. 误传值惹的祸</h4>
<p>Go推荐使用传值语义，因为它简化了逃逸分析，并使变量有更好的机会被分配到栈中，从而减少GC的压力。但有些类型是不能通过传值方式传递的，比如下面例子中的sync.Mutex：</p>
<p><img src="https://tonybai.com/wp-content/uploads/data-race-detection-and-pattern-in-go-14.png" alt="" /></p>
<p>sync.Mutex是一个零值可用的类型，我们无需做任何初始赋值即可使用Mutex实例。但Mutex类型有内部状态的：</p>
<p><img src="https://tonybai.com/wp-content/uploads/data-race-detection-and-pattern-in-go-15.png" alt="" /></p>
<p>通过传值方式会导致状态拷贝，失去了在多个goroutine间同步数据访问的作用，就像上面例子中的Mutex类型变量m那样。</p>
<h4>5. 误用消息传递(channel)与共享内存</h4>
<p>Go采用CSP的并发模型，而channel类型充当goroutine间的通信机制。虽然相对于共享内存，CSP并发模型更为高级，但从实际来看，在对CSP模型理解不到位的情况下，使用channel时也十分易错。</p>
<p><img src="https://tonybai.com/wp-content/uploads/data-race-detection-and-pattern-in-go-16.png" alt="" /></p>
<p>这个例子中的问题在于Start函数启动的goroutine可能阻塞在f.ch的send操作上。因为，一旦ctx cancel了，Wait就会退出，此时没有goroutine再在f.ch上阻塞读，这将导致Start函数启动的新goroutine可能阻塞在“f.ch &lt;- 1”这一行上。</p>
<p>大家也可以看到，像这样的问题很细微，如果不细致分析，很难肉眼识别出来。</p>
<h4>6. sync.WaitGroup误用导致data race问题</h4>
<p>sync.WaitGroup是Go并发程序常用的用于等待一组goroutine退出的机制。它通过Add和Done方法实现内部计数的调整。而Wait方法用于等待，直到内部计数器为0才会返回。不过像下面例子中的对WaitGroup的误用会导致data race问题：</p>
<p><img src="https://tonybai.com/wp-content/uploads/data-race-detection-and-pattern-in-go-17.png" alt="" /></p>
<p>我们看到例子中的代码将wg.Add(1)放在了goroutine执行的函数中了，而没有像正确方法那样，将Add(1)放在goroutine创建启动之前，这就导致了对WaitGroup内部计数器形成了数据竞争，很可能因goroutine调度问题，是的Add(1)在未来得及调用，从而导致Wait提前返回。</p>
<p>下面这个例子则是由于defer函数在函数返回时的执行顺序问题，导致两个goroutine在locationErr这个变量上形成数据竞争：</p>
<p><img src="https://tonybai.com/wp-content/uploads/data-race-detection-and-pattern-in-go-18.png" alt="" /></p>
<p>main goroutine在判断locationErr是否为nil的时候，另一个goroutine中的doCleanup可能执行，也可能没有执行。</p>
<h4>7. 并行的表驱动测试可能引发数据竞争</h4>
<p>Go内置单测框架，并支持并行测试(testing.T.Parallel())。但如若使用并行测试，则极其容易导致数据竞争问题，原文没有给出例子，这个大家自行体会吧。</p>
<h3>五. 小结</h3>
<p>关于data race的代码模式，在uber发布这两篇文章之前，也有一些资料对数据竞争问题的代码模式进行了分类整理，比如下面两个资源，大家可以参照着看。</p>
<ul>
<li>《Data Race Detector》- https://go.dev/doc/articles/race_detector</li>
<li>《ThreadSanitizer Popular Data Races》- https://github.com/google/sanitizers/wiki/ThreadSanitizerPopularDataRaces中的模式</li>
</ul>
<p>在刚刚发布的<a href="https://tonybai.com/2022/06/12/go-1-19-foresight">Go 1.19beta1版本</a>中提到，最新的-race升级到了TSan v3版本，race检测性能相对于上一版将提升1.5倍-2倍，内存开销减半，并且没有对goroutine的数量的上限限制。</p>
<blockquote>
<p>注：Go要使用-race，则必须启用CGO。</p>
</blockquote>
<pre><code>// runtime/race.go

//go:nosplit
func raceinit() (gctx, pctx uintptr) {
    // cgo is required to initialize libc, which is used by race runtime
    if !iscgo {
        throw("raceinit: race build must use cgo")
    }
    ... ...
}
</code></pre>
<h3>六. 参考资料</h3>
<ul>
<li>“Finding races and memory errors with compiler instrumentation” &#8211; http://gcc.gnu.org/wiki/cauldron2012?action=AttachFile&amp;do=get&amp;target=kcc.pdf</li>
<li>《Race detection and more with ThreadSanitizer 2》 &#8211; https://lwn.net/Articles/598486/</li>
<li>《Google ThreadSanitizer &#8212; 排查多线程问题data race的大杀器》- https://zhuanlan.zhihu.com/p/139000777</li>
<li>《Introducing the Go Race Detector》- https://go.dev/blog/race-detector</li>
<li>ThreadSanitizer Algorithm V2 &#8211; https://github.com/google/sanitizers/wiki/ThreadSanitizerAlgorithm</li>
<li>paper: FastTrack: Efficient and Precise Dynamic Race Detection &#8211; https://users.soe.ucsc.edu/~cormac/papers/pldi09.pdf</li>
<li>paper: Eraser: A Dynamic Data Race Detector for Multithreaded Programs &#8211; https://homes.cs.washington.edu/~tom/pubs/eraser.pdf</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/06/21/data-race-detection-and-pattern-in-go/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go语言之父谈Go编程语言与环境</title>
		<link>https://tonybai.com/2021/10/06/the-go-programming-language-and-environment/</link>
		<comments>https://tonybai.com/2021/10/06/the-go-programming-language-and-environment/#comments</comments>
		<pubDate>Wed, 06 Oct 2021 04:43:31 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[cloud]]></category>
		<category><![CDATA[CNCF]]></category>
		<category><![CDATA[CSP]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[generics]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Go1]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[Go社区]]></category>
		<category><![CDATA[http]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[KenThompson]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[RobertGriesemer]]></category>
		<category><![CDATA[RobPike]]></category>
		<category><![CDATA[scale]]></category>
		<category><![CDATA[select]]></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=3302</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2021/10/06/the-go-programming-language-and-environment 2021年中旬，Go语言联合创始人Rob Pike应邀在线出席由UNSW Computing(悉尼新南威尔士大学计算机)组织主办的John Lions Distinguished Lectures，会上Rob Pike以Go之父身份讲述了究竟是什么将Go语言塑造成今天的这个样子以及进入Go生态系统的其他一些事物。 Rob Pike关于Go的观点总是高屋建瓴的，从这个talk中我们可以了解Go语言演化的来龙去脉，这对于我们理解Go、理解Go演化方向、理解Go生态会有较大帮助。由于仅有视频资料，这里将视频中的slide截图按顺序贴在这里，并配以slide中没有但talk中有的一些rob pike的重要观点，供大家参考。 Rob Pike： (谦虚的说)Go还不能算是主流语言，但Go在全世界范围的影响力与发展远超当初预期。 我们知道：在众多编程语言中，Go可能不是那种interesting的语言。在当时，Go甚至不是一种有技术优势的语言。我们并没有试图推动编程语言理论或设计甚至实践的进步。我们对此并不介意，因为这不是我们的目标。 不知何故，这种语言已经成功地接管了云世界。它是主导docker、kubernetes以及基本上云原生计算基金会中的所有东西的开发语言，当然也包括这之外的其他很多项目。 多年前，有人预测Go是云计算基础设施语言，但现在这已经成为现实。 那么问题来了：一种本质上无人喜欢的语言是如何最终变得如此重要了呢？究竟发生了什么？ Rob Pike给出答案： 一门编程语言的成功取决于很多东西，而不仅仅是语言本身。 Go团队从一开始就知道这一点，于是他们不再局限于创造一门新编程语言，而是将目标定为创造一种编写软件的更好的方法上。因此这门新编程语言将被用于处理当时所用语言所解决不了的诸多问题：包括上面slide中列举的诸多问题。 虽然编程语言本身可以解决上面的一些问题，但仅语言本身还远不够。 Rob Pike： 我们遇到的一个最大的问题就是scale，并且scale拥有多个维度(数轴axes)，包括concurrency、engineering、dependencies。 Rob Pike： - 这就是我们几个第一次碰面设计一门新编程语言时讨论的话题。 Rob Pike： - 这就是Go实现的一个生产就绪的Web server的代码。 - 下面探讨fmt.Fprintf的第一个参数的类型，它很特殊，它是一个io.Writer接口类型。 Rob Pike： - Go代码中充满了这种仅有一两个方法甚至是零个方法的接口类型，这些构成了Go文化之一。 - 我们相信，接口不应该为你所构建的整个世界预先定义，而应该在程序开发过程中有机地产生。让编译器解决一个接口是否好的问题，实际上是比强迫程序员优先解决这些问题更有效的进行软件演化的方式。(because we believe that interfaces should not be predefined for the [...]]]></description>
			<content:encoded><![CDATA[<p><a href="https://tonybai.com/2021/10/06/the-go-programming-language-and-environment">本文永久链接</a> &#8211; https://tonybai.com/2021/10/06/the-go-programming-language-and-environment</p>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/1.png" alt="" /></p>
<p>2021年中旬，Go语言联合创始人Rob Pike应邀在线出席由UNSW Computing(悉尼新南威尔士大学计算机)组织主办的<a href="https://www.youtube.com/channel/UCghXRiDxEojP599HKE_nRZg">John Lions Distinguished Lectures</a>，会上Rob Pike以Go之父身份讲述了究竟是什么将Go语言塑造成今天的这个样子以及进入Go生态系统的其他一些事物。</p>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/2.png" alt="" /></p>
<p>Rob Pike关于Go的观点总是高屋建瓴的，从<a href="https://www.youtube.com/watch?v=YXV7sa4oM4I">这个talk</a>中我们可以了解Go语言演化的来龙去脉，这对于我们理解Go、理解Go演化方向、理解Go生态会有较大帮助。由于仅有视频资料，这里将视频中的slide截图按顺序贴在这里，并配以slide中没有但talk中有的一些rob pike的重要观点，供大家参考。</p>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/3.png" alt="" /></p>
<p>Rob Pike：</p>
<ul>
<li>(谦虚的说)Go还不能算是主流语言，但Go在全世界范围的影响力与发展远超当初预期。</li>
<li>我们知道：在众多编程语言中，Go可能不是那种interesting的语言。在当时，Go甚至不是一种有技术优势的语言。我们并没有试图推动编程语言理论或设计甚至实践的进步。我们对此并不介意，因为这不是我们的目标。 </li>
<li>不知何故，这种语言已经成功地接管了云世界。它是主导docker、kubernetes以及基本上云原生计算基金会中的所有东西的开发语言，当然也包括这之外的其他很多项目。</li>
<li>多年前，有人预测Go是云计算基础设施语言，但现在这已经成为现实。</li>
</ul>
<p>那么问题来了：一种本质上无人喜欢的语言是如何最终变得如此重要了呢？究竟发生了什么？</p>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/4.png" alt="" /></p>
<p>Rob Pike给出答案：</p>
<ul>
<li>一门编程语言的成功取决于很多东西，而不仅仅是语言本身。</li>
<li>Go团队从一开始就知道这一点，于是他们不再局限于创造一门新编程语言，而是将目标定为<strong>创造一种编写软件的更好的方法上</strong>。因此这门新编程语言将被用于处理当时所用语言所解决不了的诸多问题：包括上面slide中列举的诸多问题。</li>
<li>虽然编程语言本身可以解决上面的一些问题，但仅语言本身还远不够。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/5.png" alt="" /></p>
<p>Rob Pike：</p>
<ul>
<li>我们遇到的一个最大的问题就是scale，并且scale拥有多个维度(数轴axes)，包括concurrency、engineering、dependencies。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/6.png" alt="" /></p>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/7.png" alt="" /></p>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/8.png" alt="" /></p>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/9.png" alt="" /></p>
<p>Rob Pike：<br />
- 这就是我们几个第一次碰面设计一门新编程语言时讨论的话题。</p>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/10.png" alt="" /></p>
<p>Rob Pike：<br />
- 这就是Go实现的一个生产就绪的Web server的代码。<br />
- 下面探讨fmt.Fprintf的第一个参数的类型，它很特殊，它是一个io.Writer接口类型。</p>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/11.png" alt="" /></p>
<p>Rob Pike：<br />
- Go代码中充满了这种仅有一两个方法甚至是零个方法的接口类型，这些构成了Go文化之一。<br />
- 我们相信，接口不应该为你所构建的整个世界预先定义，而应该在程序开发过程中有机地产生。让编译器解决一个接口是否好的问题，实际上是比强迫程序员优先解决这些问题更有效的进行软件演化的方式。(because we believe that interfaces should not be predefined for the entire world you are building. but instead should arise organically through program development. and having the compiler work out whether an interface is good or not is an actually more effective way to grow software than forcing the programmers to work it all out a priori)。</p>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/12.png" alt="" /></p>
<p>Rob Pike：<br />
- 不同于其他编程语言，这些整型不能混合在一起运算(译注：需显式转型)。</p>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/13.png" alt="" /></p>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/14.png" alt="" /></p>
<p>Rob Pike：</p>
<ul>
<li>我们的想法是，从概念上讲，处理并行性和并发性的开销在Go中是非常轻的。这是该语言的一个重要卖点。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/15.png" alt="" /></p>
<p>Rob Pike：</p>
<ul>
<li>一旦你把channel/select这些和goroutines结合起来，你就可以完全简单地、正交地把它们放在过程语言(procedure language)之上。并使并发变得简单，让那些以前我承认有时害怕它的人可以使用。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/16.png" alt="" /></p>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/17.png" alt="" /></p>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/18.png" alt="" /></p>
<p>Rob Pike：</p>
<ul>
<li>我们做了很多努力来建立一套非常好的核心库，允许你做一些事情，如网络、密码学、文本处理、格式化的IO，我们建立了一套核心库，建立在这些简单的接口的想法上，并使用这些接口和其他我们可以使用的机制，如并发性和内存安全属性等等。我们建立了基础库，这样你就可以写一个程序，只使用核心库，这将起到有效的作用，它也可以在生产中启动，并能够处理成千上万并发进行的负载。我们已经看到运行在内部启动的数百万个goroutine的二进制文件，因为它们是轻量级的，它们可以扩展。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/19.png" alt="" /></p>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/20.png" alt="" /></p>
<p>Rob Pike：</p>
<ul>
<li>也许Go的成功最重要的部分是这种兼容性承诺(Go1兼容性承诺)。</li>
<li>更重要的是，我们向用户承诺，如果你的代码今天能用，十年后也能用，而且确实如此。这种对用户社区的承诺是Go应用的一个巨大特点。实际上，在曲线上有一个膝盖型突起，你可以看到采用率的上升，工业界现在可以开始依赖它，因为他们知道，如果他们投资于它，它就会工作。书的作者也可以写书，他们知道十年后书中内容仍然有意义，这是我们故事的一个主要部分。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/21.png" alt="" /></p>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/22.png" alt="" /></p>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/23.png" alt="" /></p>
<p>Rob Pike：</p>
<ul>
<li>因此，所有这些元素都有一个主题，这个主题就是，如果你想发展一种语言或一个系统，特别是在开源世界中，你必须让别人容易进来。这并不仅仅意味着接受每一个他人提出的pull request，这更意味着创建一个系统，在这个系统中，大家可以很容易使用一种语言，比如：易于解析，易于用支持它的工具进行分析。可以单独工作的库，但被设计成可以相互协作以建立更大的系统。用于高质量工具开发的包，易于理解的开发，高速执行，简单的部署，易于移植。一个模块系统让每个人都能舒适地分享他们的代码，也包括一种鼓励人们共同成长的文化。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/24.png" alt="" /></p>
<p>Rob Pike：</p>
<ul>
<li>我们已经建立起这个社区，在社区中大家一起构建了一个软件开发环境并且乐趣多多，这个环境不仅是由语言所培育的，更多是因为上面这些更为重要的因素。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/25.png" alt="" /></p>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/26.png" alt="" /></p>
<p>Rob Pike：</p>
<ul>
<li>Go是关于软件开发的。它不仅仅是关于编程。我认为这就是为什么它能做得那么好的原因。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/the-go-programming-language-and-environment/27.png" alt="" /></p>
<ul>
<li>泛型会不会改变编写Go代码的方式？</li>
</ul>
<p>Rob Pike：</p>
<p>我们没有从一开始就把它们放进去，因为我们不明白我们怎么会对它感到不舒服，所以不是我们决定不放它们，而是我们不确定如果我们从一个具有参数化多态性的语言开始，如何在所有这些其他方面实现我们想实现的目标。</p>
<p>我相信这仍然是事实。</p>
<p>我相信关于库的工作方式和互连的工作方式等等的很多事情都会有非常不同的味道。 如果它是一种多态的语言，我不确定它会有多好。</p>
<p>经过Ian Taylor等人十多年的努力，我们现在有了一个设计，我想说的是，我们不是真正的我，但团队有了一个参数化多态性模型的设计，感觉它与语言的其他部分相匹配。我很想知道它是否会打破这个局面，它可能会打破一切，因为程序员会开始考虑用这种方式写代码，我很想知道它的效果。</p>
<ul>
<li>Rob Pike的其他观点
<ul>
<li>我认为声明变量的方式有些多。</li>
<li>经过我们三人(Rob Pike, Ken Thompson, Robert)达成一致的Go特性已经足够多，足够好了。</li>
<li>我们很努力地寻找channel与network一起工作的方式，但我们失败了！</li>
</ul>
</li>
</ul>
<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/10/06/the-go-programming-language-and-environment/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
