<?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; 内存模型</title>
	<atom:link href="http://tonybai.com/tag/%e5%86%85%e5%ad%98%e6%a8%a1%e5%9e%8b/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Sat, 23 May 2026 23:26:24 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>真相调查：Go 语言真的消灭了 Undefined Behavior 吗？</title>
		<link>https://tonybai.com/2026/03/16/go-language-eliminated-undefined-behavior-truth-investigation/</link>
		<comments>https://tonybai.com/2026/03/16/go-language-eliminated-undefined-behavior-truth-investigation/#comments</comments>
		<pubDate>Mon, 16 Mar 2026 00:27:35 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[ArrayIndexOutOfBounds]]></category>
		<category><![CDATA[BufferOverflow]]></category>
		<category><![CDATA[CompilerOptimization]]></category>
		<category><![CDATA[Concurrency]]></category>
		<category><![CDATA[DataRace]]></category>
		<category><![CDATA[DaveCheney]]></category>
		<category><![CDATA[Determinism]]></category>
		<category><![CDATA[EngineeringTradeoffs]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GoLanguage]]></category>
		<category><![CDATA[Go语言]]></category>
		<category><![CDATA[IanLanceTaylor]]></category>
		<category><![CDATA[IntegerOverflow]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[memorymodel]]></category>
		<category><![CDATA[MemorySafety]]></category>
		<category><![CDATA[MultiwordDataStructures]]></category>
		<category><![CDATA[NilPointerDereference]]></category>
		<category><![CDATA[RaceDetection]]></category>
		<category><![CDATA[RuntimeChecks]]></category>
		<category><![CDATA[Slice]]></category>
		<category><![CDATA[TypeConfusion]]></category>
		<category><![CDATA[UndefinedBehavior]]></category>
		<category><![CDATA[UnspecifiedBehavior]]></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=6045</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/03/16/go-language-eliminated-undefined-behavior-truth-investigation 大家好，我是Tony Bai。 在系统编程的古老传说中，流传着一个关于“鼻恶魔”（Nasal Demons）的笑话。 这个梗源自 comp.std.c 新闻组，它是对 C/C++ 语言中“未定义行为”（Undefined Behavior，以下简称 UB）最生动也最恐怖的诠释。根据 ISO C++ 标准，如果你的代码触犯了 UB（例如数组越界、有符号整数溢出、空指针解引用），编译器可以“为所欲为”。 这种“为所欲为”不仅包括程序崩溃，还包括产生错误的结果、损坏数据，甚至——虽然只是笑话——让恶魔从你的鼻孔里飞出来。换句话说，一旦触碰 UB，程序的所有保证瞬间失效。 2009 年，Go 语言横空出世，高举“云原生时代系统语言”的旗帜，承诺提供比 C++ 更高的安全性、更快的编译速度和更简单的并发模型。Go 的拥趸们津津乐道于它的内存安全特性，仿佛 Go 已经彻底终结了 UB 的噩梦。 但真相果真如此吗？ 近日，我翻阅了一份珍贵的历史资料——2013 年发生在 golang-nuts 邮件组的一场深度辩论。对话的一方是 Go 语言曾经的顶级贡献者 Dave Cheney，另一方是 Go 核心团队成员、gccgo 的作者 Ian Lance Taylor。 这场发生在这个语言童年时期的对话，揭示了一个令人背脊发凉又引人深思的事实：Go 并没有完全消灭未定义行为，它只是将 UB 赶进了一个更隐秘、更危险的角落——并发。 本文将带你层层剥开 Go 语言规范的表皮，调查“未定义行为”在 Go 中的真实生存状态，并探讨这对我们编写高质量代码意味着什么。 用“定义”换取“安全”——Go [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2026/go-language-eliminated-undefined-behavior-truth-investigation-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/03/16/go-language-eliminated-undefined-behavior-truth-investigation">本文永久链接</a> &#8211; https://tonybai.com/2026/03/16/go-language-eliminated-undefined-behavior-truth-investigation</p>
<p>大家好，我是Tony Bai。</p>
<p>在系统编程的古老传说中，流传着一个关于“鼻恶魔”（Nasal Demons）的笑话。</p>
<p>这个梗源自 comp.std.c 新闻组，它是对 C/C++ 语言中“未定义行为”（Undefined Behavior，以下简称 UB）最生动也最恐怖的诠释。根据 ISO C++ 标准，如果你的代码触犯了 UB（例如数组越界、有符号整数溢出、空指针解引用），编译器可以“为所欲为”。</p>
<p>这种“为所欲为”不仅包括程序崩溃，还包括产生错误的结果、损坏数据，甚至——虽然只是笑话——让恶魔从你的鼻孔里飞出来。换句话说，一旦触碰 UB，程序的所有保证瞬间失效。</p>
<p>2009 年，Go 语言横空出世，高举“云原生时代系统语言”的旗帜，承诺提供比 C++ 更高的安全性、更快的编译速度和更简单的并发模型。Go 的拥趸们津津乐道于它的内存安全特性，仿佛 Go 已经彻底终结了 UB 的噩梦。</p>
<p><strong>但真相果真如此吗？</strong></p>
<p>近日，我翻阅了一份珍贵的历史资料——2013 年发生在 golang-nuts 邮件组的<a href="https://groups.google.com/g/golang-nuts/c/MB1QmhDd_Rk">一场深度辩论</a>。对话的一方是 Go 语言曾经的顶级贡献者 Dave Cheney，另一方是 Go 核心团队成员、gccgo 的作者 Ian Lance Taylor。</p>
<p>这场发生在这个语言童年时期的对话，揭示了一个令人背脊发凉又引人深思的事实：<strong>Go 并没有完全消灭未定义行为</strong>，它只是将 UB 赶进了一个更隐秘、更危险的角落——并发。</p>
<p>本文将带你层层剥开 Go 语言规范的表皮，调查“未定义行为”在 Go 中的真实生存状态，并探讨这对我们编写高质量代码意味着什么。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/agentic-software-engineering-qr.png" alt="" /></p>
<h2>用“定义”换取“安全”——Go 的显式哲学</h2>
<p>要理解 Go 做了什么，我们首先得明白 C/C++ 为什么保留 UB。Ian Lance Taylor 指出，C/C++ 保留 UB 本质上是为了<strong>性能</strong>——允许编译器假设“坏事永远不会发生”，从而进行激进的优化。</p>
<p>Dave Cheney 的疑问直击灵魂：“Go 规范中几乎看不到‘undefined’这个词，这种设计如何影响了 Go 的安全性与性能？”</p>
<p>答案是：Go 选择了一条确定性（Determinism）优先的道路。Go 语言规范以一种近乎偏执的态度，将绝大多数在 C/C++ 中属于 UB 的行为，都进行了严格的“定义”。即便是在错误场景下，Go 也要保证行为是<strong>可预测的</strong>。</p>
<h3>整数溢出的“确定性”承诺</h3>
<p>在 C 语言中，有符号整数（Signed Integer）的溢出是经典的 UB。编译器有权假设溢出永远不会发生，从而将 x + 1 > x 优化为恒真（Always True），这曾导致过无数的安全漏洞。</p>
<p>但在 Go 语言规范中，对此有着截然不同的定义：</p>
<blockquote>
<p>无符号整数：运算结果严格按照 2^n 取模。这意味着高位被丢弃，程序可以依赖这种“回绕（Wrap-around）”行为。</p>
<p>有符号整数：运算可以合法地溢出（legally overflow）。结果由有符号整数的表示方式（通常是补码）、运算类型和操作数确定性地定义。溢出不会导致运行时 Panic。</p>
</blockquote>
<p>最关键的是，Go 规范明确禁止编译器进行危险的假设：“编译器不得假设溢出不会发生。例如，它不得假设 x &lt; x + 1 总是为真。”</p>
<p><strong>代码实证：</strong></p>
<pre><code class="go">// https://go.dev/play/p/5CZVVU-SITX
package main

import "fmt"

func main() {
    // 1. 有符号整数溢出 (Signed Overflow)
    var a int8 = 127
    // 在 C 语言中这是 UB，但在 Go 中这是明确定义的
    b := a + 1
    fmt.Printf("int8: %d + 1 = %d\n", a, b)
    // 输出: 127 + 1 = -128 (确定性的回绕)

    // 2. 编译器禁止做的优化
    // 如果编译器假设溢出不发生，它会把这个判断优化掉
    if b &lt; a {
        fmt.Println("发生溢出：b 确实小于 a")
    } else {
        fmt.Println("未发生溢出逻辑（Go 中不会走到这里）")
    }

    // 3. 无符号整数溢出 (Unsigned Overflow)
    var c uint8 = 255
    d := c + 1
    fmt.Printf("uint8: %d + 1 = %d\n", c, d)
    // 输出: 255 + 1 = 0 (严格的 Modulo 2^n)
}
</code></pre>
<p>Go这么做的代价是Go 编译器失去了一些数学优化机会（例如不能简单地消除某些循环边界检查）。但也消除了因编译器“自作聪明”而导致的逻辑崩塌，保证了不同平台下的行为一致性。</p>
<h3>数组越界的“必杀令”</h3>
<p>缓冲区溢出（Buffer Overflow）是网络安全史上最大的杀手。C/C++ 将越界访问视为 UB，允许攻击者通过越界读取敏感内存或覆盖返回地址，进而控制系统。</p>
<p>Go 对此零容忍：<strong>越界必须触发 Panic。</strong></p>
<p>无论是在栈上分配的数组，还是在堆上分配的切片，Go 编译器都会在每一次访问操作前（除非能静态证明安全）插入一段 Bounds Check（边界检查）指令。一旦越界，程序立即停止，绝不含糊。</p>
<p><strong>代码实证：</strong></p>
<pre><code class="go">// https://go.dev/play/p/-CqDpIDr0BC
package main

import "fmt"

func main() {
    // 定义一个长度为 3 的切片
    s := []int{1, 2, 3}

    // 模拟一个动态索引（避免编译器在编译期直接报错）
    index := getIndex() 

    fmt.Println("尝试访问索引:", index)

    // 这里会触发 Runtime Panic
    // 错误信息明确：runtime error: index out of range [3] with length 3
    val := s[index] 

    fmt.Println("这行代码永远不会执行", val)
}

func getIndex() int {
    return 3
}
</code></pre>
<p>这种边界检查是在运行时（Runtime）介入，抛出 Panic，打印堆栈信息。因此会带来运行时性能损耗。虽然现代 Go 编译器引入了 BCA（边界检查消除）技术，但在无法静态分析的场景下，这就是必须缴纳的“安全税”。</p>
<h3>空指针的“硬着陆”</h3>
<p>在 C 语言中，解引用一个空指针是 UB。编译器有时会优化掉判空逻辑，因为它认为“既然你解引用了，那指针肯定不为空”，导致后续的安全检查失效。</p>
<p>Go 规定：<strong>解引用 nil 指针必须触发 Panic。</strong></p>
<p>这通常是通过 CPU 的硬件异常（SIGSEGV）来捕获的。Go 运行时会接管这个硬件信号，并将其转化为一个可恢复的 Go Panic，而不是让进程直接 Core Dump 或进入不可预测的僵死状态。</p>
<p><strong>代码实证：</strong></p>
<pre><code class="go">// https://go.dev/play/p/hlyZks1dGRf
package main

import "fmt"

type User struct {
    Name string
}

func main() {
    var u *User // u 默认为 nil

    fmt.Println("准备访问 nil 指针...")

    // 在 C 中这是 UB，可能导致程序崩溃或更糟的情况
    // 在 Go 中，这不仅会 Panic，还可以被 Recover 捕获
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("捕获到恐慌:", r)
            // 输出: runtime error: invalid memory address or nil pointer dereference
        }
    }()

    // 触发 Panic
    fmt.Println(u.Name)
}
</code></pre>
<p>综上，我们可知：在单线程维度，Go 确实几乎消灭了 Undefined Behavior。它通过强制规定行为（Wrapping, Panicking），将“未定义”变成了“定义明确的错误”。<strong>即使程序写错了，它的错误方式也是确定的，而非随机的。</strong></p>
<h2>房间里的大象——数据竞争</h2>
<p>如果文章到这里结束，那么 Go 就是一个完美的、绝对安全的语言。</p>
<p>但 Ian Lance Taylor 随后抛出了一个重磅炸弹：</p>
<blockquote>
<p><strong>“However, Go does have undefined behavior: if your program has a race condition, the behaviour is undefined.”</strong><br />
  <strong>（然而，Go 确实存在未定义行为：如果你的程序存在数据竞争，那么行为就是未定义的。）</strong></p>
