|
|
<!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++ return } 上面代码输出1,而不是2。
|
|
|
defer函数是按照后进先出的顺序执行 # func main() { for i := 1; i <= 5; i++ { defer fmt.Print(i) } } 上面代码输出54321,而不是12345。
|
|
|
defer函数可以读取和修改函数的命名返回值 # func main() { fmt.Println(test()) } func test() (i int) { defer func() { i++ }() return 100 } 上面代码输出输出101,而不是100或者1。
|
|
|
白话defer原理 # defer函数底层数据结构是_defer结构体,多个defer函数会构建成一个_defer链表,后面加入的defer函数会插入链表的头部,该链表链表头部会链接到G上。当函数执行完成返回的时候,会从_defer链表头部开始依次执行defer函数。这也就是defer函数执行时会LIFO的原因。_defer链接结构示意图如下:
|
|
|
.. image:: https://static.cyub.vip/images/202105/defer_profile.png :alt: defer结构示意图 :width: 450px :align: center 创建_defer结构体是需要进行内存分配的,为了减少分配_defer结构体时资源消耗,Go底层使用了defer缓冲池(defer pool),用来缓存上次使用完的_defer结构体,这样下次可以直接使用,不必再重新分配内存了。defer缓冲池一共有两级:per-P级defer缓冲池和全局defer缓冲池。当创建_defer结构体时候,优先从当前M关联的P的缓冲池中取得_defer结构体,即从per-P缓冲池中获取,这个过程是无锁操作。如果per-P缓冲池中没有,则在尝试从全局defer缓冲池获取,若也没有获取到,则重新分配一个新的_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++ return } 上面代码输出1,而不是2。
|
|
|
defer函数是按照后进先出的顺序执行 # func main() { for i := 1; i <= 5; i++ { defer fmt.Print(i) } } 上面代码输出54321,而不是12345。
|
|
|
defer函数可以读取和修改函数的命名返回值 # func main() { fmt.Println(test()) } func test() (i int) { defer func() { i++ }() return 100 } 上面代码输出输出101,而不是100或者1。
|
|
|
白话defer原理 # defer函数底层数据结构是_defer结构体,多个defer函数会构建成一个_defer链表,后面加入的defer函数会插入链表的头部,该链表链表头部会链接到G上。当函数执行完成返回的时候,会从_defer链表头部开始依次执行defer函数。这也就是defer函数执行时会LIFO的原因。_defer链接结构示意图如下:
|
|
|
.. image:: https://static.cyub.vip/images/202105/defer_profile.png :alt: defer结构示意图 :width: 450px :align: center 创建_defer结构体是需要进行内存分配的,为了减少分配_defer结构体时资源消耗,Go底层使用了defer缓冲池(defer pool),用来缓存上次使用完的_defer结构体,这样下次可以直接使用,不必再重新分配内存了。defer缓冲池一共有两级:per-P级defer缓冲池和全局defer缓冲池。当创建_defer结构体时候,优先从当前M关联的P的缓冲池中取得_defer结构体,即从per-P缓冲池中获取,这个过程是无锁操作。如果per-P缓冲池中没有,则在尝试从全局defer缓冲池获取,若也没有获取到,则重新分配一个新的_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.7e9d53d4a20eea8c87bf76a4502bd21aa041c1ef2adc7e37ffc5339c57accccd.js" integrity="sha256-fp1T1KIO6oyHv3akUCvSGqBBwe8q3H43/8UznFeszM0=" 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>准备篇</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>关键字,我们可以声明一个延迟执行函数,当调用者返回之前开始执行该函数,一般用来完成资源、锁、连接等释放工作,或者recover可能发生的panic。</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"><=</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>
|
|
|
<pre tabindex="0"><code class="language-eval_rst" data-lang="eval_rst">.. image:: https://static.cyub.vip/images/202105/defer_profile.png
|
|
|
:alt: defer结构示意图
|
|
|
:width: 450px
|
|
|
:align: center
|
|
|
</code></pre><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">"friend"</span>)
|
|
|
</span></span><span style="display:flex;"><span> println(<span style="color:#e6db74">"welcome"</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">"hello "</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">""</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">""</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">"friend"</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">"welcome\n"</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">"defer on system stack"</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">&</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">"deferproc: d.panic != nil after newdefer"</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> < 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">&&</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">&</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>]) < 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">&&</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">&</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> > <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"><=</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">&</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">&</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">&</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">>=</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>]) > 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">&</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">&</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第一个参数fn,fn是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>
|
|
|
<pre tabindex="0"><code class="language-eval_rst" data-lang="eval_rst">.. image:: https://static.cyub.vip/images/202106/jmpdefer.png
|
|
|
:alt: defer结构示意图
|
|
|
:width: 65%
|
|
|
:align: center
|
|
|
</code></pre><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 ---------> D --------> B
|
|
|
</code></pre><p>当C中的deferreturn执行完defer链表中延迟函数D之后,开始执行B的时候,由于B的sp指向的是A的栈顶,不等于C的栈顶,此时满足终止条件2,C中的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">"hello"</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>
|
|
|
<pre tabindex="0"><code class="language-eval_rst" data-lang="eval_rst">.. code-block::
|
|
|
:emphasize-lines: 4,9
|
|
|
|
|
|
leaq "".greet·f(SB), AX
|
|
|
pcdata $2, $0
|
|
|
movq AX, 8(SP)
|
|
|
call runtime.deferproc(SB)
|
|
|
testl AX, AX
|
|
|
jne main_pc73
|
|
|
.loc 1 5 0
|
|
|
xchgl AX, AX
|
|
|
call runtime.deferreturn(SB)
|
|
|
</code></pre><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>
|
|
|
<pre tabindex="0"><code class="language-eval_rst" data-lang="eval_rst">.. code-block::
|
|
|
:emphasize-lines: 8,13
|
|
|
|
|
|
leaq "".greet·f(SB), AX
|
|
|
pcdata $0, $0
|
|
|
movq AX, ""..autotmp_0+32(SP)
|
|
|
pcdata $0, $1
|
|
|
leaq ""..autotmp_0+8(SP), AX
|
|
|
pcdata $0, $0
|
|
|
movq AX, (SP)
|
|
|
call runtime.deferprocStack(SB)
|
|
|
testl AX, AX
|
|
|
jne main_pc83
|
|
|
.loc 1 5 0
|
|
|
xchgl AX, AX
|
|
|
call runtime.deferreturn(SB)
|
|
|
</code></pre><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>
|
|
|
<pre tabindex="0"><code class="language-eval_rst" data-lang="eval_rst">.. code-block::
|
|
|
:emphasize-lines: 7
|
|
|
|
|
|
leaq "".greet·f(SB), AX
|
|
|
pcdata $0, $0
|
|
|
pcdata $1, $1
|
|
|
movq AX, ""..autotmp_1+8(SP)
|
|
|
.loc 1 5 0
|
|
|
movb $0, ""..autotmp_0+7(SP)
|
|
|
call "".greet(SB)
|
|
|
movq 16(SP), BP
|
|
|
addq $24, SP
|
|
|
ret
|
|
|
call runtime.deferreturn(SB)
|
|
|
</code></pre><p>go1.14版本不再调用deferproc/deferprocStack函数来处理,而是在return返回之前直接调用该defer函数(即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>虽大大提高了defer函数执行的性能,但panic的recover的执行性能会大大变慢,这是因为panic处理过程中会扫描<code>open-coded defer</code>的栈帧。具体参见<code>open-coded defer</code>的
|
|
|
<a href="https://go-review.googlesource.com/c/go/+/190098">代码提交记录</a>。<code>open-coded defer</code>带来的好处的是明显,毕竟panic是比较少发生的。</p>
|
|
|
<p>go1.14也增加了<code>-d defer</code>编译选项,可以查看defer实现时候使用哪一种方式,用法示例:<code> go build -gcflags="-d defer" main.go</code></p>
|
|
|
<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>
|
|
|
<pre tabindex="0"><code class="language-eval_rst" data-lang="eval_rst">.. disqus::
|
|
|
</code></pre></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">
|
|
|
<div id="disqus_thread"></div>
|
|
|
<script type="application/javascript">
|
|
|
window.disqus_config = function () {
|
|
|
|
|
|
|
|
|
|
|
|
};
|
|
|
(function() {
|
|
|
if (["localhost", "127.0.0.1"].indexOf(window.location.hostname) != -1) {
|
|
|
document.getElementById('disqus_thread').innerHTML = 'Disqus comments not available by default when the website is previewed locally.';
|
|
|
return;
|
|
|
}
|
|
|
var d = document, s = d.createElement('script'); s.async = true;
|
|
|
s.src = '//' + "go-cyub-vip" + '.disqus.com/embed.js';
|
|
|
s.setAttribute('data-timestamp', +new Date());
|
|
|
(d.head || d.body).appendChild(s);
|
|
|
})();
|
|
|
</script>
|
|
|
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
|
|
|
<a href="https://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<label for="menu-control" class="hidden book-menu-overlay"></label>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
</main>
|
|
|
|
|
|
|
|
|
</body>
|
|
|
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|