You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

927 lines
94 KiB
HTML

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE html>
<html lang="zh-cn" dir="ltr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="延迟执行 - defer语法 # defer 语法支持是Go 语言中一大特性,通过 defer 关键字,我们可以声明一个延迟执行函数,当调用者返回之前开始执行该函数,一般用来完成资源、锁、连接等释放工作,或者 recover 可能发生的panic。
三大特性 # defer延迟执行语法有三大特性
defer函数的传入参数在定义时就已经明确 # func main() { i := 1 defer fmt.Println(i) i&#43;&#43; return } 上面代码输出1而不是2。
defer函数是按照后进先出的顺序执行 # func main() { for i := 1; i &lt;= 5; i&#43;&#43; { defer fmt.Print(i) } } 上面代码输出54321而不是12345。
defer函数可以读取和修改函数的命名返回值 # func main() { fmt.Println(test()) } func test() (i int) { defer func() { i&#43;&#43; }() return 100 } 上面代码输出输出101而不是100或者1。
白话defer原理 # defer函数底层数据结构是_defer结构体多个defer函数会构建成一个_defer链表后面加入的defer函数会插入链表的头部该链表链表头部会链接到G上。当函数执行完成返回的时候会从_defer链表头部开始依次执行defer函数。这也就是defer函数执行时会LIFO的原因。_defer链接结构示意图如下">
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#343a40">
<meta name="color-scheme" content="light dark"><meta property="og:title" content="延迟执行 - defer语法" />
<meta property="og:description" content="延迟执行 - defer语法 # defer 语法支持是Go 语言中一大特性,通过 defer 关键字,我们可以声明一个延迟执行函数,当调用者返回之前开始执行该函数,一般用来完成资源、锁、连接等释放工作,或者 recover 可能发生的panic。
三大特性 # defer延迟执行语法有三大特性
defer函数的传入参数在定义时就已经明确 # func main() { i := 1 defer fmt.Println(i) i&#43;&#43; return } 上面代码输出1而不是2。
defer函数是按照后进先出的顺序执行 # func main() { for i := 1; i &lt;= 5; i&#43;&#43; { defer fmt.Print(i) } } 上面代码输出54321而不是12345。
defer函数可以读取和修改函数的命名返回值 # func main() { fmt.Println(test()) } func test() (i int) { defer func() { i&#43;&#43; }() return 100 } 上面代码输出输出101而不是100或者1。
白话defer原理 # defer函数底层数据结构是_defer结构体多个defer函数会构建成一个_defer链表后面加入的defer函数会插入链表的头部该链表链表头部会链接到G上。当函数执行完成返回的时候会从_defer链表头部开始依次执行defer函数。这也就是defer函数执行时会LIFO的原因。_defer链接结构示意图如下" />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://go.cyub.vip/feature/defer/" /><meta property="article:section" content="feature" />
<title>延迟执行 - defer语法 | 深入Go语言之旅</title>
<link rel="manifest" href="/manifest.json">
<link rel="icon" href="/favicon.png" >
<link rel="stylesheet" href="/book.min.f06572240ce28e67eb332ac5cf5d59a696c47ad4c6f700d5842c5ed93dd8ec77.css" integrity="sha256-8GVyJAzijmfrMyrFz11ZppbEetTG9wDVhCxe2T3Y7Hc=" crossorigin="anonymous">
<script defer src="/flexsearch.min.js"></script>
<script defer src="/en.search.min.9749981f5a5252740b10f807556d022e7ea806edbe559c9ba2c2a1a86afdbf64.js" integrity="sha256-l0mYH1pSUnQLEPgHVW0CLn6oBu2&#43;VZybosKhqGr9v2Q=" crossorigin="anonymous"></script>
<script async src="https://www.googletagmanager.com/gtag/js?id=G-BQ229RRTTX"></script>
<script>
var doNotTrack = false;
if (!doNotTrack) {
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-BQ229RRTTX', { 'anonymize_ip': false });
}
</script>
<!--
Made with Book Theme
https://github.com/alex-shpak/hugo-book
-->
</head>
<body dir="ltr">
<input type="checkbox" class="hidden toggle" id="menu-control" />
<input type="checkbox" class="hidden toggle" id="toc-control" />
<main class="container flex">
<aside class="book-menu">
<div class="book-menu-content">
<nav>
<h2 class="book-brand">
<a class="flex align-center" href="/"><img src="https://static.cyub.vip/images/202310/golang-480.png" alt="Logo" /><span>深入Go语言之旅</span>
</a>
</h2>
<div class="book-search">
<input type="text" id="book-search-input" placeholder="Search" aria-label="Search" maxlength="64" data-hotkeys="s/" />
<div class="book-search-spinner hidden"></div>
<ul id="book-search-results"></ul>
</div>
<ul>
<li>
<a href="https://www.cyub.vip/" target="_blank" rel="noopener">
个人博客
</a>
</li>
<li>
<a href="https://github.com/cyub" target="_blank" rel="noopener">
Github主页
</a>
</li>
<li>
<a href="https://www.topgoer.cn/?ref=go.cyub.vip" target="_blank" rel="noopener">
地鼠文档
</a>
</li>
</ul>
<ul>
<li>
<p><strong>
<a href="/">深入Go语言之旅</a></strong></p>
</li>
<li>
<p><strong>准备篇</strong></p>
<ul>
<li>
<a href="/compiler/">编译流程</a></li>
<li>
<a href="/analysis-tools/">分析工具</a>
<ul>
<li>
<a href="/analysis-tools/gdb/">GDB</a></li>
<li>
<a href="/analysis-tools/dlv/">Delve</a></li>
<li>
<a href="/analysis-tools/go-buildin-tools/">Go 内置工具</a></li>
</ul>
</li>
<li>
<a href="/go-assembly/">Go汇编</a></li>
</ul>
</li>
<li>
<p><strong>基础篇</strong></p>
<ul>
<li>
<a href="/type/">数据类型与数据结构</a>
<ul>
<li>
<a href="/type/string/">字符串</a></li>
<li>
<a href="/type/array/">数组</a></li>
<li>
<a href="/type/slice/">切片</a></li>
<li>
<a href="/type/nil/">nil</a></li>
<li>
<a href="/type/empty_struct/">空结构体</a></li>
<li>
<a href="/type/pointer/">指针</a></li>
<li>
<a href="/type/map/">映射</a></li>
</ul>
</li>
<li>
<a href="/function/">函数</a>
<ul>
<li>
<a href="/function/first-class/">一等公民</a></li>
<li>
<a href="/function/call-stack/">函数调用栈</a></li>
<li>
<a href="/function/pass-by-value/">值传递</a></li>
<li>
<a href="/function/closure/">闭包</a></li>
<li>
<a href="/function/method/">方法</a></li>
</ul>
</li>
<li>
<a href="/feature/">语言特性</a>
<ul>
<li>
<a href="/feature/comma-ok/">逗号ok模式</a></li>
<li>
<a href="/feature/for-range/">遍历 - for-range语法</a></li>
<li>
<a href="/feature/defer/"class=active>延迟执行 - defer语法</a></li>
<li>
<a href="/feature/select/">通道选择器 - select语法</a></li>
<li>
<a href="/feature/panic-recover/">恐慌与恢复 - panic/recover</a></li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>运行时篇</strong></p>
<ul>
<li>
<a href="/concurrency/">并发编程</a>
<ul>
<li>
<a href="/concurrency/memory-model/">内存模型</a></li>
<li>
<a href="/concurrency/context/">上下文 - context</a></li>
<li>
<a href="/concurrency/channel/">通道 - channel</a></li>
<li>
<a href="/concurrency/atomic/">原子操作 - atomic</a></li>
<li>
<a href="/concurrency/sync-map/">并发Map - sync.Map</a></li>
<li>
<a href="/concurrency/sync-waitgroup/">等待组 - sync.WaitGroup</a></li>
<li>
<a href="/concurrency/sync-once/">一次性操作 - sync.Once</a></li>
<li>
<a href="/concurrency/sync-pool/">缓冲池 - sync.Pool</a></li>
<li>
<a href="/concurrency/sync-cond/">条件变量 - sync.Cond</a></li>
<li>
<a href="/concurrency/sync-mutex/">互斥锁 - sync.Mutex</a></li>
<li>
<a href="/concurrency/sync-rwmutex/">读写锁 - sync.RWMutex</a></li>
</ul>
</li>
<li>
<a href="/gmp/">G-M-P调度机制</a>
<ul>
<li>
<a href="/gmp/gmp-model/">调度机制概述</a></li>
<li>
<a href="/gmp/scheduler/">调度器</a></li>
</ul>
</li>
<li>
<a href="/memory/">内存管理</a>
<ul>
<li>
<a href="/memory/allocator/">内存分配器</a></li>
<li>
<a href="/memory/gc/">GC</a></li>
</ul>
</li>
<li>
<a href="/type-system/">类型系统</a>
<ul>
<li>
<a href="/type-system/type/">类型系统</a></li>
<li>
<a href="/type-system/interface/">接口</a></li>
<li>
<a href="/type-system/reflect/">反射</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
<script>(function(){var e=document.querySelector("aside .book-menu-content");addEventListener("beforeunload",function(){localStorage.setItem("menu.scrollTop",e.scrollTop)}),e.scrollTop=localStorage.getItem("menu.scrollTop")})()</script>
</div>
</aside>
<div class="book-page">
<header class="book-header">
<div class="flex align-center justify-between">
<label for="menu-control">
<img src="/svg/menu.svg" class="book-icon" alt="Menu" />
</label>
<strong>延迟执行 - defer语法</strong>
<label for="toc-control">
</label>
</div>
</header>
<article class="markdown"><h1 id="延迟执行---defer语法">
延迟执行 - defer语法
<a class="anchor" href="#%e5%bb%b6%e8%bf%9f%e6%89%a7%e8%a1%8c---defer%e8%af%ad%e6%b3%95">#</a>
</h1>
<p><code>defer</code> 语法支持是Go 语言中一大特性,通过 <code>defer</code> 关键字,我们可以声明一个延迟执行函数,当调用者返回之前开始执行该函数,一般用来完成资源、锁、连接等释放工作,或者 <code>recover</code> 可能发生的<code>panic</code></p>
<h2 id="三大特性">
三大特性
<a class="anchor" href="#%e4%b8%89%e5%a4%a7%e7%89%b9%e6%80%a7">#</a>
</h2>
<p>defer延迟执行语法有三大特性</p>
<h3 id="defer函数的传入参数在定义时就已经明确">
defer函数的传入参数在定义时就已经明确
<a class="anchor" href="#defer%e5%87%bd%e6%95%b0%e7%9a%84%e4%bc%a0%e5%85%a5%e5%8f%82%e6%95%b0%e5%9c%a8%e5%ae%9a%e4%b9%89%e6%97%b6%e5%b0%b1%e5%b7%b2%e7%bb%8f%e6%98%8e%e7%a1%ae">#</a>
</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">i</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>上面代码输出1而不是2。</p>
<h3 id="defer函数是按照后进先出的顺序执行">
defer函数是按照后进先出的顺序执行
<a class="anchor" href="#defer%e5%87%bd%e6%95%b0%e6%98%af%e6%8c%89%e7%85%a7%e5%90%8e%e8%bf%9b%e5%85%88%e5%87%ba%e7%9a%84%e9%a1%ba%e5%ba%8f%e6%89%a7%e8%a1%8c">#</a>
</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">1</span>; <span style="color:#a6e22e">i</span> <span style="color:#f92672">&lt;=</span> <span style="color:#ae81ff">5</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Print</span>(<span style="color:#a6e22e">i</span>)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>上面代码输出<code>54321</code>,而不是<code>12345</code></p>
<h3 id="defer函数可以读取和修改函数的命名返回值">
defer函数可以读取和修改函数的命名返回值
<a class="anchor" href="#defer%e5%87%bd%e6%95%b0%e5%8f%af%e4%bb%a5%e8%af%bb%e5%8f%96%e5%92%8c%e4%bf%ae%e6%94%b9%e5%87%bd%e6%95%b0%e7%9a%84%e5%91%bd%e5%90%8d%e8%bf%94%e5%9b%9e%e5%80%bc">#</a>
</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">test</span>())
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">test</span>() (<span style="color:#a6e22e">i</span> <span style="color:#66d9ef">int</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">defer</span> <span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span>
</span></span><span style="display:flex;"><span> }()
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">100</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>上面代码输出输出101而不是100或者1。</p>
<h2 id="白话defer原理">
白话defer原理
<a class="anchor" href="#%e7%99%bd%e8%af%9ddefer%e5%8e%9f%e7%90%86">#</a>
</h2>
<p>defer函数底层数据结构是_defer结构体多个defer函数会构建成一个_defer链表后面加入的defer函数会插入链表的头部该链表链表头部会链接到G上。当函数执行完成返回的时候会从_defer链表头部开始依次执行defer函数。这也就是defer函数执行时会LIFO的原因。_defer链接结构示意图如下</p>
<figure class="text-center"><img src="https://static.cyub.vip/images/202105/defer_profile.png" width="450px"/><figcaption>
<h4>defer原理示意图</h4>
</figcaption>
</figure>
<p>创建_defer结构体是需要进行内存分配的为了减少分配_defer结构体时资源消耗Go底层使用了defer缓冲池defer pool用来缓存上次使用完的_defer结构体这样下次可以直接使用不必再重新分配内存了。defer缓冲池一共有两级per-P级defer缓冲池和全局defer缓冲池。当创建_defer结构体时候优先从当前M关联的P的缓冲池中取得_defer结构体即从per-P缓冲池中获取这个过程是无锁操作。如果per-P缓冲池中没有则在尝试从全局defer缓冲池获取若也没有获取到则重新分配一个新的_defer结构体。</p>
<p>当defer函数执行完成之后Go底层会将分配的_defer结构体进行回收先存放在per-P级defer缓冲池中若已存满则存放在全局defer缓冲池中。</p>
<h2 id="源码分析">
源码分析
<a class="anchor" href="#%e6%ba%90%e7%a0%81%e5%88%86%e6%9e%90">#</a>
</h2>
<p>我们以下代码作为示例分析defer实现机制</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">greet</span>(<span style="color:#e6db74">&#34;friend&#34;</span>)
</span></span><span style="display:flex;"><span> println(<span style="color:#e6db74">&#34;welcome&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">greet</span>(<span style="color:#a6e22e">text</span> <span style="color:#66d9ef">string</span>) {
</span></span><span style="display:flex;"><span> print(<span style="color:#e6db74">&#34;hello &#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">text</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>在分析之前我们先来看下defer结构体</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">_defer</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">siz</span> <span style="color:#66d9ef">int32</span> <span style="color:#75715e">// 参数和返回值共占用空间大小这段空间会在_defer结构体后面用于defer注册时候保存参数并在执行时候拷贝到调用者参数与返回值空间。
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">started</span> <span style="color:#66d9ef">bool</span> <span style="color:#75715e">// 标记defer是否已经执行
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">heap</span> <span style="color:#66d9ef">bool</span> <span style="color:#75715e">// 标记该_defer结构体是否分配在堆上
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">openDefer</span> <span style="color:#66d9ef">bool</span> <span style="color:#75715e">// 标志是否使用open coded defer方式处理defer
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">sp</span> <span style="color:#66d9ef">uintptr</span> <span style="color:#75715e">// 调用者栈指针执行时会根据sp判断该defer是否是当前执行调用者注册的
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">pc</span> <span style="color:#66d9ef">uintptr</span> <span style="color:#75715e">// deferprocStack或deferproc的返回地址
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">fn</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">funcval</span> <span style="color:#75715e">// defer函数是funcval类型
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">_panic</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">_panic</span> <span style="color:#75715e">// panic链表用于panic处理
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">link</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">_defer</span> <span style="color:#75715e">// 链接到下一个_defer结构体即该在_defer之前注册的_defer结构体
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">fd</span> <span style="color:#a6e22e">unsafe</span>.<span style="color:#a6e22e">Pointer</span> <span style="color:#75715e">// funcdata for the function associated with the frame
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">varp</span> <span style="color:#66d9ef">uintptr</span> <span style="color:#75715e">// value of varp for the stack frame
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">framepc</span> <span style="color:#66d9ef">uintptr</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>_defer</code>结构体中<code>siz</code>字段记录着defer函数参数和返回值大小如果defer函数拥有参数则Go会把其参数拷贝到该defer函数对应的_defer结构体后面的内存块中。</p>
<p><code>_defer</code>结构体中<code>fn</code>字段是指向一个funcval类型的指针funcval结构体的fn字段字段指向defer函数的入口地址。对应上面示例代码中就是<code>greet</code>函数的入口地址</p>
<p>上面示例代码中编译后的Go汇编代码如下
<a href="https://go.godbolt.org/z/4d74z37zr">点击在线查看汇编代码</a></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-as" data-lang="as"><span style="display:flex;"><span><span style="color:#a6e22e">main_pc0</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">TEXT</span> <span style="color:#e6db74">&#34;&#34;</span>.<span style="color:#a6e22e">main</span>(<span style="color:#a6e22e">SB</span>)<span style="color:#f92672">,</span> <span style="color:#a6e22e">ABIInternal</span><span style="color:#f92672">,</span> <span style="color:#a6e22e">$40</span><span style="color:#f92672">-</span><span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">MOVQ</span> (<span style="color:#a6e22e">TLS</span>)<span style="color:#f92672">,</span> <span style="color:#a6e22e">CX</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">CMPQ</span> <span style="color:#a6e22e">SP</span><span style="color:#f92672">,</span> <span style="color:#ae81ff">16</span>(<span style="color:#a6e22e">CX</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">JLS</span> <span style="color:#a6e22e">main_pc151</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">SUBQ</span> <span style="color:#a6e22e">$40</span><span style="color:#f92672">,</span> <span style="color:#a6e22e">SP</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">MOVQ</span> <span style="color:#a6e22e">BP</span><span style="color:#f92672">,</span> <span style="color:#ae81ff">32</span>(<span style="color:#a6e22e">SP</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">LEAQ</span> <span style="color:#ae81ff">32</span>(<span style="color:#a6e22e">SP</span>)<span style="color:#f92672">,</span> <span style="color:#a6e22e">BP</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">FUNCDATA</span> <span style="color:#a6e22e">$0</span><span style="color:#f92672">,</span> <span style="color:#a6e22e">gclocals</span><span style="color:#960050;background-color:#1e0010">·</span><span style="color:#ae81ff">33</span><span style="color:#a6e22e">cdeccccebe80329f1fdbee7f5874cb</span>(<span style="color:#a6e22e">SB</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">FUNCDATA</span> <span style="color:#a6e22e">$1</span><span style="color:#f92672">,</span> <span style="color:#a6e22e">gclocals</span><span style="color:#960050;background-color:#1e0010">·</span><span style="color:#ae81ff">33</span><span style="color:#a6e22e">cdeccccebe80329f1fdbee7f5874cb</span>(<span style="color:#a6e22e">SB</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">FUNCDATA</span> <span style="color:#a6e22e">$3</span><span style="color:#f92672">,</span> <span style="color:#a6e22e">gclocals</span><span style="color:#960050;background-color:#1e0010">·</span><span style="color:#ae81ff">9</span><span style="color:#a6e22e">fb7f0986f647f17cb53dda1484e0f7a</span>(<span style="color:#a6e22e">SB</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">PCDATA</span> <span style="color:#a6e22e">$2</span><span style="color:#f92672">,</span> <span style="color:#a6e22e">$0</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">PCDATA</span> <span style="color:#a6e22e">$0</span><span style="color:#f92672">,</span> <span style="color:#a6e22e">$0</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">MOVL</span> <span style="color:#a6e22e">$16</span><span style="color:#f92672">,</span> (<span style="color:#a6e22e">SP</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">PCDATA</span> <span style="color:#a6e22e">$2</span><span style="color:#f92672">,</span> <span style="color:#a6e22e">$1</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">LEAQ</span> <span style="color:#e6db74">&#34;&#34;</span>.<span style="color:#a6e22e">greet</span><span style="color:#960050;background-color:#1e0010">·</span><span style="color:#a6e22e">f</span>(<span style="color:#a6e22e">SB</span>)<span style="color:#f92672">,</span> <span style="color:#a6e22e">AX</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">PCDATA</span> <span style="color:#a6e22e">$2</span><span style="color:#f92672">,</span> <span style="color:#a6e22e">$0</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">MOVQ</span> <span style="color:#a6e22e">AX</span><span style="color:#f92672">,</span> <span style="color:#ae81ff">8</span>(<span style="color:#a6e22e">SP</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">PCDATA</span> <span style="color:#a6e22e">$2</span><span style="color:#f92672">,</span> <span style="color:#a6e22e">$1</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">LEAQ</span> <span style="color:#a6e22e">go</span>.<span style="color:#a6e22e">string</span>.<span style="color:#e6db74">&#34;friend&#34;</span>(<span style="color:#a6e22e">SB</span>)<span style="color:#f92672">,</span> <span style="color:#a6e22e">AX</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">PCDATA</span> <span style="color:#a6e22e">$2</span><span style="color:#f92672">,</span> <span style="color:#a6e22e">$0</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">MOVQ</span> <span style="color:#a6e22e">AX</span><span style="color:#f92672">,</span> <span style="color:#ae81ff">16</span>(<span style="color:#a6e22e">SP</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">MOVQ</span> <span style="color:#a6e22e">$6</span><span style="color:#f92672">,</span> <span style="color:#ae81ff">24</span>(<span style="color:#a6e22e">SP</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">CALL</span> <span style="color:#a6e22e">runtime</span>.<span style="color:#a6e22e">deferproc</span>(<span style="color:#a6e22e">SB</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">TESTL</span> <span style="color:#a6e22e">AX</span><span style="color:#f92672">,</span> <span style="color:#a6e22e">AX</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">JNE</span> <span style="color:#a6e22e">main_pc135</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">JMP</span> <span style="color:#a6e22e">main_pc84</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">main_pc84</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">CALL</span> <span style="color:#a6e22e">runtime</span>.<span style="color:#a6e22e">printlock</span>(<span style="color:#a6e22e">SB</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">PCDATA</span> <span style="color:#a6e22e">$2</span><span style="color:#f92672">,</span> <span style="color:#a6e22e">$1</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">LEAQ</span> <span style="color:#a6e22e">go</span>.<span style="color:#a6e22e">string</span>.<span style="color:#e6db74">&#34;welcome\n&#34;</span>(<span style="color:#a6e22e">SB</span>)<span style="color:#f92672">,</span> <span style="color:#a6e22e">AX</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">PCDATA</span> <span style="color:#a6e22e">$2</span><span style="color:#f92672">,</span> <span style="color:#a6e22e">$0</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">MOVQ</span> <span style="color:#a6e22e">AX</span><span style="color:#f92672">,</span> (<span style="color:#a6e22e">SP</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">MOVQ</span> <span style="color:#a6e22e">$8</span><span style="color:#f92672">,</span> <span style="color:#ae81ff">8</span>(<span style="color:#a6e22e">SP</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">CALL</span> <span style="color:#a6e22e">runtime</span>.<span style="color:#a6e22e">printstring</span>(<span style="color:#a6e22e">SB</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">CALL</span> <span style="color:#a6e22e">runtime</span>.<span style="color:#a6e22e">printunlock</span>(<span style="color:#a6e22e">SB</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">XCHGL</span> <span style="color:#a6e22e">AX</span><span style="color:#f92672">,</span> <span style="color:#a6e22e">AX</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">CALL</span> <span style="color:#a6e22e">runtime</span>.<span style="color:#a6e22e">deferreturn</span>(<span style="color:#a6e22e">SB</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">MOVQ</span> <span style="color:#ae81ff">32</span>(<span style="color:#a6e22e">SP</span>)<span style="color:#f92672">,</span> <span style="color:#a6e22e">BP</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">ADDQ</span> <span style="color:#a6e22e">$40</span><span style="color:#f92672">,</span> <span style="color:#a6e22e">SP</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">RET</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">main_pc135</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">XCHGL</span> <span style="color:#a6e22e">AX</span><span style="color:#f92672">,</span> <span style="color:#a6e22e">AX</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">CALL</span> <span style="color:#a6e22e">runtime</span>.<span style="color:#a6e22e">deferreturn</span>(<span style="color:#a6e22e">SB</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">MOVQ</span> <span style="color:#ae81ff">32</span>(<span style="color:#a6e22e">SP</span>)<span style="color:#f92672">,</span> <span style="color:#a6e22e">BP</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">ADDQ</span> <span style="color:#a6e22e">$40</span><span style="color:#f92672">,</span> <span style="color:#a6e22e">SP</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">RET</span>
</span></span></code></pre></div><p>需要注意的是上面汇编代码是go1.12版本的汇编代码。</p>
<p>从上面汇编代码我们可以发现defer实现有两个阶段第一个阶段使用<code>runtime.deferproc</code>函数进行<strong>defer注册阶段</strong>。这一阶段主要工作是创建defer结构然后将其注册到defer链表中。在注册完成之后会根据<code>runtime.deferproc</code>函数返回结果进行下一步处理若是1则说明defer函数有panic处理则直接跳过defer后面的代码直接去执行<code>runtime.deferreturn</code>(对应就是上面汇编代码<code>JNE main_pc135</code>逻辑若是0则是正常流程则继续后面的代码对应上面汇编代码就是<code> JMP main_pc84</code>)。</p>
<p>第二个阶段是调用<code>runtime.deferreturn</code>函数执行<strong>defer执行阶段</strong>。这个阶段遍历defer链表获取defer结构然后执行defer结构中存放的defer函数信息。</p>
<h3 id="defer注册阶段">
defer注册阶段
<a class="anchor" href="#defer%e6%b3%a8%e5%86%8c%e9%98%b6%e6%ae%b5">#</a>
</h3>
<p>defer注册阶段是调用<code>deferproc</code>函数将创建defer结构体并将其注册到defer链表中。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">deferproc</span>(<span style="color:#a6e22e">siz</span> <span style="color:#66d9ef">int32</span>, <span style="color:#a6e22e">fn</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">funcval</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">getg</span>().<span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">curg</span> <span style="color:#f92672">!=</span> <span style="color:#a6e22e">getg</span>() { <span style="color:#75715e">// 判断当前G是否处在用户栈空间上若不是则抛出异常
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">throw</span>(<span style="color:#e6db74">&#34;defer on system stack&#34;</span>)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">sp</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">getcallersp</span>()
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">argp</span> <span style="color:#f92672">:=</span> uintptr(<span style="color:#a6e22e">unsafe</span>.<span style="color:#a6e22e">Pointer</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">fn</span>)) <span style="color:#f92672">+</span> <span style="color:#a6e22e">unsafe</span>.<span style="color:#a6e22e">Sizeof</span>(<span style="color:#a6e22e">fn</span>) <span style="color:#75715e">// 获取defer函数参数起始地址
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">callerpc</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">getcallerpc</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">d</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">newdefer</span>(<span style="color:#a6e22e">siz</span>)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">_panic</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">throw</span>(<span style="color:#e6db74">&#34;deferproc: d.panic != nil after newdefer&#34;</span>)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">fn</span> = <span style="color:#a6e22e">fn</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">pc</span> = <span style="color:#a6e22e">callerpc</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">sp</span> = <span style="color:#a6e22e">sp</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">siz</span> {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">case</span> <span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// Do nothing.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">sys</span>.<span style="color:#a6e22e">PtrSize</span>: <span style="color:#75715e">// defer函数等于8字节大小64位系统下则直接将_defer结构体后面8字节空间
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#f92672">*</span>(<span style="color:#f92672">*</span><span style="color:#66d9ef">uintptr</span>)(<span style="color:#a6e22e">deferArgs</span>(<span style="color:#a6e22e">d</span>)) = <span style="color:#f92672">*</span>(<span style="color:#f92672">*</span><span style="color:#66d9ef">uintptr</span>)(<span style="color:#a6e22e">unsafe</span>.<span style="color:#a6e22e">Pointer</span>(<span style="color:#a6e22e">argp</span>))
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">default</span>:
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">memmove</span>(<span style="color:#a6e22e">deferArgs</span>(<span style="color:#a6e22e">d</span>), <span style="color:#a6e22e">unsafe</span>.<span style="color:#a6e22e">Pointer</span>(<span style="color:#a6e22e">argp</span>), uintptr(<span style="color:#a6e22e">siz</span>))
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">return0</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>上面代码中<code>getcallersp()</code>返回调用者SP地址。deferproc的调用者是main函数getcallersp()返回的SP地址指向的deferproc的return address。</p>
<p><code>getcallerpc()</code>返回调用者PC此时PC指向的<code>CALL runtime.deferproc(SB)</code>指令的下一条指令,即<code>TESTL AX, AX</code></p>
<p>结合汇编和<code>deferproc</code>代码我们画出defer注册时状态图</p>
<p>接下来我们来看下newdefer函数是如何分配defer结构体的。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">newdefer</span>(<span style="color:#a6e22e">siz</span> <span style="color:#66d9ef">int32</span>) <span style="color:#f92672">*</span><span style="color:#a6e22e">_defer</span> {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">d</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">_defer</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">sc</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">deferclass</span>(uintptr(<span style="color:#a6e22e">siz</span>)) <span style="color:#75715e">// 根据defer函数参数大小计算出应该使用上面规格的defer缓冲池
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">gp</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">getg</span>()
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">sc</span> &lt; uintptr(len(<span style="color:#a6e22e">p</span>{}.<span style="color:#a6e22e">deferpool</span>)) { <span style="color:#75715e">// defer缓冲池只支持5种缓冲池从0到4若sc规格不小于5(说明defer参数大小大于64字节)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#75715e">// 则无法使用缓冲池,则需从内存中分配
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">pp</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">gp</span>.<span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">ptr</span>() <span style="color:#75715e">// pp指向当前M关联的P
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">if</span> len(<span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>]) <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">sched</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>] <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> { <span style="color:#75715e">// 若当前P的defer缓冲池为空且全局缓冲池有可用的defer那么先从全局缓冲拿一点过来存放在P的缓冲池中
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">systemstack</span>(<span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">lock</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">sched</span>.<span style="color:#a6e22e">deferlock</span>)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> len(<span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>]) &lt; cap(<span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>])<span style="color:#f92672">/</span><span style="color:#ae81ff">2</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">sched</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>] <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">d</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">sched</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>]
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">sched</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>] = <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">link</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">link</span> = <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>] = append(<span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>], <span style="color:#a6e22e">d</span>)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">unlock</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">sched</span>.<span style="color:#a6e22e">deferlock</span>)
</span></span><span style="display:flex;"><span> })
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">:=</span> len(<span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>]); <span style="color:#a6e22e">n</span> &gt; <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">d</span> = <span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>][<span style="color:#a6e22e">n</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>]
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>][<span style="color:#a6e22e">n</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>] = <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>] = <span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>][:<span style="color:#a6e22e">n</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>]
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">d</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> { <span style="color:#75715e">// 若果需要的defer缓冲池不满足所需的规格或者缓冲池中没有可用的时候切换到系统栈上进行defer结构内存分配。
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">systemstack</span>(<span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">total</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">roundupsize</span>(<span style="color:#a6e22e">totaldefersize</span>(uintptr(<span style="color:#a6e22e">siz</span>)))
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">d</span> = (<span style="color:#f92672">*</span><span style="color:#a6e22e">_defer</span>)(<span style="color:#a6e22e">mallocgc</span>(<span style="color:#a6e22e">total</span>, <span style="color:#a6e22e">deferType</span>, <span style="color:#66d9ef">true</span>))
</span></span><span style="display:flex;"><span> })
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">siz</span> = <span style="color:#a6e22e">siz</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">heap</span> = <span style="color:#66d9ef">true</span> <span style="color:#75715e">// 标记分配到堆上
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">link</span> = <span style="color:#a6e22e">gp</span>.<span style="color:#a6e22e">_defer</span> <span style="color:#75715e">// 插入到链表头部
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">gp</span>.<span style="color:#a6e22e">_defer</span> = <span style="color:#a6e22e">d</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">d</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>总结下<code>newdefer</code>函数逻辑:</p>
<ol>
<li>首先根据defer函数的参数大小使用<code>deferclass</code>计算出相应所需要的defer规格如果defer缓冲池支持该规格则尝试从defer缓冲池取出对应的defer结构体。</li>
<li>从defer缓冲池中取可用defer结构体时候会首先从per-P defer缓冲池中取若per-P defer缓冲池为空则尝试从全局缓冲池中取一些可用defer结构体然后放在per-P缓冲池然后再从per-P缓冲池中取。</li>
<li>若defer缓冲池不支持该规格或者缓冲池无可用缓冲则切换到系统栈上进行defer结构分配。</li>
</ol>
<h4 id="defer缓冲池规格">
defer缓冲池规格
<a class="anchor" href="#defer%e7%bc%93%e5%86%b2%e6%b1%a0%e8%a7%84%e6%a0%bc">#</a>
</h4>
<p>defer缓冲池是按照defer函数参数大小范围分为五种规格若不在五种规格之类则不提供缓冲池功能那么每次defer注册时候时候都必须进行内存分配创建defer结构体</p>
<table>
<thead>
<tr>
<th>缓冲池规格</th>
<th>defer函数参数大小范围</th>
<th>对应per-P缓冲池位置</th>
<th>对应全局缓冲池位置</th>
</tr>
</thead>
<tbody>
<tr>
<td>class0</td>
<td>0</td>
<td>p.deferpool[0]</td>
<td>sched.deferpool[0]</td>
</tr>
<tr>
<td>class1</td>
<td>[1, 16]</td>
<td>p.deferpool[1]</td>
<td>sched.deferpool[1]</td>
</tr>
<tr>
<td>class2</td>
<td>[17, 32]</td>
<td>p.deferpool[2]</td>
<td>sched.deferpool[2]</td>
</tr>
<tr>
<td>class3</td>
<td>[33, 48]</td>
<td>p.deferpool[3]</td>
<td>sched.deferpool[3]</td>
</tr>
<tr>
<td>class4</td>
<td>[49, 64]</td>
<td>p.deferpool[4]</td>
<td>sched.deferpool[4]</td>
</tr>
</tbody>
</table>
<p>defer函数参数大小与缓冲池规格转换是通过<code>deferclass</code>函数转换的:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">deferclass</span>(<span style="color:#a6e22e">siz</span> <span style="color:#66d9ef">uintptr</span>) <span style="color:#66d9ef">uintptr</span> {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">siz</span> <span style="color:#f92672">&lt;=</span> <span style="color:#a6e22e">minDeferArgs</span> { <span style="color:#75715e">// minDeferArgs是个常量值是0
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> (<span style="color:#a6e22e">siz</span> <span style="color:#f92672">-</span> <span style="color:#a6e22e">minDeferArgs</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">15</span>) <span style="color:#f92672">/</span> <span style="color:#ae81ff">16</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h4 id="per-p级defer缓冲池与全局级defer缓冲池结构">
per-P级defer缓冲池与全局级defer缓冲池结构
<a class="anchor" href="#per-p%e7%ba%a7defer%e7%bc%93%e5%86%b2%e6%b1%a0%e4%b8%8e%e5%85%a8%e5%b1%80%e7%ba%a7defer%e7%bc%93%e5%86%b2%e6%b1%a0%e7%bb%93%e6%9e%84">#</a>
</h4>
<p>per-P级defer缓冲池结构使用两个字段<code>deferpool</code><code>deferpoolbuf</code>构成缓冲池:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">p</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span> <span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">deferpool</span> [<span style="color:#ae81ff">5</span>][]<span style="color:#f92672">*</span><span style="color:#a6e22e">_defer</span> <span style="color:#75715e">// pool of available defer structs of different sizes (see panic.go)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">deferpoolbuf</span> [<span style="color:#ae81ff">5</span>][<span style="color:#ae81ff">32</span>]<span style="color:#f92672">*</span><span style="color:#a6e22e">_defer</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>p结构体中deferpool数组的元素是_defer指针类型的切片该切片的底层数组是deferpoolbuf数组的元素</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">pp</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">p</span>) <span style="color:#a6e22e">init</span>(<span style="color:#a6e22e">id</span> <span style="color:#66d9ef">int32</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span> {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">i</span>] = <span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpoolbuf</span>[<span style="color:#a6e22e">i</span>][:<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>全局级defer缓冲池保存在全局sched的deferpool字段中sched是<code>schedt</code>类型变量deferpool是由5个_defer类型指针构成链表组成的数组</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">schedt</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span> <span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">deferlock</span> <span style="color:#a6e22e">mutex</span> <span style="color:#75715e">// 由于存在多个P并发的从全局缓冲池中获取defer结构体所以需要一个锁
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">deferpool</span> [<span style="color:#ae81ff">5</span>]<span style="color:#f92672">*</span><span style="color:#a6e22e">_defer</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="defer执行阶段">
defer执行阶段
<a class="anchor" href="#defer%e6%89%a7%e8%a1%8c%e9%98%b6%e6%ae%b5">#</a>
</h3>
<p>当函数返回之前Go会调用<code>deferreturn</code>函数开始执行defer函数。总之defer流程可以简单概括为Go语言通过先注册通过调用deferproc函数然后函数返回之前执行defer函数通过调用deferreturn函数实现了defer延迟执行功能。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">deferreturn</span>(<span style="color:#a6e22e">arg0</span> <span style="color:#66d9ef">uintptr</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">gp</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">getg</span>()
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">d</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">gp</span>.<span style="color:#a6e22e">_defer</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">d</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> { <span style="color:#75715e">// defer链表为空直接返回。deferreturn是一个递归调用每次调用都会从defer链表弹出一个defer进行执行当defer链表为空时候说明所有defer都已经执行完成
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">sp</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">getcallersp</span>()
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">sp</span> <span style="color:#f92672">!=</span> <span style="color:#a6e22e">sp</span> { <span style="color:#75715e">// defer保存的sp与当前调用deferreturn的调用者栈顶sp不一致则直接返回
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">siz</span> {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">case</span> <span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">sys</span>.<span style="color:#a6e22e">PtrSize</span>: <span style="color:#75715e">// 若defer参数大小是8字节则直接将defer参数复制给arg0
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#f92672">*</span>(<span style="color:#f92672">*</span><span style="color:#66d9ef">uintptr</span>)(<span style="color:#a6e22e">unsafe</span>.<span style="color:#a6e22e">Pointer</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">arg0</span>)) = <span style="color:#f92672">*</span>(<span style="color:#f92672">*</span><span style="color:#66d9ef">uintptr</span>)(<span style="color:#a6e22e">deferArgs</span>(<span style="color:#a6e22e">d</span>))
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">default</span>: <span style="color:#75715e">// 否则进行内存移动将defer的参数复制到arg0中此后arg0存放的是延迟函数的参数
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">memmove</span>(<span style="color:#a6e22e">unsafe</span>.<span style="color:#a6e22e">Pointer</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">arg0</span>), <span style="color:#a6e22e">deferArgs</span>(<span style="color:#a6e22e">d</span>), uintptr(<span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">siz</span>))
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">fn</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">fn</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">fn</span> = <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">gp</span>.<span style="color:#a6e22e">_defer</span> = <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">link</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">freedefer</span>(<span style="color:#a6e22e">d</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">jmpdefer</span>(<span style="color:#a6e22e">fn</span>, uintptr(<span style="color:#a6e22e">unsafe</span>.<span style="color:#a6e22e">Pointer</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">arg0</span>)))
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>deferreturn函数通过jmpdefer实现递归调用jmpdefer是通过汇编实现的jmpdefer函数完成两个功能调用defer函数和deferreturn再次调用。deferreturn递归调用时候递归终止条件有两个1. defer链表为空。2. defer保存的sp与当前调用deferreturn调用者栈顶sp不一致。第一个条件很好了解第二个循环终止条件存在原因我们稍后探究。</p>
<p>我们需要理解arg0这个变量用途。arg0看似是deferreturn的参数实际上是用来存储延迟函数的参数。</p>
<p>在调用<code>jmpdefer</code>之前,会先调用<code>freedefer</code>将当前defer结构释放回收</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">freedefer</span>(<span style="color:#a6e22e">d</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">_defer</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">_panic</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> { <span style="color:#75715e">// freedefer调用时_panic一定是nil
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">freedeferpanic</span>() <span style="color:#75715e">// freedeferpanic作用是抛出异常freedefer with d._panic != nil
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> }
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">fn</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> { <span style="color:#75715e">// freedefer调用时fn一定已经置为nil
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">freedeferfn</span>() <span style="color:#75715e">// freedeferfn作用是抛出异常freedefer with d.fn != nil
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> }
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">heap</span> { <span style="color:#75715e">// defer结构不是在堆上分配则无需进行回收
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">sc</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">deferclass</span>(uintptr(<span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">siz</span>)) <span style="color:#75715e">// 根据defer参数和返回值大小判断规格以便决定放在哪种规格defer缓冲池中
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">sc</span> <span style="color:#f92672">&gt;=</span> uintptr(len(<span style="color:#a6e22e">p</span>{}.<span style="color:#a6e22e">deferpool</span>)) {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">pp</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">getg</span>().<span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">ptr</span>()
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> len(<span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>]) <span style="color:#f92672">==</span> cap(<span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>]) { <span style="color:#75715e">// 当前P的defer缓冲池已满则将P的defer缓冲池defer取出一般放在全局defer缓冲池中
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">systemstack</span>(<span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">first</span>, <span style="color:#a6e22e">last</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">_defer</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> len(<span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>]) &gt; cap(<span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>])<span style="color:#f92672">/</span><span style="color:#ae81ff">2</span> {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">:=</span> len(<span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>])
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">d</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>][<span style="color:#a6e22e">n</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>]
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>][<span style="color:#a6e22e">n</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>] = <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>] = <span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>][:<span style="color:#a6e22e">n</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>]
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">first</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">first</span> = <span style="color:#a6e22e">d</span>
</span></span><span style="display:flex;"><span> } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">last</span>.<span style="color:#a6e22e">link</span> = <span style="color:#a6e22e">d</span>
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">last</span> = <span style="color:#a6e22e">d</span>
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">lock</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">sched</span>.<span style="color:#a6e22e">deferlock</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">last</span>.<span style="color:#a6e22e">link</span> = <span style="color:#a6e22e">sched</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>]
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">sched</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>] = <span style="color:#a6e22e">first</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">unlock</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">sched</span>.<span style="color:#a6e22e">deferlock</span>)
</span></span><span style="display:flex;"><span> })
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// 重置defer参数
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">siz</span> = <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">started</span> = <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">sp</span> = <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">pc</span> = <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">link</span> = <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>] = append(<span style="color:#a6e22e">pp</span>.<span style="color:#a6e22e">deferpool</span>[<span style="color:#a6e22e">sc</span>], <span style="color:#a6e22e">d</span>) <span style="color:#75715e">// 将当前defer放入P的defer缓冲池中
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></div><p>我们来看下<code>jmpdefer</code>实现:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>TEXT runtime·jmpdefer<span style="color:#f92672">(</span>SB<span style="color:#f92672">)</span>, NOSPLIT, $0-16
</span></span><span style="display:flex;"><span> MOVQ fv+0<span style="color:#f92672">(</span>FP<span style="color:#f92672">)</span>, DX <span style="color:#75715e"># DX寄存器存储jmpdefer第一个参数fnfn是funcval类型指针</span>
</span></span><span style="display:flex;"><span> MOVQ argp+8<span style="color:#f92672">(</span>FP<span style="color:#f92672">)</span>, BX <span style="color:#75715e"># BX寄存器存储jmpdefer第二个参数该参数是个指针类型指向arg0</span>
</span></span><span style="display:flex;"><span> LEAQ -8<span style="color:#f92672">(</span>BX<span style="color:#f92672">)</span>, SP <span style="color:#75715e"># 将BX存放的arg0的地址减少8获取得到调用deferreturn时栈顶地址此时栈顶存放的是deferreturn的return address最后将该地址存放在SP寄存器中</span>
</span></span><span style="display:flex;"><span> MOVQ -8<span style="color:#f92672">(</span>SP<span style="color:#f92672">)</span>, BP <span style="color:#75715e"># 重置BP寄存器</span>
</span></span><span style="display:flex;"><span> SUBQ $5, <span style="color:#f92672">(</span>SP<span style="color:#f92672">)</span> <span style="color:#75715e"># 此时SP寄存器指向的是deferreturn的return address。该指令是将调用deferreturn的return address减少5</span>
</span></span><span style="display:flex;"><span> <span style="color:#75715e"># 而减少5之后return adderss恰好指向了`CALL runtime.deferreturn(SB)`这就实现了deferreturn递归调用</span>
</span></span><span style="display:flex;"><span> MOVQ 0<span style="color:#f92672">(</span>DX<span style="color:#f92672">)</span>, BX <span style="color:#75715e"># DX存储的是fn其是funcval类型指针所以获取真正函数入口地址需要0(DX)该指令等效于BX = Mem[R[DX] + 0]。</span>
</span></span><span style="display:flex;"><span> <span style="color:#75715e"># 寄存器逻辑操作不了解的话可以参看前面Go汇编章节</span>
</span></span><span style="display:flex;"><span> JMP BX <span style="color:#75715e"># 通过JMP指令调用延迟函数</span>
</span></span></code></pre></div><p>从上面代码可以看出来jmpdefer通过汇编更改了延迟函数调用的return address使return address指向deferreturn入口地址这样当延迟函数执行完成之后会继续调用deferreturn函数从而实现了deferreturn递归调用。deferreturn和jmpdefer最后实现的逻辑的伪代码如下</p>
<pre tabindex="0"><code>function deferreturn() {
var arg int
for _, d := range deferLinkList {
arg = d.arg
d.fn(arg)
deferreturn()
}
}
</code></pre><p>画出deferreturn调用内存和栈的状态图帮助理解</p>
<figure class="text-center"><img src="https://static.cyub.vip/images/202106/jmpdefer.png" width="500px"/>
</figure>
<p>最后我们来探究一下deferreturn第二个终止条件考虑下面的场景</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">A</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">B</span>()
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">C</span>()
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">C</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">D</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>将上面代码转换成成底层实现的伪代码如下:</p>
<pre tabindex="0"><code>func A() {
deferproc(B) // 注册延迟函数B
deferproc(C) // 注册延迟函数C
deferreturn() // 开始执行延迟函数
}
func C() {
deferproc(D) // 注册延迟函数C
deferreturn() // 开始执行延迟函数
}
</code></pre><p>当调用A函数的deferreturn函数时会从defer链表中取出延迟函数C进行执行当执行C函数时其内部也有一个defer函数C函数最后也会调用deferreturn函数当C函数中调用deferreturn函数时defer链表结构如下</p>
<pre tabindex="0"><code> sp指向C的栈顶 sp指向A的栈顶
| |
| |
v v
g._defer ---------&gt; D --------&gt; B
</code></pre><p>当C中的deferreturn执行完defer链表中延迟函数D之后开始执行B的时候由于B的sp指向的是A的栈顶不等于C的栈顶此时满足终止条件2C中的deferreturn会退出执行此时A的deferreturn开始继续执行A的deferreturn调用其C的deferreturn函数相当于一个大循环里面套一个小循环现在是小循环退出了大循环还是会继续的此时由于B的sp指向就是A的栈顶B函数会执行。</p>
<p>deferreturn循环终止第二个条件就是为了解决诸于此类的场景。</p>
<h2 id="优化历程">
优化历程
<a class="anchor" href="#%e4%bc%98%e5%8c%96%e5%8e%86%e7%a8%8b">#</a>
</h2>
<p>上面我们分析的代码中defer结构是分配到堆上其实为了优化defer语法性能Go在实现过程可能会将defer结构分配在栈上。我们来看看Go各个版本对<code>defer</code>都做了哪些优化?</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">greet</span>()
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">greet</span>() {
</span></span><span style="display:flex;"><span> print(<span style="color:#e6db74">&#34;hello&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>我们以上面代码为例看看其在go1.12、go1.13、go1.14这几个版本下的核心汇编代码:</p>
<p>
<a href="https://go.godbolt.org/z/341cvP">go1.12版本</a></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span></span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8
</span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">9
</span></span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>leaq <span style="color:#e6db74">&#34;&#34;</span>.greet·f<span style="color:#f92672">(</span>SB<span style="color:#f92672">)</span>, AX
</span></span><span style="display:flex;"><span>pcdata $2, $0
</span></span><span style="display:flex;"><span>movq AX, 8<span style="color:#f92672">(</span>SP<span style="color:#f92672">)</span>
</span></span><span style="display:flex; background-color:#3c3d38"><span>call runtime.deferproc<span style="color:#f92672">(</span>SB<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>testl AX, AX
</span></span><span style="display:flex;"><span>jne main_pc73
</span></span><span style="display:flex;"><span>.loc <span style="color:#ae81ff">1</span> <span style="color:#ae81ff">5</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>xchgl AX, AX
</span></span><span style="display:flex; background-color:#3c3d38"><span>call runtime.deferreturn<span style="color:#f92672">(</span>SB<span style="color:#f92672">)</span></span></span></code></pre></td></tr></table>
</div>
</div>
<p>go1.12版本中通过调用 <code>runtime.deferproc</code> 函数将defer函数包装成 <code>_defer</code> 结构并注册到defer链表中<code>_defer</code> 结构体是分配在堆内存中,需要进行垃圾回收的。</p>
<p>
<a href="https://go.godbolt.org/z/z95336">go1.13版本</a></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span></span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span></span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>leaq <span style="color:#e6db74">&#34;&#34;</span>.greet·f<span style="color:#f92672">(</span>SB<span style="color:#f92672">)</span>, AX
</span></span><span style="display:flex;"><span>pcdata $0, $0
</span></span><span style="display:flex;"><span>movq AX, <span style="color:#e6db74">&#34;&#34;</span>..autotmp_0+32<span style="color:#f92672">(</span>SP<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>pcdata $0, $1
</span></span><span style="display:flex;"><span>leaq <span style="color:#e6db74">&#34;&#34;</span>..autotmp_0+8<span style="color:#f92672">(</span>SP<span style="color:#f92672">)</span>, AX
</span></span><span style="display:flex;"><span>pcdata $0, $0
</span></span><span style="display:flex;"><span>movq AX, <span style="color:#f92672">(</span>SP<span style="color:#f92672">)</span>
</span></span><span style="display:flex; background-color:#3c3d38"><span>call runtime.deferprocStack<span style="color:#f92672">(</span>SB<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>testl AX, AX
</span></span><span style="display:flex;"><span>jne main_pc83
</span></span><span style="display:flex;"><span>.loc <span style="color:#ae81ff">1</span> <span style="color:#ae81ff">5</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>xchgl AX, AX
</span></span><span style="display:flex; background-color:#3c3d38"><span>call runtime.deferreturn<span style="color:#f92672">(</span>SB<span style="color:#f92672">)</span></span></span></code></pre></td></tr></table>
</div>
</div>
<p>go1.13版本中通过调用 <code>runtime.deferprocStack</code> 函数将defer函数包装成 <code>_defer</code> 结构并注册到defer链表中<code>_defer</code> 结构体是分配在栈上不需要进行垃圾回收处理这个地方就是go1.13相比go1.12所做的优化点。</p>
<p>
<a href="https://go.godbolt.org/z/rGsc91">go1.14版本</a></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span></span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>leaq <span style="color:#e6db74">&#34;&#34;</span>.greet·f<span style="color:#f92672">(</span>SB<span style="color:#f92672">)</span>, AX
</span></span><span style="display:flex;"><span>pcdata $0, $0
</span></span><span style="display:flex;"><span>pcdata $1, $1
</span></span><span style="display:flex;"><span>movq AX, <span style="color:#e6db74">&#34;&#34;</span>..autotmp_1+8<span style="color:#f92672">(</span>SP<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>.loc <span style="color:#ae81ff">1</span> <span style="color:#ae81ff">5</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>movb $0, <span style="color:#e6db74">&#34;&#34;</span>..autotmp_0+7<span style="color:#f92672">(</span>SP<span style="color:#f92672">)</span>
</span></span><span style="display:flex; background-color:#3c3d38"><span>call <span style="color:#e6db74">&#34;&#34;</span>.greet<span style="color:#f92672">(</span>SB<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>movq 16<span style="color:#f92672">(</span>SP<span style="color:#f92672">)</span>, BP
</span></span><span style="display:flex;"><span>addq $24, SP
</span></span><span style="display:flex;"><span>ret
</span></span><span style="display:flex;"><span>call runtime.deferreturn<span style="color:#f92672">(</span>SB<span style="color:#f92672">)</span></span></span></code></pre></td></tr></table>
</div>
</div>
<p>go1.14版本不再调用<code>deferproc/deferprocStack</code> 函数来处理,而是在 <code>return</code> 返回之前直接调用该 <code>defer</code>函数即inline方式性能相比go1.13又得到进一步提升go官方把这种处理方式称为<code>open-coded defer</code>。实际上go1.14中禁止优化和内联之后defer函数其底层实现方式就和go1.13一样了。</p>
<p>需要注意的是 <code>open-coded defer</code> 使用是有限制的它不能用于for循环中的defer函数还有就是defer的数量也是有限制的
<a href="https://github.com/golang/go/blob/go1.14.13/src/cmd/compile/internal/gc/walk.go#L218-L223">最多支持8个defer函数</a>对于for循环或者数量过的defer将使用deferproc/deferprocStack方式实现。关于 <code>open-coded defer</code> 设计细节可以参见官方设计文档:
<a href="https://github.com/golang/proposal/blob/master/design/34481-opencoded-defers.md">Proposal: Low-cost defers through inline code, and extra funcdata to manage the panic case</a></p>
<p>此外 <code>open-coded defer</code> 虽大大提高了 <code>defer</code> 函数执行的性能,但 <code>panic</code><code>recover</code> 的执行性能会大大变慢,这是因为 <code>panic</code> 处理过程中会扫描 <code>open-coded defer</code> 的栈帧。具体参见<code>open-coded defer</code>
<a href="https://go-review.googlesource.com/c/go/&#43;/190098">代码提交记录</a><code>open-coded defer</code>带来的好处的是明显毕竟panic是比较少发生的。</p>
<p>go1.14也增加了 <code>-d defer</code> 编译选项,可以查看<code>defer</code>实现时候使用哪一种方式:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>go build -gcflags<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;-d defer&#34;</span> main.go
</span></span></code></pre></div><p>总结一下defer优化历程</p>
<table>
<thead>
<tr>
<th>版本</th>
<th>优化内容</th>
</tr>
</thead>
<tbody>
<tr>
<td>Go1.12及以前</td>
<td>defer分配到堆上是heap-allocated defer</td>
</tr>
<tr>
<td>Go1.13</td>
<td>支持在栈上分配defer结构减少堆上分配和GC的开销是stack-allocated defer</td>
</tr>
<tr>
<td>G01.14</td>
<td>支持开放式编码defer不再使用defer结构直接在函数尾部调用延迟函数是open-coded defer</td>
</tr>
</tbody>
</table>
<h2 id="进一步阅读">
进一步阅读
<a class="anchor" href="#%e8%bf%9b%e4%b8%80%e6%ad%a5%e9%98%85%e8%af%bb">#</a>
</h2>
<ul>
<li>
<a href="https://tpaschalis.github.io/defer-internals/">What is a defer? And how many can you run?</a></li>
</ul>
</article>
<footer class="book-footer">
<div class="flex flex-wrap justify-between">
</div>
<script>(function(){function e(e){const t=window.getSelection(),n=document.createRange();n.selectNodeContents(e),t.removeAllRanges(),t.addRange(n)}document.querySelectorAll("pre code").forEach(t=>{t.addEventListener("click",function(){if(window.getSelection().toString())return;e(t.parentElement),navigator.clipboard&&navigator.clipboard.writeText(t.parentElement.textContent)})})})()</script>
</footer>
<div class="book-comments">
<script src="https://giscus.app/client.js" data-repo="cyub/go-1.14.13"
data-repo-id="MDEwOlJlcG9zaXRvcnkzMzc2ODUyMzQ=" data-category="Announcements"
data-category-id="DIC_kwDOFCCq8s4CZ3BC"
data-mapping="pathname" data-strict="0" data-emit-metadata="0"
data-input-position="bottom" data-reactions-enabled="0"
data-lang="zh-CN" data-theme="preferred_color_scheme" crossorigin="anonymous" async>
</script>
<noscript>Please enable JavaScript to view the comments powered by giscus.</noscript>
</div>
<label for="menu-control" class="hidden book-menu-overlay"></label>
</div>
</main>
</body>
</html>