</blockquote>
<p>这就是 Go 语言安全神话中最大的裂痕。</p>
<p>在 Rust 中，编译器借用检查器（Borrow Checker）会在编译期阻止数据竞争，因此 Rust 可以自豪地宣称“无数据竞争”。但 Go 选择了更简单的并发模型，允许 Goroutine 共享内存。</p>
<p>一旦发生数据竞争（Data Race），即多个 Goroutine 同时访问同一块内存且至少有一个是写操作，Go 就不再提供任何保证。</p>
<p><strong>为什么数据竞争是真正的 UB？</strong></p>
<p>很多 Gopher 认为数据竞争只是“读到了旧数据”或者“计数器少加了 1”。这是一种极其危险的误解。在多核 CPU 和现代编译器优化的加持下，数据竞争在 Go 中可能导致<strong>内存安全破坏</strong>。</p>
<p>这主要源于 Go 的<strong>多字数据结构（Multi-word Data Structures）</strong>。</p>
<h3>接口（Interface）的“撕裂”</h3>
<p>Go 的 interface 在底层是由两个机器字组成的：{type_ptr, data_ptr}。</p>
<ul>
<li>type_ptr 指向具体类型的元数据（如方法表）。</li>
<li>data_ptr 指向具体的数据值。</li>
</ul>
<p>假设我们有一个全局接口变量 var i interface{}，以及两个实现类型 type A 和 type B。</p>
<ul>
<li>Goroutine 1 试图将 i 赋值为 A{}。</li>
<li>Goroutine 2 试图将 i 赋值为 B{}。</li>
</ul>
<p>如果没有加锁，Goroutine 3 可能会读到一个“弗兰肯斯坦”般的怪物接口：<strong>它的 type_ptr 来自 A，但 data_ptr 却指向 B 的数据！</strong></p>
<p>当你调用这个接口的方法时，程序会尝试用 A 的方法表去操作 B 的内存布局。这会导致什么？</p>
<p>如果运气好，你会得到Panic（类型断言失败或非法内存访问）。</p>
<p>反之，如果运气不好，那远程代码执行（RCE）的攻击者可以精心构造内存布局，利用这种类型混淆（Type Confusion）来劫持控制流。</p>
<h3>切片（Slice）的“越界”</h3>
<p>切片由 {ptr, len, cap} 三个字组成。数据竞争可能导致你读到了新的 len（变得很大），但 ptr 还是旧的（指向一个小数组）。结果是你拥有了一个长度远超底层数组容量的切片，这让你能够读取甚至修改不属于该切片的任意内存——这正是 C 语言缓冲区溢出的翻版。</p>
<p><strong>这，就是 Go 中的 Undefined Behavior。</strong> 它不是“鼻恶魔”，但它是真实存在的安全黑洞。</p>
<h2>那些“未指明”的灰色地带</h2>
<p>除了致命的 UB，讨论中还涉及了 Go 语言规范中的另一种存在：<strong>未指明行为（Unspecified Behavior）</strong> 或 <strong>实现定义行为（Implementation-Defined Behavior）</strong>。</p>
<p>这些行为虽然不会导致内存破坏，但同样破坏了程序的“确定性”。</p>
<h3>Map 的迭代顺序</h3>
<p>在 Go 中，for k, v := range m 的顺序是<strong>故意</strong>未定义的。</p>
<p>Ian 解释说，这是为了防止开发者依赖某种特定的哈希实现顺序。Go 运行时甚至在每次迭代开始时引入了随机种子(迭代器会在map bucket 数组中随机选取一个起始位置向后遍历)，强制让顺序变得不可预测。</p>
<p>这是一个非常有智慧的设计：通过强制随机化，逼迫开发者编写不依赖顺序的健壮代码。</p>
<h3>表达式求值顺序：在“确定”与“未指明”之间</h3>
<p>在 C/C++ 中，f(g(), h()) 中 g() 和 h() 谁先执行是未定义的（Undefined Behavior 或 Unspecified Behavior），这取决于编译器实现。</p>
<p>Go 语言规范对此做了更严格的规定，但依然保留了一块微妙的“灰色地带”。</p>
<p><strong>确定的部分（Defined）：</strong></p>
<p>Go 规定，在求值表达式的操作数、赋值语句或返回语句时，所有的函数调用、方法调用和通信操作（Channel receive）都必须按照<strong>词法上从左到右</strong>的顺序执行。</p>
<p>例如，在赋值语句 y[f()], ok = g(h(), i()+x[j()], &lt;-c), k() 中，函数调用和通信的发生顺序被严格锁定为：</p>
<p>f() -> h() -> i() -> j() -> <-c -> g() -> k()。</p>
<p><strong>未指明的部分（Unspecified）：</strong></p>
<p>然而，规范同时也指出：<strong>并没有规定</strong>上述事件与表达式求值、索引操作、以及变量 y 的求值之间的顺序。</p>
<p>这意味着，虽然函数调用的相对顺序是固定的，但涉及副作用（Side Effects）的变量读写顺序可能是不确定的。来看 Spec 中的经典反例：</p>
<pre><code class="go">a := 1
f := func() int { a++; return a }

// x 可能是 [1, 2] 也可能是 [2, 2]
// 因为 a 的求值与 f() 的执行顺序未定义
x := []int{a, f()}
println(a, x)

// --- 示例：map 字面量中 key/value 的求值顺序未定义 ---
b := 1
g := func() int { b++; return b } // g() 会修改 b

// 若 b 先被求值：key=1, value=2  → m = {1: 2}
// 若 g() 先被执行：key=2, value=2 → m = {2: 2}
// Go 规范不保证 key 表达式与 value 表达式谁先求值
m2 := map[int]int{b: g()}
println(b, m2[b])
</code></pre>
<p>虽然 Go 比 C/C++ 确定得多，但在编写依赖于求值顺序的副作用代码（例如在参数列表中修改全局变量）时，依然可能会掉进“未指明行为”的陷阱。因此，最好不要在单行表达式中依赖复杂的副作用顺序。</p>
<h3>浮点数转换的幽灵</h3>
<p>讨论中有开发者 提到了 float64 转换为 uint8 的行为。在早期的 Go 版本中，对于溢出值的处理可能依赖于底层硬件指令（x86 vs ARM），从而表现出不一致。</p>
<p>虽然 Go 正在逐步收紧这些规范，例如 <a href="https://tonybai.com/2026/01/11/proposal-float-to-int-conversions-should-saturate-on-overflow/">#76264 提案</a>(尚未落地)正试图统一浮点转整数的饱和行为，但这提醒我们：即使是强类型语言，在跨平台移植时也可能遇到底层架构带来的“方言”差异。</p>
<h2>如何在充满 UB 的世界里生存？</h2>
<p>既然 Go 没有彻底消灭 UB，作为开发者，我们该如何自保？</p>
<h3>视 -race 为生命线</h3>
<p>Ian Lance Taylor 的警告应该被打印在每个 Go 开发者的工位上。</p>
<p><strong>建议</strong>：</p>
<ul>
<li>单元测试必须开启 -race 标志运行。</li>
<li>在 CI/CD 流水线中，竞态检测是不可跳过的阻断性步骤。</li>
<li>不要相信“我的并发逻辑很简单，不会出错”，人脑无法模拟现代 CPU 的乱序执行。</li>
</ul>
<h3>敬畏 unsafe</h3>
<p>Go 的 unsafe 包是通往 C 语言 UB 世界的后门。使用 unsafe.Pointer 进行类型转换时，你实际上是在对编译器说：“我知道我在做什么，出了事我负责。”</p>
<p>除非你是编写底层运行时或极致性能库的专家，否则在业务代码中绝对禁止使用 unsafe。一旦使用，你必须熟读《<a href="https://go.dev/ref/mem">Go 内存模型</a>》和《<a href="https://go.dev/doc/gc-guide">垃圾回收器写屏障规则</a>》。</p>
<h3>理解“实现定义”与“未定义”的区别</h3>
<ul>
<li>未定义（UB）：可能导致 Crash、数据损坏、安全漏洞（如数据竞争）。零容忍。</li>
<li>未指明/实现定义：不同版本或平台可能表现不同（如 Map 顺序）。不要依赖它。</li>
<li>已定义：Go 承诺的行为（如整数回绕）。可以依赖，但需知晓代价。</li>
</ul>
<h2>小结：完美的幻象与工程的现实</h2>
<p>通过这次“真相调查”，我们得出的结论可能有些令人沮丧，但也足够清醒：</p>
<p><strong>Go 语言并没有彻底消灭 Undefined Behavior。它只是通过牺牲一部分性能和增加运行时检查，将 UB 的“攻击范围”从 C/C++ 的“随处可见”缩小到了“并发数据竞争”和“不安全代码”这两个特定的领域。</strong></p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/go-language-eliminated-undefined-behavior-truth-investigation-2.png" alt="" /></p>
<p>这是一种极其成功的工程权衡。它让 Go 在保持高性能的同时，为 99% 的日常编码提供了坚实的安全保障。</p>
<p>然而，作为 Gopher，我们不能沉浸在“绝对安全”的幻象中。我们必须意识到，当我们敲下 go func() 的那一刻，当我们试图共享一个指针的那一刻，我们正行走在悬崖的边缘。</p>
<p>Go 给了我们围栏（定义明确的行为），但也给了我们梯子（并发与 Unsafe）。能否不跌入 UB 的深渊，最终取决于我们是否遵守工程的纪律。</p>
<p>资料链接：https://groups.google.com/g/golang-nuts/c/MB1QmhDd_Rk</p>
<hr />
<p><strong>你遇到过“鼻恶魔”吗？</strong></p>
<p>哪怕是 Go 这样严谨的语言，在并发面前也会露出锋利的牙齿。在你的开发生涯中，是否遇到过那种因为没开 -race 而在生产环境产生的“灵异事件”？你对 Go 这种“用性能换确定性”的哲学怎么看？</p>
<p>欢迎在评论区分享你的“探案”心得！</p>
<hr />
<p>还在为“复制粘贴喂AI”而烦恼？我的新专栏 <strong>《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》</strong> 将带你：</p>
<ul>
<li>告别低效，重塑开发范式</li>
<li>驾驭AI Agent(Claude Code)，实现工作流自动化</li>
<li>从“AI使用者”进化为规范驱动开发的“工作流指挥家”</li>
</ul>
<p>扫描下方二维码，开启你的AI原生开发之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/03/16/go-language-eliminated-undefined-behavior-truth-investigation/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go 1.19中值得关注的几个变化</title>
		<link>https://tonybai.com/2022/08/22/some-changes-in-go-1-19/</link>
		<comments>https://tonybai.com/2022/08/22/some-changes-in-go-1-19/#comments</comments>
		<pubDate>Mon, 22 Aug 2022 02:23:50 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[atomic]]></category>
		<category><![CDATA[ballast]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Cgo]]></category>
		<category><![CDATA[CGO_FLAGS]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[DataRaceFree]]></category>
		<category><![CDATA[DRF-SC]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[generics]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Go1]]></category>
		<category><![CDATA[go1.18]]></category>
		<category><![CDATA[go1.19]]></category>
		<category><![CDATA[Go1.5]]></category>
		<category><![CDATA[GOARCH]]></category>
		<category><![CDATA[godoc]]></category>
		<category><![CDATA[goenv]]></category>
		<category><![CDATA[GOGC]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GOMEMLIMIT]]></category>
		<category><![CDATA[GOOS]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[heap]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[LeslieLamport]]></category>
		<category><![CDATA[loong64]]></category>
		<category><![CDATA[memory-model]]></category>
		<category><![CDATA[OOM]]></category>
		<category><![CDATA[pacer]]></category>
		<category><![CDATA[paper]]></category>
		<category><![CDATA[runtime]]></category>
		<category><![CDATA[RussCox]]></category>
		<category><![CDATA[SC]]></category>
		<category><![CDATA[SetFinalizer]]></category>
		<category><![CDATA[SetGCPercent]]></category>
		<category><![CDATA[SetMemoryLimit]]></category>
		<category><![CDATA[sync]]></category>
		<category><![CDATA[TSO]]></category>
		<category><![CDATA[twitch]]></category>
		<category><![CDATA[共享内存]]></category>
		<category><![CDATA[内存模型]]></category>
		<category><![CDATA[原子操作]]></category>
		<category><![CDATA[同步]]></category>
		<category><![CDATA[同步原语]]></category>
		<category><![CDATA[垃圾回收]]></category>
		<category><![CDATA[并发]]></category>
		<category><![CDATA[泛型]]></category>
		<category><![CDATA[论文]]></category>
		<category><![CDATA[顺序一致性]]></category>

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

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

vs.

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

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

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

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

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

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

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

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

    // Application execution continues
    // ...

    runtime.Keepalive(ballast)
    // ... ...
}
</code></pre>
<p>这个切片由于太大，将在堆上分配并被runtime跟踪，但这个切片并不会给应用带去实质上的物理内存消耗，这得益于os对应用进程内存的<strong>延迟簿记</strong>：只有读写的内存才会导致缺页中断并由OS为之分配物理内存。从类似top的工具来看，这10个G的字节仅会记录在VIRT/VSZ(虚拟内存)上，而不会记录在RES/RSS(常驻内存)上。</p>
<p>这样一来，根据前面Pacer算法的原理，触发GC的下一个目标堆大小就至少为20G，在Go服务分配堆内存到20G之前GC都不会被触发，所有cpu资源都会被用来处理业务，这也与twitch的实测结果一致(GC次数下降99%)。</p>
<p>一旦到了20G，由于之前观测的结果是服务仅需400多M物理内存，大量heap object会被回收，Go服务的live heap会回到400多M，但重新计算目标堆内存时，由于前面那个“压舱石”的存在，目标堆内存已经会在至少20G的水位上，就这样GC次数少了，GC少了，worker goroutine参加“劳役”的时间就少了，cpu利用率高了，服务响应的延迟也下来了。</p>
<blockquote>
<p>注：“劳役”是指worker goroutine在mallocgc内存时被runtime强制“劳役”：停下自己手头的工作，去辅助GC做heap live object的mark。</p>
</blockquote>
<p>不过使用该方案的前提是你对你的Go服务的内存消耗情况(忙闲时)有着精确的了解，这样才能结合硬件资源情况设定合理的ballast值。</p>
<p>按照<a href="https://github.com/golang/proposal/blob/master/design/48409-soft-memory-limit.md">Soft memory limit proposal</a>的说法，该方案的弊端如下：</p>
<ul>
<li>不能跨平台移植，据说Windows上不适用(压舱石的值会直接反映为应用的物理内存占用)；</li>
<li>不能保证随着Go运行时的演进而继续正常工作（比如：一旦pacer算法发生了巨大变化）；</li>
<li>开发者需要进行复杂的计算并估计运行时内存开销以选择适合的ballast大小。</li>
</ul>
<p>接下来我们再来看看自动GC动态调优方案。</p>
<p>去年12月，uber在其官方博客分享了uber内部使用的<a href="https://www.uber.com/en-US/blog/how-we-saved-70k-cores-across-30-mission-critical-services/">半自动化Go GC调优方案</a>，按uber的说法，这种方案实施后帮助uber节省了70K cpu核的算力。其背后的原理依旧是从Pacer的算法公式出发，改变原先Go服务生命周期全程保持GOGC值静态不变的作法，在每次GC时，依据容器的内存限制以及当前的live heap size动态计算并设置GOGC值，从而实现对内存不足oom-killed的保护，同时最大程度利用内存，改善Gc对cpu的占用率。</p>
<p>显然这种方案更为复杂，需要有一个专家团队来保证这种自动调优的参数的设置与方案的实现。</p>
<h4>4. 引入Soft memory limit</h4>
<p>其实Go GC pacer的问题还有很多, Go核心团队开发者Michael Knyszek提了一个<a href="https://github.com/golang/go/issues/42430">pacer问题综述的issue</a>，将这些问题做了汇总。但问题还需一个一个解决，在Go 1.19这个版本中，Michael Knyszek就带来了他的<a href="https://github.com/golang/proposal/blob/master/design/48409-soft-memory-limit.md">Soft memory limit的解决方案</a>。</p>
<p>这个方案在runtime/debug包中添加了一个名为SetMemoryLimit的函数以及GOMEMLIMIT环境变量，通过他们任意一个都可以设定Go应用的Memory limit。</p>
<p>一旦设定了Memory limit，当Go堆大小达到“Memory limit减去非堆内存后的值”时，一轮GC会被触发。即便你手动关闭了GC(GOGC=off)，GC亦是会被触发。</p>
<p>通过原理我们可以看到，这个特性最直接解决的就是oom-killed这个问题！就像前面pacer问题示意图中的那个例子，如果我们设定了一个比hard memory limit小一些的soft memory limit的值，那么在(3)那个点便不会出现oom-killed，因为在那之前soft memory limit就会触发一次GC，将一些无用的堆内存回收掉了。</p>
<p>但我们也要注意：soft memory limit不保证不会出现oom-killed，这个也很好理解。如果live heap object到达limit了，说明你的应用内存资源真的不够了，是时候扩内存条资源了，这个是GC无论如何都无法解决的问题。</p>
<p>但如果一个Go应用的live heap object超过了soft memory limit但还尚未被kill，那么此时GC会被持续触发，但为了保证在这种情况下业务依然能继续进行，soft memory limit方案保证GC最多只会使用50%的CPU算力，以保证业务处理依然能够得到cpu资源。</p>
<p>对于GC触发频率高，要降低GC频率的情况，soft memory limit的方案就是<strong>关闭GC(GOGC=off)</strong>，这样GC只有当堆内存到达soft memory limit值时才会触发，可以提升cpu利用率。不过有一种情况，<a href="https://go.dev/doc/gc-guide">Go官方的GC guide</a>中不建议你这么做，那就是当你的Go程序与其他程序共享一些有限的内存时。这时只需保留内存限制并将其设置为一个较小的合理值即可，因为它可能有助于抑制不良的瞬时行为。</p>
<p>那么多大的值是合理的soft memory limit值呢？在Go服务独占容器资源时，一个好的经验法则是留下额外的5-10%的空间，以考虑Go运行时不知道的内存来源。uber在其博客中设定的limit为资源上限的70%，也是一个不错的经验值。</p>
<h3>四. 小结</h3>
<p>也许Go 1.19因开发周期的压缩给大家带来的惊喜并不多。不过特性虽少，却都很实用，比如上面的soft memory limit，一旦用好，便可以帮助大家解决大问题。</p>
<p>而拥有正常开发周期的Go 1.20已经处于积极的开发中，从目前<a href="https://github.com/golang/go/milestone/250">里程碑</a>中规划的功能和改进来看，Go泛型语法将得到进一步的补全，向着完整版迈进，就这一点就值得大家期待了！</p>
<h3>五. 参考资料</h3>
<ul>
<li>Russ Cox内存模型系列 &#8211; https://research.swtch.com/mm</li>
<li>关于Go内存模型的讨论 &#8211; https://github.com/golang/go/discussions/47141</li>
<li>How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programs- https://www.microsoft.com/en-us/research/publication/make-multiprocessor-computer-correctly-executes-multiprocess-programs</li>
<li>A Tutorial Introduction to the ARM and POWER Relaxed Memory Models- https://www.cl.cam.ac.uk/~pes20/ppc-supplemental/test7.pdf</li>
<li>Weak Ordering &#8211; A New Definition- https://people.eecs.berkeley.edu/~kubitron/courses/cs258-S08/handouts/papers/adve-isca90.pdf</li>
<li>Foundations of the C++ Concurrency Memory Model &#8211; https://www.hpl.hp.com/techreports/2008/HPL-2008-56.pdf</li>
<li>Go GC pacer原理 &#8211;  https://docs.google.com/document/d/1wmjrocXIWTr1JxU-3EQBI6BK6KgtiFArkG47XK73xIQ/edit</li>
</ul>
<hr />
<p><a href="https://wx.zsxq.com/dweb2/index/group/51284458844544">“Gopher部落”知识星球</a>旨在打造一个精品Go学习和进阶社群！高品质首发Go技术文章，“三天”首发阅读权，每年两期Go语言发展现状分析，每天提前1小时阅读到新鲜的Gopher日报，网课、技术专栏、图书内容前瞻，六小时内必答保证等满足你关于Go语言生态的所有需求！2022年，Gopher部落全面改版，将持续分享Go语言与Go应用领域的知识、技巧与实践，并增加诸多互动形式。欢迎大家加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/go-programming-from-beginner-to-master-qr.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /></p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/。smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。2020年4月8日，中国三大电信运营商联合发布《5G消息白皮书》，51短信平台也会全新升级到“51商用消息平台”，全面支持5G RCS消息。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻)归档仓库 &#8211; https://github.com/bigwhite/gopherdaily</p>
<p>我的联系方式：</p>
<ul>
<li>微博：https://weibo.com/bigwhite20xx</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2022, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2022/08/22/some-changes-in-go-1-19/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Go 1.19新特性前瞻</title>
		<link>https://tonybai.com/2022/06/12/go-1-19-foresight/</link>
		<comments>https://tonybai.com/2022/06/12/go-1-19-foresight/#comments</comments>
		<pubDate>Sun, 12 Jun 2022 09:06:20 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[atomic]]></category>
		<category><![CDATA[build-tag]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[EDNS]]></category>
		<category><![CDATA[fd]]></category>
		<category><![CDATA[flag]]></category>
		<category><![CDATA[Function]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[generics]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go1.18]]></category>
		<category><![CDATA[go1.19]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Gopher]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[loong64]]></category>
		<category><![CDATA[memory-model]]></category>
		<category><![CDATA[Method]]></category>
		<category><![CDATA[mmap]]></category>
		<category><![CDATA[race]]></category>
		<category><![CDATA[receiver]]></category>
		<category><![CDATA[RussCox]]></category>
		<category><![CDATA[SetMemoryLimit]]></category>
		<category><![CDATA[sync]]></category>
		<category><![CDATA[sync/atomic]]></category>
		<category><![CDATA[TextVar]]></category>
		<category><![CDATA[thread-sanitizer]]></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=3584</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2022/06/12/go-1-19-foresight 美国时间2022年5月7日，Go 1.19版本开发分支进入新特性冻结(freeze)阶段，即只能修Bug，不能再向Go 1.19版本中增加新特性了。由于上一个版本Go 1.18因引入泛型改动较大，推迟了一个月发布，这直接导致了Go 1.19版本的开发周期被缩短。 虽然开发周期少了近一个月，但Go 1.19版本仍然会按计划在2022年8月份发布。而Go 1.19的第一个beta版也于今天凌晨发布了。Go 1.19版本都有哪些重要变化呢，我通过这篇文章带大家先睹为快。 注1：版本特性变化以最终发布为准！ 注2：本文仅是前瞻，不会过于深入细节。细节待Go 1.19正式发布后再聊。 泛型问题的fix 尽管Go核心团队在Go 1.18泛型上投入了很多精力，但Go 1.18发布后泛型这块依然有已知的天生局限，以及后续逐渐发现的一些问题，而Go 1.19版本将继续打磨Go泛型，并重点fix Go 1.18中发现的泛型问题。目前Go 1.19开发版本中大约有5-6个泛型问题待解决。之前谈到的可能放开一些泛型约束，在Go 1.19估计不会如期兑现了。 不过可以确定的是Go 1.19将包含Go语法规范中的一处关于泛型的修正，即由下面表述： The scope of an identifier denoting a type parameter of a function or declared by a method receiver is the function body and all parameter lists of the function.(译文：一个用于表示函数的类型参数或由方法接收器声明的类型参数的标识符的作用域范围包括函数体和函数的所有形式参数列表。) [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/go-1-19-foresight-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2022/06/12/go-1-19-foresight">本文永久链接</a> &#8211; https://tonybai.com/2022/06/12/go-1-19-foresight</p>
<p>美国时间2022年5月7日，Go 1.19版本开发分支进入新特性冻结(freeze)阶段，即只能修Bug，不能再向Go 1.19版本中增加新特性了。由于上一个版本<a href="https://tonybai.com/2022/04/20/some-changes-in-go-1-18">Go 1.18</a>因引入泛型改动较大，推迟了一个月发布，这直接导致了Go 1.19版本的开发周期被缩短。</p>
<p>虽然开发周期少了近一个月，但Go 1.19版本仍然会按计划在2022年8月份发布。而<a href="https://github.com/golang/go/releases/tag/go1.19beta1">Go 1.19的第一个beta版</a>也于今天凌晨发布了。Go 1.19版本都有哪些重要变化呢，我通过这篇文章带大家先睹为快。</p>
<blockquote>
<p>注1：版本特性变化以最终发布为准！<br />
  注2：本文仅是前瞻，不会过于深入细节。细节待Go 1.19正式发布后再聊。</p>
</blockquote>
<h3>泛型问题的fix</h3>
<p>尽管Go核心团队在Go 1.18泛型上投入了很多精力，但Go 1.18发布后泛型这块依然有已知的天生局限，以及后续逐渐发现的一些问题，而Go 1.19版本将<a href="https://github.com/golang/go/issues?q=is%3Aopen+is%3Aissue+label%3Agenerics+milestone%3AGo1.19">继续打磨Go泛型</a>，并重点fix Go 1.18中发现的泛型问题。目前<a href="https://github.com/golang/go/issues?q=is%3Aissue+label%3Agenerics+milestone%3AGo1.19+is%3Aopen">Go 1.19开发版本中大约有5-6个泛型问题待解决</a>。之前谈到的可能放开一些泛型约束，在Go 1.19估计不会如期兑现了。</p>
<p>不过可以确定的是Go 1.19将包含<a href="https://github.com/golang/go/issues/52038">Go语法规范中的一处关于泛型的修正</a>，即由下面表述：</p>
<blockquote>
<p>The scope of an identifier denoting a type parameter of a function or declared by a method receiver is the function body and all parameter lists of the function.(译文：一个用于表示函数的类型参数或由方法接收器声明的类型参数的标识符的作用域范围包括函数体和函数的所有形式参数列表。)</p>
</blockquote>
<p>改为下面更新版的表述：</p>
<blockquote>
<p>The scope of an identifier denoting a type parameter of a function or declared by a method receiver starts after the function name and ends at the end of the function body.(译文：一个用于表示函数的类型参数或由方法接收器声明的类型参数的标识符的作用域始于函数名，终止于函数体末尾。)</p>
</blockquote>
<p>这样一个改动，使得原本在当前版本Go编译器(Go 1.18.x)下编译报错的源码，在Go 1.19版本中可以正常编译通过：</p>
<pre><code>type T[T any] struct {}
func (T[T]) m() {} // error: T is not a generic type
</code></pre>
<h3><a href="https://github.com/golang/go/issues/50859">修订Go memory model</a></h3>
<p><a href="https://go.dev/ref/mem">Go memory model</a>是Go文档中最抽象的一篇，没有之一！随着Go的演进，原先的Go memory model描述有很多地方不够正式，也缺少对一些同步机制的说明，如atomic等。</p>
<p>这次修订，参考了Hans-J. Boehm和Sarita V. Adve在<a href="https://www.hpl.hp.com/techreports/2008/HPL-2008-56.pdf">“Foundations of the C++ Concurrency Memory Model，(PLDI 2008)”</a>中对C++ memory model的描述方式，对Go memory model做了更正式的整体描述，增加了对multiword竞态、runtime.SetFinalizer、更多sync类型、atomic操作以及编译器优化方面的描述。</p>
<h3>修订go doc comment格式</h3>
<p>Go内置了将comment直接提取为包文档的能力，这与其他语言通过第三方工具生成文档不同。go doc comment为Gopher提供了很大便利。但go doc comment设计于2009年，有些过时。对很多呈现形式的支持不够或缺少更为精确的格式描述，这次Russ Cox主导了go doc comment的修订，<a href="https://github.com/golang/go/issues/51082">增加了对超链、列表、标题、标准库API引用等格式支持</a>，修订后的go doc comment并非markdown语法，但从markdown语法中做了借鉴，同时兼容老comment格式。下面是Russ Cox提供的一些新doc comment的渲染后的效果图：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-1-19-foresight-2.png" alt="" /><br />
<img src="https://tonybai.com/wp-content/uploads/go-1-19-foresight-3.png" alt="" /><br />
<img src="https://tonybai.com/wp-content/uploads/go-1-19-foresight-4.png" alt="" /></p>
<p>同时，Go团队还提供了go/doc/comment包，gopher使用它可以轻松解析go doc comment。</p>
<h3>runtime.SetMemoryLimit</h3>
<p>在Go 1.19中，一个新的runtime.SetMemoryLimit函数以及一个GOMEMLIMIT环境变量被引入。有了这个memory软限制，Go运行时将通过限制堆的大小，以及更积极地将内存返回给底层os，来试图维持这个内存限制，以尽量避免Go程序因分配heap过多，超出系统内存资源限制而被kill。</p>
<p>默认memory limit是math.MaxInt64。一旦通过SetMemoryLimit自行设定limit，那么Go运行时将尊重这个memory limit，通过调整GC回收频率以及及时将内存返还给os来保证go运行时掌控的内存总size在limit之下。</p>
<p>注意：limit限制的是go runtime掌控的内存总量，对于开发者自行从os申请的内存(比如通过mmap)则不予考虑。limit的具体措施细节可以参考<a href="https://github.com/golang/proposal/blob/master/design/48409-soft-memory-limit.md">该proposal design文档</a>。</p>
<p>另外要注意的是：该limit不能100%消除out-of-memory的情况。</p>
<h3>Go 1.19在启动时将默认提高打开文件的限值</h3>
<p>经调查，一些系统对打开的文件数量设置了一个人为的soft限制, 主要是为了与使用select和其硬编码的最大文件描述符（由 fd_set 的大小限制）的代码兼容。通常限制为1024，有的更小，比如256。这样即便是gofmt这样的简单程序，当它们并行地遍历一个文件树时，也很容易遇到打开文件描述符超量的错误。</p>
<p>Go不使用select，所以它不应该受这些限制的影响。于是对于导入os包的go程序，<a href="https://github.com/golang/go/issues/46279">Go将在1.19中默认提高这些限制值</a>到hard limit。</p>
<h3>Go 1.19 race detector将升级到v3版thread sanitizer</h3>
<p>升级后的<a href="https://github.com/golang/go/issues/49761">新版race detector</a>的race检测性能相对于上一版将提升1.5倍-2倍，内存开销减半，并且没有对goroutine的数量的上限限制。</p>
<blockquote>
<p>注：thread sanitizer检测数据竞态的工作原理：记录每一个内存访问的信息，并检测线程对这块内存的访问是否存在竞争。基于这种原理，我们也可以知道一旦开启race detect，Go程序的执行效率将受到很大影响，运行的开销将大幅增加。v3版thread sanitizer虽然得到了优化，但对程序的总体影响还是存在的并且依旧很大。</p>
</blockquote>
<h3>Go 1.19增加”unix” build tag</h3>
<p>Go 1.19将增加”unix”构建标签：</p>
<pre><code>//go:build unix
</code></pre>
<p>等价于</p>
<pre><code>//go:build aix || linux || darwin || dragonfly || freebsd || openbsd || netbsd || solaris
</code></pre>
<p>不过要注意，”*_unix.go”还保留原语义，不能被识别，以便向后兼容现有文件，<a href="https://github.com/golang/go/issues/20322#issuecomment-1057682650">尤其是go标准库之外的使用</a>。</p>
<h3>标准库的一些变化</h3>
<h4>net软件包将使用EDNS</h4>
<p>在Go 1.19中，<a href="https://github.com/golang/go/issues/51153">net软件包将使用EDNS来增加DNS数据包的大小</a>，以遵守现代DNS标准和实现。这应该有助于解决一些DNS服务器的问题。</p>
<h4>flag包增加TextVar函数</h4>
<p><a href="https://github.com/golang/go/issues/45754">Go flag包增加TextVar函数</a>，这样flag包便可以与任何实现了encoding.Text{Marshaler,Unmarshaler}的Go类型集成。比如：</p>
<pre><code>flag.TextVar(&amp;ipaddr, "ipaddr", net.IPv4(192, 168, 0, 1), "what server to connect to?") // 与net.IPv4类型
flag.TextVar(&amp;start, "start", time.Now(), "when should we start processing?") // 与time.Time类型
</code></pre>
<h3>其它</h3>
<ul>
<li>在linux上，Go正式支持64位龙芯cpu架构 (GOOS=linux, GOARCH=loong64)。</li>
<li>当Go程序空闲时，Go GC进入到周期性的GC循环的情况下(2分钟一次)，Go运行时现在会在idle的操作系统线程上安排更少的GC worker goroutine，减少空闲时Go应用对os资源的占用。</li>
<li>Go行时将根据goroutine的历史平均栈使用率来分配初始goroutine栈，避免了一些goroutine的最多2倍的goroutine栈空间浪费。</li>
<li>sync/atomic包增加了新的高级原子类型Bool, Int32, Int64, Uint32, Uint64, Uintptr和Pointer，提升了使用体验。</li>
<li>Go 1.19中Go编译器使用jump table重新实现了针对大整型数和string类型的switch语句，平均性能提升20%左右。</li>
</ul>
<h2>小结</h2>
<p>相对于Go 1.18，Go 1.19的确是一个“小版本”。但Go 1.19对memory model的更新、SetMemoryLimit的加入、go doc comment的修订以及对go runtime的持续打磨依然可以让gopher们产生一丝丝“小兴奋”，<strong>尤其是SetMemoryLimit的加入，是否能改善Go应用因GC不及时被kill的情况呢</strong>，让我们拭目以待。</p>
<p>Go 1.19的里程碑在<a href="https://github.com/golang/go/milestone/224">这里</a>，所有feature和fix大家可以在该里程碑中看到。</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>微信公众号：iamtonybai</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>“Gopher部落”知识星球：https://public.zsxq.com/groups/51284458844544</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2022, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2022/06/12/go-1-19-foresight/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Golang Channel用法简编</title>
		<link>https://tonybai.com/2014/09/29/a-channel-compendium-for-golang/</link>
		<comments>https://tonybai.com/2014/09/29/a-channel-compendium-for-golang/#comments</comments>
		<pubDate>Mon, 29 Sep 2014 09:02:43 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Actor]]></category>
		<category><![CDATA[BestPractice]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[Concurrent]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[CSP]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[GopherCon]]></category>
		<category><![CDATA[LXC]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[TIOBE]]></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">http://tonybai.com/?p=1551</guid>
		<description><![CDATA[在进入正式内容前，我这里先顺便转发一则消息，那就是Golang 1.3.2已经正式发布了。国内的golangtc已经镜像了golang.org的安装包下载页面，国内go程序员与爱好者们可以到&#34;Golang中 国&#34;，即golangtc.com去下载go 1.3.2版本。 Go这门语言也许你还不甚了解，甚至是完全不知道，这也有情可原，毕竟Go在TIOBE编程语言排行榜上位列30开外。但近期使用Golang 实现的一杀手级应用 Docker你却不该不知道。docker目前火得是一塌糊涂啊。你去国内外各大技术站点用眼轻瞥一下，如 果没有涉及到&#8220;docker&#8221;字样新闻的站点建 议你以后就不要再去访问了^_^。Docker是啥、怎么用以及基础实践可以参加国内一位仁兄的经验之作：《 Docker &#8211; 从入门到实践》。 据我了解，目前国内试水Go语言开发后台系统的大公司与初创公司日益增多，比如七牛、京东、小米，盛大，金山，东软，搜狗等，在这里我们可以看到一些公司的Go语言应用列表，并且目前这个列表似乎依旧在丰富中。国内Go语言的推广与布道也再稳步推进中，不过目前来看多以Go入 门与基础为主题，Go idioms、tips或Best Practice的Share并不多见，想必国内的先行者、布道师们还在韬光养晦，积攒经验，等到时机来临再厚积薄发。另外国内似乎还没有一个针对Go的 布道平台，比如Golang技术大会之类的的平台。 在国外，虽然Go也刚刚起步，但在Golang share的广度和深度方面显然更进一步。Go的国际会议目前还不多，除了Golang老东家Google在自己的各种大会上留给Golang展示自己的 机会外，由 Gopher Academy 发起的GopherCon 会议也于今年第一次举行，并放出诸多高质量资料，在这里可以下载。欧洲的Go语言大会.dotgo也即将开幕，估计后续这两个大会将撑起Golang技术分享 的旗帜。 言归正传，这里要写的东西并非原创，自己的Go仅仅算是入门级别，工程经验、Best Practice等还谈不上有多少，因此这里主要是针对GopherCon2014上的&#8220;舶来品&#8221;的学习心得。来自CloudFlare的工程师John Graham-Cumming谈了关于 Channel的实践经验，这里针对其分享的内容，记录一些学习体会和理解，并结合一些外延知识，也可以算是一种学习笔记吧，仅供参考。 一、Golang并发基础理论 Golang在并发设计方面参考了C.A.R Hoare的CSP，即Communicating Sequential Processes并发模型理论。但就像John Graham-Cumming所说的那样，多数Golang程序员或爱好者仅仅停留在&#8220;知道&#8221;这一层次，理解CSP理论的并不多，毕竟多数程序员是搞工程 的。不过要想系统学习CSP的人可以从这里下载到CSP论文的最新版本。 维基百科中概要罗列了CSP模型与另外一种并发模型Actor模型的区别： Actor模型广义上讲与CSP模型很相似。但两种模型就提供的原语而言，又有一些根本上的不同之处： &#160;&#160;&#160; &#8211; CSP模型处理过程是匿名的，而Actor模型中的Actor则具有身份标识。 &#160;&#160;&#160; &#8211; CSP模型的消息传递在收发消息进程间包含了一个交会点，即发送方只能在接收方准备好接收消息时才能发送消息。相反，actor模型中的消息传递是异步 的，即消息的发送和接收无需在同一时间进行，发送方可以在接收方准备好接收消息前将消息发送出去。这两种方案可以认为是彼此对偶的。在某种意义下，基于交 会点的系统可以通过构造带缓冲的通信的方式来模拟异步消息系统。而异步系统可以通过构造带消息/应答协议的方式来同步发送方和接收方来模拟交会点似的通信 方式。 &#160;&#160;&#160; &#8211; CSP使用显式的Channel用于消息传递，而Actor模型则将消息发送给命名的目的Actor。这两种方法可以被认为是对偶的。某种意义下，进程可 以从一个实际上拥有身份标识的channel接收消息，而通过将actors构造成类Channel的行为模式也可以打破actors之间的名字耦合。 二、Go Channel基本操作语法 Go Channel的基本操作语法如下： [...]]]></description>
			<content:encoded><![CDATA[<p>在进入正式内容前，我这里先顺便转发一则消息，那就是Golang 1.3.2已经正式发布了。国内的<a href="http://golangtc.com">golangtc</a>已经镜像了golang.org的安装包下载页面，国内go程序员与爱好者们可以到&quot;Golang中 国&quot;，即golangtc.com去下载go 1.3.2版本。</p>
<p><a href="http://golang.org">Go</a>这门语言也许你还不甚了解，甚至是完全不知道，这也有情可原，毕竟Go在<a href="http://www.tiobe.com">TIOBE</a>编程语言排行榜上位列30开外。但近期使用Golang 实现的一杀手级应用<a href="http://docker.com"> <b>Docker</b></a>你却不该不知道。docker目前火得是一塌糊涂啊。你去国内外各大技术站点用眼轻瞥一下，如 果没有涉及到&ldquo;docker&rdquo;字样新闻的站点建 议你以后就不要再去访问了^_^。Docker是啥、怎么用以及基础实践可以参加国内一位仁兄的经验之作：《 <a href="https://www.gitbook.io/book/yeasy/docker_practice">Docker &#8211; 从入门到实践</a>》。</p>
<p>据我了解，目前国内试水Go语言开发后台系统的大公司与初创公司日益增多，比如七牛、京东、小米，盛大，金山，东软，搜狗等，在<a href="https://github.com/qiniu/go/issues/15#issuecomment-55568731">这里</a>我们可以看到一些公司的Go语言应用列表，并且目前这个列表似乎依旧在丰富中。国内Go语言的推广与布道也再稳步推进中，不过目前来看多以Go入 门与基础为主题，Go idioms、tips或Best Practice的Share并不多见，想必国内的先行者、布道师们还在韬光养晦，积攒经验，等到时机来临再厚积薄发。另外国内似乎还没有一个针对Go的 布道平台，比如Golang技术大会之类的的平台。</p>
<p>在国外，虽然Go也刚刚起步，但在Golang share的广度和深度方面显然更进一步。Go的国际会议目前还不多，除了Golang老东家Google在自己的各种大会上留给Golang展示自己的 机会外，由 <span style="font-family: Ubuntu, Tahoma, sans-serif; font-size: 14px; line-height: 20px;"><a href="http://gopheracademy.com/">Gopher Academy</a> 发起的</span><a href="http://www.gophercon.com">GopherCon</a> 会议也于今年第一次举行，并放出诸多高质量资料，在<a href="https://github.com/gophercon/2014-talks">这里</a>可以下载。欧洲的Go语言大会<a href="http://www.dotgo.eu/">.dotgo</a>也即将开幕，估计后续这两个大会将撑起Golang技术分享 的旗帜。</p>
<p>言归正传，这里要写的东西并非原创，自己的Go仅仅算是入门级别，工程经验、Best Practice等还谈不上有多少，因此这里主要是针对GopherCon2014上的&ldquo;舶来品&rdquo;的学习心得。来自CloudFlare的工程师John Graham-Cumming谈了关于 Channel的实践经验，这里针对其分享的内容，记录一些学习体会和理解，并结合一些外延知识，也可以算是一种学习笔记吧，仅供参考。</p>
<p><b>一、Golang并发基础理论</b></p>
<p>Golang在并发设计方面参考了C.A.R Hoare的CSP，即Communicating Sequential Processes并发模型理论。但就像John Graham-Cumming所说的那样，多数Golang程序员或爱好者仅仅停留在&ldquo;知道&rdquo;这一层次，理解CSP理论的并不多，毕竟多数程序员是搞工程 的。不过要想系统学习CSP的人可以从<a href="http://www.usingcsp.com">这里</a>下载到CSP论文的最新版本。</p>
<p><a href="http://en.wikipedia.org/wiki/Communicating_sequential_processes">维基百科</a>中概要罗列了CSP模型与另外一种并发模型Actor模型的区别：</p>
<p>Actor模型广义上讲与CSP模型很相似。但两种模型就提供的原语而言，又有一些根本上的不同之处：<br />
	&nbsp;&nbsp;&nbsp; &#8211; CSP模型处理过程是匿名的，而Actor模型中的Actor则具有身份标识。<br />
	&nbsp;&nbsp;&nbsp; &#8211; CSP模型的消息传递在收发消息进程间包含了一个交会点，即发送方只能在接收方准备好接收消息时才能发送消息。相反，actor模型中的消息传递是异步 的，即消息的发送和接收无需在同一时间进行，发送方可以在接收方准备好接收消息前将消息发送出去。这两种方案可以认为是彼此对偶的。在某种意义下，基于交 会点的系统可以通过构造带缓冲的通信的方式来模拟异步消息系统。而异步系统可以通过构造带消息/应答协议的方式来同步发送方和接收方来模拟交会点似的通信 方式。<br />
	&nbsp;&nbsp;&nbsp; &#8211; CSP使用显式的Channel用于消息传递，而Actor模型则将消息发送给命名的目的Actor。这两种方法可以被认为是对偶的。某种意义下，进程可 以从一个实际上拥有身份标识的channel接收消息，而通过将actors构造成类Channel的行为模式也可以打破actors之间的名字耦合。</p>
<p><b>二、Go Channel基本操作语法</b></p>
<p>Go Channel的基本操作语法如下：</p>
<p><font face="Courier New">c := make(chan bool) //创建一个无缓冲的bool型Channel <br />
	c &lt;- x&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //向一个Channel发送一个值<br />
	&lt;- c&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //从一个Channel中接收一个值<br />
	x = &lt;- c&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //从Channel c接收一个值并将其存储到x中<br />
	x, ok = &lt;- c&nbsp; //从Channel接收一个值，如果channel关闭了或没有数据，那么ok将被置为false</font></p>
<p><i>不带缓冲的Channel</i>兼具通信和同步两种特性，颇受青睐。</p>
<p><b>三、Channel用作信号(Signal)的场景</b></p>
<p>1、等待一个事件(Event)</p>
<p><font face="Courier New">等待一个事件，有时候通过close一个Channel就足够了。例如：</font></p>
<p><font face="Courier New">//testwaitevent1.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Begin doing something!&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c := make(chan bool)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Doing something&#8230;&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <i>close(c)</i><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <i>&lt;-c</i><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Done!&quot;)<br />
	}</font></p>
<p>这里main goroutine通过&quot;<font face="Courier New">&lt;-c</font>&quot;来等待sub goroutine中的&ldquo;完成事件&rdquo;，sub goroutine通过close channel促发这一事件。当然也可以通过向Channel写入一个bool值的方式来作为事件通知。main goroutine在channel c上没有任何数据可读的情况下会阻塞等待。</p>
<p>关于输出结果：</p>
<p>根据《<a href="http://golang.org/ref/mem">Go memory model</a>》中关于close channel与recv from channel的order的定义：<span style="color: rgb(34, 34, 34); font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-style: italic; line-height: normal;"><font face="Courier New">The closing of a channel happens before a receive that returns a zero value because the channel is closed.</font></span></p>
<p>我们可以很容易判断出上面程序的输出结果：</p>
<p><font face="Courier New">Begin doing something!<br />
	Doing something&#8230;<br />
	Done!</font></p>
<p>如果将<font face="Courier New">close(c)</font>换成<font face="Courier New">c&lt;-true</font>，则根据《Go memory model》中的定义：<font face="Courier New"><span style="color: rgb(34, 34, 34); font-size: 16px; font-style: italic; line-height: normal;">A receive from an unbuffered channel happens before the send on that channel completes.</span></font><br />
	&quot;<font face="Courier New">&lt;-c</font>&quot;要先于&quot;<font face="Courier New">c&lt;-true</font>&quot;完成，但也不影响日志的输出顺序，输出结果仍为上面三行。</p>
<p>2、协同多个Goroutines</p>
<p>同上，close channel还可以用于协同多个Goroutines，比如下面这个例子，我们创建了100个Worker Goroutine，这些Goroutine在被创建出来后都阻塞在&quot;<font face="Courier New">&lt;-start&quot;</font>上，直到我们在main goroutine中给出<u>开工</u>的信号：&quot;<font face="Courier New">close(start)&quot;</font>，这些goroutines才开始真正的并发运行起来。</p>
<p><font face="Courier New">//testwaitevent2.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func worker(start chan bool, index int) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;-start<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;This is Worker:&quot;, index)<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; start := make(chan bool)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for i := 1; i &lt;= 100; i++ {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go worker(start, i)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(start)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {} //deadlock we expected<br />
	}</font></p>
<p>3、Select</p>
<p>【select的基本操作】<br />
	select是Go语言特有的操作，使用select我们可以同时在多个channel上进行发送/接收操作。下面是select的基本操作。</p>
<p><font face="Courier New">select {<br />
	case x := &lt;- somechan:<br />
	&nbsp;&nbsp;&nbsp; // &#8230; 使用x进行一些操作</font></p>
<p><font face="Courier New">case y, ok := &lt;- someOtherchan:<br />
	&nbsp;&nbsp;&nbsp; // &#8230; 使用y进行一些操作，<br />
	&nbsp;&nbsp;&nbsp; // </font><font face="Courier New"><font face="Courier New">检查ok值判断someOtherchan是否已经关闭</font></font></p>
<p><font face="Courier New">case outputChan &lt;- z:<br />
	&nbsp;&nbsp;&nbsp; // &#8230; z值被成功发送到Channel上时</font></p>
<p><font face="Courier New">default:<br />
	&nbsp;&nbsp;&nbsp; // &#8230; 上面case均无法通信时，执行此分支<br />
	}</font></p>
<p>【惯用法：for/select】</p>
<p>我们在使用select时很少只是对其进行一次evaluation，我们常常将其与for {}结合在一起使用，并选择适当时机从for{}中退出。</p>
<p><font face="Courier New">for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x := &lt;- somechan:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; 使用x进行一些操作</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case y, ok := &lt;- someOtherchan:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; 使用y进行一些操作，<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 检查ok值判断someOtherchan是否已经关闭</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case outputChan &lt;- z:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; z值被成功发送到Channel上时</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; default:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; 上面case均无法通信时，执行此分支<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	} </font></p>
<p>【终结workers】</p>
<p>下面是一个常见的终结sub worker goroutines的方法，每个worker goroutine通过select监视一个die channel来及时获取main goroutine的退出通知。</p>
<p><font face="Courier New">//testterminateworker1.go</font><br />
	<font face="Courier New">package main</font></p>
<p><font face="Courier New">import (<br />
	&nbsp;&nbsp;&nbsp; &quot;fmt&quot;<br />
	&nbsp;&nbsp;&nbsp; &quot;time&quot;<br />
	)</font></p>
<p><font face="Courier New">func worker(die chan bool, index int) {<br />
	&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Begin: This is Worker:&quot;, index)<br />
	&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //case xx：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //做事的分支<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case &lt;-die:<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; fmt.Println(&quot;Done: This is Worker:&quot;, index)<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp; die := make(chan bool)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; for i := 1; i &lt;= 100; i++ {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; go worker(die, i)<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; time.Sleep(time.Second * 5)<br />
	&nbsp;&nbsp;&nbsp; close(die)<br />
	&nbsp;&nbsp;&nbsp; select {} </font><font face="Courier New"><font face="Courier New">//deadlock we expected</font><br />
	}</font></p>
<p>【终结验证】</p>
<p>有时候终结一个worker后，main goroutine想确认worker routine是否真正退出了，可采用下面这种方法：</p>
<p><font face="Courier New">//testterminateworker2.go<br />
	package main</font></p>
<p><font face="Courier New">import (<br />
	&nbsp;&nbsp;&nbsp; &quot;fmt&quot;<br />
	&nbsp;&nbsp;&nbsp; //&quot;time&quot;<br />
	)</font></p>
<p><font face="Courier New">func worker(die chan bool) {<br />
	&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Begin: This is Worker&quot;)<br />
	&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //case xx：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //做事的分支<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case &lt;-die:<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; fmt.Println(&quot;Done: This is Worker&quot;)<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; die &lt;- true<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp; die := make(chan bool)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; go worker(die)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; die &lt;- true<br />
	&nbsp;&nbsp;&nbsp; &lt;-die<br />
	&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Worker goroutine has been terminated&quot;)<br />
	}</font></p>
<p>【关闭的Channel永远不会阻塞】</p>
<p>下面演示在一个已经关闭了的channel上读写的结果：</p>
<p><font face="Courier New">//testoperateonclosedchannel.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cb := make(chan bool)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(cb)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; x := &lt;-cb<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%#v\n&quot;, x)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; x, ok := &lt;-cb<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%#v %#v\n&quot;, x, ok)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ci := make(chan int)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(ci)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; y := &lt;-ci<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%#v\n&quot;, y)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cb &lt;- true<br />
	}</font></p>
<p><font face="Courier New">$go run </font><font face="Courier New"><font face="Courier New">testoperateonclosedchannel.go</font><br />
	false<br />
	false false<br />
	0<br />
	panic: runtime error: send on closed channel</font></p>
<p>可以看到在一个已经close的unbuffered channel上执行读操作，回返回channel对应类型的零值，比如bool型channel返回false，int型channel返回0。但向close的channel写则会触发panic。不过无论读写都不会导致阻塞。</p>
<p>【关闭带缓存的channel】</p>
<p>将unbuffered channel换成buffered channel会怎样？我们看下面例子：</p>
<p><font face="Courier New">//testclosedbufferedchannel.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c := make(chan int, 3)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 15<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 34<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 65<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%d\n&quot;, &lt;-c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%d\n&quot;, &lt;-c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%d\n&quot;, &lt;-c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%d\n&quot;, &lt;-c)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 1<br />
	}</font></p>
<p><font face="Courier New">$go run testclosedbufferedchannel.go<br />
	15<br />
	34<br />
	65<br />
	0<br />
	panic: runtime error: send on closed channel</font></p>
<p>可以看出带缓冲的channel略有不同。尽管已经close了，但我们依旧可以从中读出关闭前写入的3个值。第四次读取时，则会返回该channel类型的零值。向这类channel写入操作也会触发panic。</p>
<p>【range】</p>
<p>Golang中的range常常和channel并肩作战，它被用来从channel中读取所有值。下面是一个简单的实例：</p>
<p><font face="Courier New">//testrange.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func generator(strings chan string) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings &lt;- &quot;Five hour&#39;s New York jet lag&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings &lt;- &quot;and Cayce Pollard wakes in Camden Town&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings &lt;- &quot;to the dire and ever-decreasing circles&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings &lt;- &quot;of disrupted circadian rhythm.&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(strings)<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings := make(chan string)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go generator(strings)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for s := range strings {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%s\n&quot;, s)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;\n&quot;)<br />
	}</font></p>
<p><b>四、隐藏状态</b></p>
<p>下面通过一个例子来演示一下channel如何用来隐藏状态：</p>
<p>1、例子：唯一的ID服务</p>
<p><font face="Courier New">//testuniqueid.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func newUniqueIDService() &lt;-chan string {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; id := make(chan string)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var counter int64 = 0<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; id &lt;- fmt.Sprintf(&quot;%x&quot;, counter)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; counter += 1<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return id<br />
	}<br />
	func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; id := newUniqueIDService()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for i := 0; i &lt; 10; i++ {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&lt;-id)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">$ go run testuniqueid.go<br />
	0<br />
	1<br />
	2<br />
	3<br />
	4<br />
	5<br />
	6<br />
	7<br />
	8<br />
	9</font></p>
<p>newUniqueIDService通过一个channel与main goroutine关联，main goroutine无需知道uniqueid实现的细节以及当前状态，只需通过channel获得最新id即可。</p>
<p><b>五、默认情况</b></p>
<p>我想这里John Graham-Cumming主要是想告诉我们select的default分支的实践用法。</p>
<p>1、select&nbsp; for non-blocking receive</p>
<p><font face="Courier New">idle:= make(chan []byte, 5) //用一个带缓冲的channel构造一个简单的队列</font></p>
<p><font face="Courier New">select {<br />
	case b = &lt;-idle:  //尝试从idle队列中读取<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	default:&nbsp; //队列空，分配一个新的buffer<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; makes += 1<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = make([]byte, size)<br />
	}</font></p>
<p>2、select for non-blocking send</p>
<p><font face="Courier New">idle:= make(chan []byte, 5) </font><font face="Courier New"><font face="Courier New">//用一个带缓冲的channel构造一个简单的队列</font></font></p>
<p><font face="Courier New">select {<br />
	case idle &lt;- b: //尝试向队列中插入一个buffer<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&#8230;<br />
	default: //队列满？</font></p>
<p><font face="Courier New">}</font></p>
<p><b>六、Nil Channels</b></p>
<p>1、nil channels阻塞</p>
<p>对一个没有初始化的channel进行读写操作都将发生阻塞，例子如下：</p>
<p><font face="Courier New">package main</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var c chan int<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;-c<br />
	}</font></p>
<p><font face="Courier New">$go run testnilchannel.go<br />
	fatal error: all goroutines are asleep &#8211; deadlock!</font></p>
<p><font face="Courier New">package main</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var c chan int<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 1<br />
	}</font></p>
<p><font face="Courier New">$go run testnilchannel.go<br />
	fatal error: all goroutines are asleep &#8211; deadlock!</font></p>
<p>2、nil channel在select中很有用</p>
<p>看下面这个例子：</p>
<p><font face="Courier New">//testnilchannel_bad.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;<br />
	import &quot;time&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var c1, c2 chan int = make(chan int), make(chan int)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Second * 5)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c1 &lt;- 5<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Second * 7)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c2 &lt;- 7<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c2)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x := &lt;-c1:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(x)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x := &lt;-c2:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(x)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;over&quot;)<br />
	}</font></p>
<p>我们原本期望程序交替输出5和7两个数字，但实际的输出结果却是：</p>
<p><font face="Courier New">5<br />
	0<br />
	0<br />
	0<br />
	&#8230; &#8230; 0死循环</font></p>
<p>再仔细分析代码，原来select每次按case顺序evaluate：<br />
	&nbsp;&nbsp;&nbsp; &#8211; 前5s，select一直阻塞；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 第5s，c1返回一个5后被close了，&ldquo;case x := &lt;-c1&rdquo;这个分支返回，select输出5，并重新select<br />
	&nbsp;&nbsp;&nbsp; &#8211; 下一轮select又从&ldquo;case x := &lt;-c1&rdquo;这个分支开始evaluate，由于c1被close，按照前面的知识，close的channel不会阻塞，我们会读出这个 channel对应类型的零值，这里就是0；select再次输出0；这时即便c2有值返回，程序也不会走到c2这个分支<br />
	&nbsp;&nbsp;&nbsp; &#8211; 依次类推，程序无限循环的输出0</p>
<p>我们利用nil channel来改进这个程序，以实现我们的意图，代码如下：</p>
<p><font face="Courier New">//testnilchannel.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;<br />
	import &quot;time&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var c1, c2 chan int = make(chan int), make(chan int)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Second * 5)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c1 &lt;- 5<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Second * 7)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c2 &lt;- 7<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c2)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x, ok := &lt;-c1:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if !ok {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c1 = nil<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(x)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x, ok := &lt;-c2:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if !ok {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c2 = nil<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(x)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if c1 == nil &amp;&amp; c2 == nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;over&quot;)<br />
	}</font></p>
<p><font face="Courier New">$go run testnilchannel.go<br />
	5<br />
	7<br />
	over</font></p>
<p>可以看出：通过将已经关闭的channel置为nil，下次select将会阻塞在该channel上，使得select继续下面的分支evaluation。</p>
<p><b>七、Timers</b></p>
<p>1、超时机制Timeout</p>
<p>带超时机制的select是常规的tip，下面是示例代码，实现30s的超时select：</p>
<p><font face="Courier New">func worker(start chan bool) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; timeout := time.After(30 * time.Second)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; do some stuff<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case &lt;- timeout:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	} </font></p>
<p>2、心跳HeartBeart</p>
<p>与timeout实现类似，下面是一个简单的心跳select实现：</p>
<p><font face="Courier New">func worker(start chan bool) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; heartbeat := time.Tick(30 * time.Second)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; do some stuff<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case &lt;- heartbeat:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&#8230; do heartbeat stuff<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	} </font></p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/09/29/a-channel-compendium-for-golang/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
	</channel>
</rss>
