├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── Bug_report.md │ └── Question.md └── workflows │ └── website.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── archetypes └── default.md ├── config.toml ├── content ├── _index.md ├── assets │ ├── cover.png │ └── qrcode.jpg ├── channel │ ├── 1-什么是 CSP.md │ ├── 10-channel 在什么情况下会引起资源泄漏.md │ ├── 11-关于 channel 的 happened-before 有哪些.md │ ├── 12-channel 有哪些应用.md │ ├── 2-channel 底层的数据结构是什么.md │ ├── 3-向 channel 发送数据的过程是怎样的.md │ ├── 4-从 channel 接收数据的过程是怎样的.md │ ├── 5-关闭一个 channel 的过程是怎样的.md │ ├── 6-从一个关闭的 channel 仍然能读出数据吗.md │ ├── 7-操作 channel 的情况总结.md │ ├── 8-如何优雅地关闭 channel.md │ ├── 9-channel 发送和接收元素的本质是什么.md │ ├── _index.md │ └── assets │ │ ├── 0.png │ │ ├── 1.png │ │ ├── 10.png │ │ ├── 11.png │ │ ├── 12.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ └── 9.png ├── compile │ ├── 1-逃逸分析是怎么进行的.md │ ├── 2-GoRoot 和 GoPath 有什么用.md │ ├── 3-Go 编译链接过程概述.md │ ├── 4-Go 编译相关的命令详解.md │ ├── 5-Go 程序启动过程是怎样的.md │ ├── _index.md │ └── assets │ │ ├── 0.png │ │ ├── 1.png │ │ ├── 10.png │ │ ├── 11.png │ │ ├── 12.png │ │ ├── 13.png │ │ ├── 14.png │ │ ├── 15.png │ │ ├── 16.png │ │ ├── 17.png │ │ ├── 18.png │ │ ├── 19.png │ │ ├── 2.png │ │ ├── 20.png │ │ ├── 21.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ └── 9.png ├── errata.md ├── interface │ ├── 1-Go 语言与鸭子类型的关系.md │ ├── 10-Go 接口与 C++ 接口有何异同.md │ ├── 2-值接收者和指针接收者的区别.md │ ├── 3-iface 和 eface 的区别是什么.md │ ├── 4-接口的动态类型和动态值.md │ ├── 5-编译器自动检测类型是否实现接口.md │ ├── 6-接口的构造过程是怎样的.md │ ├── 7-类型转换和断言的区别.md │ ├── 8-接口转换的原理.md │ ├── 9-如何用 interface 实现多态.md │ ├── _index.md │ └── assets │ │ ├── 0.png │ │ └── 1.png ├── map │ ├── 1-map的底层实现原理是什么.md │ ├── 10-可以对 map 的元素取地址吗.md │ ├── 11-如何比较两个 map 相等.md │ ├── 12-map 是线程安全的吗.md │ ├── 2-如何实现两种 get 操作.md │ ├── 3-map 的遍历过程是怎样的.md │ ├── 4-map 的赋值过程是怎样的.md │ ├── 5-map 的删除过程是怎样的.md │ ├── 6-map 的扩容过程是怎样的.md │ ├── 7-map 中的 key 为什么是无序的.md │ ├── 8-float 类型可以作为 map 的 key 吗.md │ ├── 9-可以边遍历边删除吗.md │ ├── _index.md │ └── assets │ │ ├── 0.png │ │ ├── 1.png │ │ ├── 10.png │ │ ├── 11.png │ │ ├── 12.png │ │ ├── 13.png │ │ ├── 14.png │ │ ├── 15.png │ │ ├── 16.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ └── 9.png ├── memgc │ ├── 1-垃圾回收的认识.md │ ├── 2-垃圾回收机制的实现.md │ ├── 3-垃圾回收的优化问题.md │ ├── 4-历史及演进.md │ ├── 5-总结.md │ ├── _index.md │ ├── assets │ │ ├── gc-blueprint.png │ │ ├── gc-leak1.png │ │ ├── gc-mark-assist.png │ │ ├── gc-mark-sweep.png │ │ ├── gc-mutator.png │ │ ├── gc-pacing.png │ │ ├── gc-process.png │ │ ├── gc-trace.png │ │ ├── gc-trace2.png │ │ ├── gc-trigger.png │ │ ├── gc-trigger2.png │ │ ├── gc-trigger3.png │ │ ├── gc-tuning-ex1-1.png │ │ ├── gc-tuning-ex1-2.png │ │ ├── gc-tuning-ex1-3.png │ │ ├── gc-tuning-ex1-4.png │ │ ├── gc-tuning-ex2-1.png │ │ ├── gc-tuning-ex2-2.png │ │ ├── gc-tuning-ex2-3.png │ │ ├── gc-tuning-ex3.png │ │ ├── gc-wb-dijkstra.png │ │ ├── gc-wb-yuasa.png │ │ ├── gc1.png │ │ ├── gc2.png │ │ └── gc3.png │ └── code │ │ ├── 5 │ │ └── main.go │ │ ├── 6 │ │ ├── gc.txt │ │ └── main.go │ │ ├── 7 │ │ └── main.go │ │ ├── 11 │ │ └── main.go │ │ ├── 14 │ │ ├── 1 │ │ │ ├── after │ │ │ │ └── main.go │ │ │ └── before │ │ │ │ └── main.go │ │ └── 2 │ │ │ ├── after │ │ │ └── main.go │ │ │ └── before │ │ │ └── main.go │ │ └── 20 │ │ ├── 1.go │ │ ├── 4.txt │ │ └── 4_test.go ├── sched │ ├── 1-goroutine和线程的区别.md │ ├── 10-schedule 循环如何启动.md │ ├── 11-goroutine 如何退出.md │ ├── 12-schedule 循环如何运转.md │ ├── 13-M 如何找工作.md │ ├── 14-sysmon 后台监控线程做了什么.md │ ├── 15-一个调度相关的陷阱.md │ ├── 2-什么是 go scheduler.md │ ├── 3-goroutine 调度时机有哪些.md │ ├── 4-什么是M:N模型.md │ ├── 5-什么是workstealing.md │ ├── 6-GPM 是什么.md │ ├── 7-描述 scheduler 的初始化过程.md │ ├── 8-main goroutine 如何创建.md │ ├── 9-g0 栈何用户栈如何切换.md │ ├── _index.md │ └── assets │ │ ├── 0.png │ │ ├── 1.png │ │ ├── 10.png │ │ ├── 11.png │ │ ├── 12.png │ │ ├── 13.png │ │ ├── 14.png │ │ ├── 15.png │ │ ├── 16.png │ │ ├── 17.png │ │ ├── 18.png │ │ ├── 19.png │ │ ├── 2.png │ │ ├── 20.png │ │ ├── 21.png │ │ ├── 22.png │ │ ├── 23.png │ │ ├── 24.png │ │ ├── 25.png │ │ ├── 26.png │ │ ├── 27.png │ │ ├── 28.png │ │ ├── 29.png │ │ ├── 3.png │ │ ├── 30.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ └── 9.png ├── slice │ ├── 1-数组和切片有什么异同.md │ ├── 2-切片的容量是怎样增长的.md │ ├── 3-切片作为函数参数.md │ ├── _index.md │ └── assets │ │ ├── 0.png │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ └── 4.png └── stdlib │ ├── _index.md │ ├── context │ ├── 1-context 是什么.md │ ├── 2-context 有什么作用.md │ ├── 3-context.Value 的查找过程是怎样的.md │ ├── 4-context 如何被取消.md │ ├── _index.md │ └── assets │ │ ├── 0.png │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ └── 5.png │ ├── reflect │ ├── 1-什么是反射.md │ ├── 2-什么情况下需要使用反射.md │ ├── 3-Go 语言如何实现反射.md │ ├── 4-Go 语言中反射有哪些应用.md │ ├── 5-如何比较两个对象完全相同.md │ ├── _index.md │ └── assets │ │ ├── 0.png │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ └── 7.png │ └── unsafe │ ├── 1-Go指针和unsafe.Pointer有什么区别.md │ ├── 2-如何利用unsafe获取slice&map的长度.md │ ├── 3-如何利用unsafe包修改私有成员.md │ ├── 4-如何实现字符串和byte切片的零拷贝转换.md │ ├── _index.md │ └── assets │ └── 0.png ├── scripts └── downloader.go └── themes └── book ├── LICENSE ├── archetypes ├── docs.md └── posts.md ├── assets ├── _custom.scss ├── _defaults.scss ├── _fonts.scss ├── _main.scss ├── _markdown.scss ├── _print.scss ├── _shortcodes.scss ├── _utils.scss ├── _variables.scss ├── book.scss ├── manifest.json ├── menu-reset.js ├── mermaid.json ├── normalize.css ├── plugins │ ├── _numbered.scss │ └── _scrollbars.scss ├── search-data.js ├── search.js ├── sw-register.js ├── sw.js └── themes │ ├── _auto.scss │ ├── _dark.scss │ └── _light.scss ├── i18n └── zh.yaml ├── layouts ├── 404.html ├── _default │ ├── _markup │ │ ├── render-heading.html │ │ ├── render-image.html │ │ └── render-link.html │ ├── baseof.html │ ├── list.html │ └── single.html ├── partials │ └── docs │ │ ├── brand.html │ │ ├── comments.html │ │ ├── date.html │ │ ├── footer.html │ │ ├── header.html │ │ ├── html-head.html │ │ ├── inject │ │ ├── body.html │ │ ├── content-after.html │ │ ├── content-before.html │ │ ├── footer.html │ │ ├── head.html │ │ ├── menu-after.html │ │ ├── menu-before.html │ │ ├── toc-after.html │ │ └── toc-before.html │ │ ├── languages.html │ │ ├── menu-bundle.html │ │ ├── menu-filetree.html │ │ ├── menu-hugo.html │ │ ├── menu.html │ │ ├── post-meta.html │ │ ├── search.html │ │ ├── taxonomy.html │ │ ├── title.html │ │ └── toc.html ├── posts │ ├── list.html │ └── single.html ├── shortcodes │ ├── button.html │ ├── columns.html │ ├── details.html │ ├── expand.html │ ├── hint.html │ ├── katex.html │ ├── mermaid.html │ ├── section.html │ ├── tab.html │ └── tabs.html └── taxonomy │ ├── list.html │ └── taxonomy.html ├── static ├── favicon.png ├── favicon.svg ├── flexsearch.min.js ├── fonts │ ├── roboto-mono-v6-latin-regular.woff │ ├── roboto-mono-v6-latin-regular.woff2 │ ├── roboto-v19-latin-300italic.woff │ ├── roboto-v19-latin-300italic.woff2 │ ├── roboto-v19-latin-700.woff │ ├── roboto-v19-latin-700.woff2 │ ├── roboto-v19-latin-regular.woff │ └── roboto-v19-latin-regular.woff2 ├── katex │ ├── auto-render.min.js │ ├── fonts │ │ ├── KaTeX_AMS-Regular.ttf │ │ ├── KaTeX_AMS-Regular.woff │ │ ├── KaTeX_AMS-Regular.woff2 │ │ ├── KaTeX_Caligraphic-Bold.ttf │ │ ├── KaTeX_Caligraphic-Bold.woff │ │ ├── KaTeX_Caligraphic-Bold.woff2 │ │ ├── KaTeX_Caligraphic-Regular.ttf │ │ ├── KaTeX_Caligraphic-Regular.woff │ │ ├── KaTeX_Caligraphic-Regular.woff2 │ │ ├── KaTeX_Fraktur-Bold.ttf │ │ ├── KaTeX_Fraktur-Bold.woff │ │ ├── KaTeX_Fraktur-Bold.woff2 │ │ ├── KaTeX_Fraktur-Regular.ttf │ │ ├── KaTeX_Fraktur-Regular.woff │ │ ├── KaTeX_Fraktur-Regular.woff2 │ │ ├── KaTeX_Main-Bold.ttf │ │ ├── KaTeX_Main-Bold.woff │ │ ├── KaTeX_Main-Bold.woff2 │ │ ├── KaTeX_Main-BoldItalic.ttf │ │ ├── KaTeX_Main-BoldItalic.woff │ │ ├── KaTeX_Main-BoldItalic.woff2 │ │ ├── KaTeX_Main-Italic.ttf │ │ ├── KaTeX_Main-Italic.woff │ │ ├── KaTeX_Main-Italic.woff2 │ │ ├── KaTeX_Main-Regular.ttf │ │ ├── KaTeX_Main-Regular.woff │ │ ├── KaTeX_Main-Regular.woff2 │ │ ├── KaTeX_Math-BoldItalic.ttf │ │ ├── KaTeX_Math-BoldItalic.woff │ │ ├── KaTeX_Math-BoldItalic.woff2 │ │ ├── KaTeX_Math-Italic.ttf │ │ ├── KaTeX_Math-Italic.woff │ │ ├── KaTeX_Math-Italic.woff2 │ │ ├── KaTeX_SansSerif-Bold.ttf │ │ ├── KaTeX_SansSerif-Bold.woff │ │ ├── KaTeX_SansSerif-Bold.woff2 │ │ ├── KaTeX_SansSerif-Italic.ttf │ │ ├── KaTeX_SansSerif-Italic.woff │ │ ├── KaTeX_SansSerif-Italic.woff2 │ │ ├── KaTeX_SansSerif-Regular.ttf │ │ ├── KaTeX_SansSerif-Regular.woff │ │ ├── KaTeX_SansSerif-Regular.woff2 │ │ ├── KaTeX_Script-Regular.ttf │ │ ├── KaTeX_Script-Regular.woff │ │ ├── KaTeX_Script-Regular.woff2 │ │ ├── KaTeX_Size1-Regular.ttf │ │ ├── KaTeX_Size1-Regular.woff │ │ ├── KaTeX_Size1-Regular.woff2 │ │ ├── KaTeX_Size2-Regular.ttf │ │ ├── KaTeX_Size2-Regular.woff │ │ ├── KaTeX_Size2-Regular.woff2 │ │ ├── KaTeX_Size3-Regular.ttf │ │ ├── KaTeX_Size3-Regular.woff │ │ ├── KaTeX_Size3-Regular.woff2 │ │ ├── KaTeX_Size4-Regular.ttf │ │ ├── KaTeX_Size4-Regular.woff │ │ ├── KaTeX_Size4-Regular.woff2 │ │ ├── KaTeX_Typewriter-Regular.ttf │ │ ├── KaTeX_Typewriter-Regular.woff │ │ └── KaTeX_Typewriter-Regular.woff2 │ ├── katex.min.css │ └── katex.min.js ├── mermaid.min.js └── svg │ ├── calendar.svg │ ├── edit.svg │ ├── menu.svg │ ├── toc.svg │ └── translate.svg └── theme.toml /.gitattributes: -------------------------------------------------------------------------------- 1 | *.* linguist-language=Go -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: 书籍勘误 4 | --- 5 | 6 | ## 实际描述 7 | 8 | - 原文页码: 9 | - 原文段落: 10 | 11 | ``` 12 | 复制原文段落 13 | ``` 14 | 15 | ## 预期描述 16 | 17 | ``` 18 | 修改后的段落 19 | ``` 20 | 21 | ## 附图 22 | 23 | 必要时,请附上相关页面的照片或者截图 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: 提交问题 4 | 5 | --- 6 | 7 | ## 问题描述 8 | 9 | 请在此描述你的问题,提问前请参考[提问的智慧](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/master/README-zh_CN.md) -------------------------------------------------------------------------------- /.github/workflows/website.yml: -------------------------------------------------------------------------------- 1 | name: Website 2 | on: 3 | push: 4 | branches: [ main ] 5 | jobs: 6 | 7 | build: 8 | name: Website 9 | runs-on: ubuntu-latest 10 | steps: 11 | 12 | - name: Set up Go 1.16 13 | uses: actions/setup-go@v1 14 | with: 15 | go-version: 1.16 16 | id: go 17 | 18 | - name: Setup Hugo 19 | uses: peaceiris/actions-hugo@v2 20 | with: 21 | hugo-version: '0.80.0' 22 | extended: true 23 | 24 | - name: Check out code into the Go module directory 25 | uses: actions/checkout@v1 26 | 27 | - name: Build Website 28 | env: 29 | USER: ${{ secrets.SERVER_USER }} 30 | TARGET: ${{ secrets.SERVER_PATH }} 31 | KEY: ${{ secrets.SERVER_KEY }} 32 | DOMAIN: ${{ secrets.SERVER_DOMAIN }} 33 | run: | 34 | make 35 | mkdir ~/.ssh 36 | echo "$KEY" | tr -d '\r' > ~/.ssh/id_ed25519 37 | chmod 400 ~/.ssh/id_ed25519 38 | eval "$(ssh-agent -s)" 39 | ssh-add ~/.ssh/id_ed25519 40 | ssh-keyscan -H $DOMAIN >> ~/.ssh/known_hosts 41 | scp -r public/* $USER@$DOMAIN:$TARGET -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | log/ 3 | 4 | *.xml 5 | *.iml 6 | *.idea 7 | 8 | *.DS_Store 9 | 10 | 11 | # Hugo website 12 | resources 13 | public 14 | data 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | hugo 3 | s: 4 | hugo server -D 5 | clean: 6 | rm -rf public data -------------------------------------------------------------------------------- /archetypes/default.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 10000 3 | title: "新的稿件" 4 | slug: /new 5 | draft: true 6 | --- 7 | 8 | -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | baseURL = "http://golang.design/go-questions" 2 | languageCode = "zh-CN" 3 | title = "Go 程序员面试笔试宝典" 4 | theme = "book" 5 | 6 | [markup.highlight] 7 | codeFences = true 8 | guessSyntax = false 9 | hl_Lines = "" 10 | lineNoStart = 1 11 | lineNos = true 12 | lineNumbersInTable = true 13 | noClasses = true 14 | style = "vs" 15 | tabWidth = 4 16 | [markup.goldmark.renderer] 17 | unsafe= true 18 | -------------------------------------------------------------------------------- /content/assets/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/assets/cover.png -------------------------------------------------------------------------------- /content/assets/qrcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/assets/qrcode.jpg -------------------------------------------------------------------------------- /content/channel/1-什么是 CSP.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 401 3 | title: "什么是 CSP" 4 | slug: /csp 5 | --- 6 | 7 | > Do not communicate by sharing memory; instead, share memory by communicating. 8 | 9 | 不要通过共享内存来通信,而要通过通信来实现内存共享。 10 | 11 | 这就是 Go 的并发哲学,它依赖 CSP 模型,基于 channel 实现。 12 | 13 | CSP 经常被认为是 Go 在并发编程上成功的关键因素。CSP 全称是 “Communicating Sequential Processes”,这也是 Tony Hoare 在 1978 年发表在 ACM 的一篇论文。论文里指出一门编程语言应该重视 input 和 output 的原语,尤其是并发编程的代码。 14 | 15 | 在那篇文章发表的时代,人们正在研究模块化编程的思想,该不该用 goto 语句在当时是最激烈的议题。彼时,面向对象编程的思想正在崛起,几乎没什么人关心并发编程。 16 | 17 | 在文章中,CSP 也是一门自定义的编程语言,作者定义了输入输出语句,用于 processes 间的通信(communication)。processes 被认为是需要输入驱动,并且产生输出,供其他 processes 消费,processes 可以是进程、线程、甚至是代码块。输入命令是:!,用来向 processes 写入;输出是:?,用来从 processes 读出。这篇文章要讲的 channel 正是借鉴了这一设计。 18 | 19 | Hoare 还提出了一个 -> 命令,如果 -> 左边的语句返回 false,那它右边的语句就不会执行。 20 | 21 | 通过这些输入输出命令,Hoare 证明了如果一门编程语言中把 processes 间的通信看得第一等重要,那么并发编程的问题就会变得简单。 22 | 23 | Go 是第一个将 CSP 的这些思想引入,并且发扬光大的语言。仅管内存同步访问控制(原文是 memory access synchronization)在某些情况下大有用处,Go 里也有相应的 sync 包支持,但是这在大型程序很容易出错。 24 | 25 | Go 一开始就把 CSP 的思想融入到语言的核心里,所以并发编程成为 Go 的一个独特的优势,而且很容易理解。 26 | 27 | 大多数的编程语言的并发编程模型是基于线程和内存同步访问控制,Go 的并发编程的模型则用 goroutine 和 channel 来替代。Goroutine 和线程类似,channel 和 mutex (用于内存同步访问控制)类似。 28 | 29 | Goroutine 解放了程序员,让我们更能贴近业务去思考问题。而不用考虑各种像线程库、线程开销、线程调度等等这些繁琐的底层问题,goroutine 天生替你解决好了。 30 | 31 | Channel 则天生就可以和其他 channel 组合。我们可以把收集各种子系统结果的 channel 输入到同一个 channel。Channel 还可以和 select, cancel, timeout 结合起来。而 mutex 就没有这些功能。 32 | 33 | Go 的并发原则非常优秀,目标就是简单:尽量使用 channel;把 goroutine 当作免费的资源,随便用。 34 | -------------------------------------------------------------------------------- /content/channel/10-channel 在什么情况下会引起资源泄漏.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 410 3 | title: "channel 在什么情况下会引起资源泄漏" 4 | slug: /leak 5 | --- 6 | 7 | Channel 可能会引发 goroutine 泄漏。 8 | 9 | 泄漏的原因是 goroutine 操作 channel 后,处于发送或接收阻塞状态,而 channel 处于满或空的状态,一直得不到改变。同时,垃圾回收器也不会回收此类资源,进而导致 gouroutine 会一直处于等待队列中,不见天日。 10 | 11 | 另外,程序运行过程中,对于一个 channel,如果没有任何 goroutine 引用了,gc 会对其进行回收操作,不会引起内存泄漏。 12 | -------------------------------------------------------------------------------- /content/channel/11-关于 channel 的 happened-before 有哪些.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 411 3 | title: "关于 channel 的 happened-before 有哪些" 4 | slug: /happens-before 5 | --- 6 | 7 | 维基百科上给的定义: 8 | 9 | > In computer science, the happened-before relation (denoted: ->) is a relation between the result of two events, such that if one event should happen before another event, the result must reflect that, even if those events are in reality executed out of order (usually to optimize program flow). 10 | 11 | 简单来说就是如果事件 a 和事件 b 存在 happened-before 关系,即 a -> b,那么 a,b 完成后的结果一定要体现这种关系。由于现代编译器、CPU 会做各种优化,包括编译器重排、内存重排等等,在并发代码里,happened-before 限制就非常重要了。 12 | 13 | 根据晃岳攀老师在 Gopher China 2019 上的并发编程分享,关于 channel 的发送(send)、发送完成(send finished)、接收(receive)、接收完成(receive finished)的 happened-before 关系如下: 14 | 15 | 1. 第 n 个 `send` 一定 `happened before` 第 n 个 `receive finished`,无论是缓冲型还是非缓冲型的 channel。 16 | 2. 对于容量为 m 的缓冲型 channel,第 n 个 `receive` 一定 `happened before` 第 n+m 个 `send finished`。 17 | 3. 对于非缓冲型的 channel,第 n 个 `receive` 一定 `happened before` 第 n 个 `send finished`。 18 | 4. channel close 一定 `happened before` receiver 得到通知。 19 | 20 | 我们来逐条解释一下。 21 | 22 | 第一条,我们从源码的角度看也是对的,send 不一定是 `happened before` receive,因为有时候是先 receive,然后 goroutine 被挂起,之后被 sender 唤醒,send happened after receive。但不管怎样,要想完成接收,一定是要先有发送。 23 | 24 | 第二条,缓冲型的 channel,当第 n+m 个 send 发生后,有下面两种情况: 25 | 26 | 若第 n 个 receive 没发生。这时,channel 被填满了,send 就会被阻塞。那当第 n 个 receive 发生时,sender goroutine 会被唤醒,之后再继续发送过程。这样,第 n 个 `receive` 一定 `happened before` 第 n+m 个 `send finished`。 27 | 28 | 若第 n 个 receive 已经发生过了,这直接就符合了要求。 29 | 30 | 第三条,也是比较好理解的。第 n 个 send 如果被阻塞,sender goroutine 挂起,第 n 个 receive 这时到来,先于第 n 个 send finished。如果第 n 个 send 未被阻塞,说明第 n 个 receive 早就在那等着了,它不仅 happened before send finished,它还 happened before send。 31 | 32 | 第四条,回忆一下源码,先设置完 closed = 1,再唤醒等待的 receiver,并将零值拷贝给 receiver。 33 | 34 | 参考资料【鸟窝 并发编程分享】这篇博文的评论区有 PPT 的下载链接,这是晁老师在 Gopher 2019 大会上的演讲。 35 | 36 | 关于 happened before,这里再介绍一个柴大和曹大的新书《Go 语言高级编程》里面提到的一个例子。 37 | 38 | 书中 1.5 节先讲了顺序一致性的内存模型,这是并发编程的基础。 39 | 40 | 我们直接来看例子: 41 | 42 | ```golang 43 | var done = make(chan bool) 44 | var msg string 45 | 46 | func aGoroutine() { 47 | msg = "hello, world" 48 | done <- true 49 | } 50 | 51 | func main() { 52 | go aGoroutine() 53 | <-done 54 | println(msg) 55 | } 56 | ``` 57 | 58 | 先定义了一个 done channel 和一个待打印的字符串。在 main 函数里,启动一个 goroutine,等待从 done 里接收到一个值后,执行打印 msg 的操作。如果 main 函数中没有 `<-done` 这行代码,打印出来的 msg 为空,因为 aGoroutine 来不及被调度,还来不及给 msg 赋值,主程序就会退出。而在 Go 语言里,主协程退出时不会等待其他协程。 59 | 60 | 加了 `<-done` 这行代码后,就会阻塞在此。等 aGoroutine 里向 done 发送了一个值之后,才会被唤醒,继续执行打印 msg 的操作。而这在之前,msg 已经被赋值过了,所以会打印出 `hello, world`。 61 | 62 | 这里依赖的 happened before 就是前面讲的第一条。第一个 send 一定 happened before 第一个 receive finished,即 `done <- true` 先于 `<-done` 发生,这意味着 main 函数里执行完 `<-done` 后接着执行 `println(msg)` 这一行代码时,msg 已经被赋过值了,所以会打印出想要的结果。 63 | 64 | 进一步利用前面提到的第 3 条 happened before 规则,修改一下代码: 65 | 66 | ```golang 67 | var done = make(chan bool) 68 | var msg string 69 | 70 | func aGoroutine() { 71 | msg = "hello, world" 72 | <-done 73 | } 74 | 75 | func main() { 76 | go aGoroutine() 77 | done <- true 78 | println(msg) 79 | } 80 | ``` 81 | 82 | 同样可以得到相同的结果,为什么?根据第三条规则,对于非缓冲型的 channel,第一个 receive 一定 happened before 第一个 send finished。也就是说, 83 | 在 `done <- true` 完成之前,`<-done` 就已经发生了,也就意味着 msg 已经被赋上值了,最终也会打印出 `hello, world`。 84 | -------------------------------------------------------------------------------- /content/channel/12-channel 有哪些应用.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 412 3 | title: "channel 有哪些应用" 4 | slug: /application 5 | --- 6 | 7 | Channel 和 goroutine 的结合是 Go 并发编程的大杀器。而 Channel 的实际应用也经常让人眼前一亮,通过与 select,cancel,timer 等结合,它能实现各种各样的功能。接下来,我们就要梳理一下 channel 的应用。 8 | 9 | # 停止信号 10 | “如何优雅地关闭 channel”那一节已经讲得很多了,这块就略过了。 11 | 12 | channel 用于停止信号的场景还是挺多的,经常是关闭某个 channel 或者向 channel 发送一个元素,使得接收 channel 的那一方获知道此信息,进而做一些其他的操作。 13 | 14 | # 任务定时 15 | 16 | 与 timer 结合,一般有两种玩法:实现超时控制,实现定期执行某个任务。 17 | 18 | 有时候,需要执行某项操作,但又不想它耗费太长时间,上一个定时器就可以搞定: 19 | 20 | ```golang 21 | select { 22 | case <-time.After(100 * time.Millisecond): 23 | case <-s.stopc: 24 | return false 25 | } 26 | ``` 27 | 28 | 等待 100 ms 后,如果 s.stopc 还没有读出数据或者被关闭,就直接结束。这是来自 etcd 源码里的一个例子,这样的写法随处可见。 29 | 30 | 定时执行某个任务,也比较简单: 31 | 32 | ```golang 33 | func worker() { 34 | ticker := time.Tick(1 * time.Second) 35 | for { 36 | select { 37 | case <- ticker: 38 | // 执行定时任务 39 | fmt.Println("执行 1s 定时任务") 40 | } 41 | } 42 | } 43 | ``` 44 | 45 | 每隔 1 秒种,执行一次定时任务。 46 | 47 | # 解耦生产方和消费方 48 | 49 | 服务启动时,启动 n 个 worker,作为工作协程池,这些协程工作在一个 `for {}` 无限循环里,从某个 channel 消费工作任务并执行: 50 | 51 | ```golang 52 | func main() { 53 | taskCh := make(chan int, 100) 54 | go worker(taskCh) 55 | 56 | // 塞任务 57 | for i := 0; i < 10; i++ { 58 | taskCh <- i 59 | } 60 | 61 | // 等待 1 小时 62 | select { 63 | case <-time.After(time.Hour): 64 | } 65 | } 66 | 67 | func worker(taskCh <-chan int) { 68 | const N = 5 69 | // 启动 5 个工作协程 70 | for i := 0; i < N; i++ { 71 | go func(id int) { 72 | for { 73 | task := <- taskCh 74 | fmt.Printf("finish task: %d by worker %d\n", task, id) 75 | time.Sleep(time.Second) 76 | } 77 | }(i) 78 | } 79 | } 80 | ``` 81 | 82 | 5 个工作协程在不断地从工作队列里取任务,生产方只管往 channel 发送任务即可,解耦生产方和消费方。 83 | 84 | 程序输出: 85 | 86 | ```shell 87 | finish task: 1 by worker 4 88 | finish task: 2 by worker 2 89 | finish task: 4 by worker 3 90 | finish task: 3 by worker 1 91 | finish task: 0 by worker 0 92 | finish task: 6 by worker 0 93 | finish task: 8 by worker 3 94 | finish task: 9 by worker 1 95 | finish task: 7 by worker 4 96 | finish task: 5 by worker 2 97 | ``` 98 | 99 | # 控制并发数 100 | 101 | 有时需要定时执行几百个任务,例如每天定时按城市来执行一些离线计算的任务。但是并发数又不能太高,因为任务执行过程依赖第三方的一些资源,对请求的速率有限制。这时就可以通过 channel 来控制并发数。 102 | 103 | 下面的例子来自《Go 语言高级编程》: 104 | 105 | ```golang 106 | var limit = make(chan int, 3) 107 | 108 | func main() { 109 | // ………… 110 | for _, w := range work { 111 | go func() { 112 | limit <- 1 113 | w() 114 | <-limit 115 | }() 116 | } 117 | // ………… 118 | } 119 | ``` 120 | 121 | 构建一个缓冲型的 channel,容量为 3。接着遍历任务列表,每个任务启动一个 goroutine 去完成。真正执行任务,访问第三方的动作在 w() 中完成,在执行 w() 之前,先要从 limit 中拿“许可证”,拿到许可证之后,才能执行 w(),并且在执行完任务,要将“许可证”归还。这样就可以控制同时运行的 goroutine 数。 122 | 123 | 这里,`limit <- 1` 放在 func 内部而不是外部,原因是: 124 | 125 | > 如果在外层,就是控制系统 goroutine 的数量,可能会阻塞 for 循环,影响业务逻辑。 126 | 127 | >limit 其实和逻辑无关,只是性能调优,放在内层和外层的语义不太一样。 128 | 129 | 还有一点要注意的是,如果 w() 发生 panic,那“许可证”可能就还不回去了,因此需要使用 defer 来保证。 130 | 131 | # 参考资料 132 | 133 | 【channel 应用】https://www.s0nnet.com/archives/go-channels-practice 134 | 135 | 【应用举例】https://zhuyasen.com/post/go_queue.html 136 | 137 | 【应用】https://tonybai.com/2014/09/29/a-channel-compendium-for-golang/ 138 | 139 | 【Go 语言高级并发编程】https://chai2010.cn/advanced-go-programming-book/ 140 | -------------------------------------------------------------------------------- /content/channel/2-channel 底层的数据结构是什么.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 402 3 | title: "channel 底层的数据结构是什么" 4 | slug: /struct 5 | --- 6 | 7 | # 数据结构 8 | 底层数据结构需要看源码,版本为 go 1.9.2: 9 | 10 | ```golang 11 | type hchan struct { 12 | // chan 里元素数量 13 | qcount uint 14 | // chan 底层循环数组的长度 15 | dataqsiz uint 16 | // 指向底层循环数组的指针 17 | // 只针对有缓冲的 channel 18 | buf unsafe.Pointer 19 | // chan 中元素大小 20 | elemsize uint16 21 | // chan 是否被关闭的标志 22 | closed uint32 23 | // chan 中元素类型 24 | elemtype *_type // element type 25 | // 已发送元素在循环数组中的索引 26 | sendx uint // send index 27 | // 已接收元素在循环数组中的索引 28 | recvx uint // receive index 29 | // 等待接收的 goroutine 队列 30 | recvq waitq // list of recv waiters 31 | // 等待发送的 goroutine 队列 32 | sendq waitq // list of send waiters 33 | 34 | // 保护 hchan 中所有字段 35 | lock mutex 36 | } 37 | ``` 38 | 39 | 关于字段的含义都写在注释里了,再来重点说几个字段: 40 | 41 | `buf` 指向底层循环数组,只有缓冲型的 channel 才有。 42 | 43 | `sendx`,`recvx` 均指向底层循环数组,表示当前可以发送和接收的元素位置索引值(相对于底层数组)。 44 | 45 | `sendq`,`recvq` 分别表示被阻塞的 goroutine,这些 goroutine 由于尝试读取 channel 或向 channel 发送数据而被阻塞。 46 | 47 | `waitq` 是 `sudog` 的一个双向链表,而 `sudog` 实际上是对 goroutine 的一个封装: 48 | 49 | ```golang 50 | type waitq struct { 51 | first *sudog 52 | last *sudog 53 | } 54 | ``` 55 | 56 | `lock` 用来保证每个读 channel 或写 channel 的操作都是原子的。 57 | 58 | 例如,创建一个容量为 6 的,元素为 int 型的 channel 数据结构如下 : 59 | 60 | ![chan data structure](../assets/0.png) 61 | 62 | # 创建 63 | 我们知道,通道有两个方向,发送和接收。理论上来说,我们可以创建一个只发送或只接收的通道,但是这种通道创建出来后,怎么使用呢?一个只能发的通道,怎么接收呢?同样,一个只能收的通道,如何向其发送数据呢? 64 | 65 | 一般而言,使用 `make` 创建一个能收能发的通道: 66 | 67 | ```golang 68 | // 无缓冲通道 69 | ch1 := make(chan int) 70 | // 有缓冲通道 71 | ch2 := make(chan int, 10) 72 | ``` 73 | 74 | 通过[汇编](https://mp.weixin.qq.com/s/obnnVkO2EiFnuXk_AIDHWw)分析,我们知道,最终创建 chan 的函数是 `makechan`: 75 | 76 | ```golang 77 | func makechan(t *chantype, size int64) *hchan 78 | ``` 79 | 80 | 从函数原型来看,创建的 chan 是一个指针。所以我们能在函数间直接传递 channel,而不用传递 channel 的指针。 81 | 82 | 具体来看下代码: 83 | 84 | ```golang 85 | const hchanSize = unsafe.Sizeof(hchan{}) + uintptr(-int(unsafe.Sizeof(hchan{}))&(maxAlign-1)) 86 | 87 | func makechan(t *chantype, size int64) *hchan { 88 | elem := t.elem 89 | 90 | // 省略了检查 channel size,align 的代码 91 | // …… 92 | 93 | var c *hchan 94 | // 如果元素类型不含指针 或者 size 大小为 0(无缓冲类型) 95 | // 只进行一次内存分配 96 | if elem.kind&kindNoPointers != 0 || size == 0 { 97 | // 如果 hchan 结构体中不含指针,GC 就不会扫描 chan 中的元素 98 | // 只分配 "hchan 结构体大小 + 元素大小*个数" 的内存 99 | c = (*hchan)(mallocgc(hchanSize+uintptr(size)*elem.size, nil, true)) 100 | // 如果是缓冲型 channel 且元素大小不等于 0(大小等于 0的元素类型:struct{}) 101 | if size > 0 && elem.size != 0 { 102 | c.buf = add(unsafe.Pointer(c), hchanSize) 103 | } else { 104 | // race detector uses this location for synchronization 105 | // Also prevents us from pointing beyond the allocation (see issue 9401). 106 | // 1. 非缓冲型的,buf 没用,直接指向 chan 起始地址处 107 | // 2. 缓冲型的,能进入到这里,说明元素无指针且元素类型为 struct{},也无影响 108 | // 因为只会用到接收和发送游标,不会真正拷贝东西到 c.buf 处(这会覆盖 chan的内容) 109 | c.buf = unsafe.Pointer(c) 110 | } 111 | } else { 112 | // 进行两次内存分配操作 113 | c = new(hchan) 114 | c.buf = newarray(elem, int(size)) 115 | } 116 | c.elemsize = uint16(elem.size) 117 | c.elemtype = elem 118 | // 循环数组长度 119 | c.dataqsiz = uint(size) 120 | 121 | // 返回 hchan 指针 122 | return c 123 | } 124 | ``` 125 | 126 | 新建一个 chan 后,内存在堆上分配,大概长这样: 127 | 128 | ![make chan](../assets/1.png) 129 | 130 | # 参考资料 131 | 132 | 【Kavya在Gopher Con 上关于 channel 的设计,非常好】https://speakerd.s3.amazonaws.com/presentations/10ac0b1d76a6463aa98ad6a9dec917a7/GopherCon_v10.0.pdf 133 | -------------------------------------------------------------------------------- /content/channel/5-关闭一个 channel 的过程是怎样的.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 405 3 | title: "关闭一个 channel 的过程是怎样的" 4 | slug: /close 5 | --- 6 | 7 | 关闭某个 channel,会执行函数 `closechan`: 8 | 9 | ```golang 10 | func closechan(c *hchan) { 11 | // 关闭一个 nil channel,panic 12 | if c == nil { 13 | panic(plainError("close of nil channel")) 14 | } 15 | 16 | // 上锁 17 | lock(&c.lock) 18 | // 如果 channel 已经关闭 19 | if c.closed != 0 { 20 | unlock(&c.lock) 21 | // panic 22 | panic(plainError("close of closed channel")) 23 | } 24 | 25 | // ………… 26 | 27 | // 修改关闭状态 28 | c.closed = 1 29 | 30 | var glist *g 31 | 32 | // 将 channel 所有等待接收队列的里 sudog 释放 33 | for { 34 | // 从接收队列里出队一个 sudog 35 | sg := c.recvq.dequeue() 36 | // 出队完毕,跳出循环 37 | if sg == nil { 38 | break 39 | } 40 | 41 | // 如果 elem 不为空,说明此 receiver 未忽略接收数据 42 | // 给它赋一个相应类型的零值 43 | if sg.elem != nil { 44 | typedmemclr(c.elemtype, sg.elem) 45 | sg.elem = nil 46 | } 47 | if sg.releasetime != 0 { 48 | sg.releasetime = cputicks() 49 | } 50 | // 取出 goroutine 51 | gp := sg.g 52 | gp.param = nil 53 | if raceenabled { 54 | raceacquireg(gp, unsafe.Pointer(c)) 55 | } 56 | // 相连,形成链表 57 | gp.schedlink.set(glist) 58 | glist = gp 59 | } 60 | 61 | // 将 channel 等待发送队列里的 sudog 释放 62 | // 如果存在,这些 goroutine 将会 panic 63 | for { 64 | // 从发送队列里出队一个 sudog 65 | sg := c.sendq.dequeue() 66 | if sg == nil { 67 | break 68 | } 69 | 70 | // 发送者会 panic 71 | sg.elem = nil 72 | if sg.releasetime != 0 { 73 | sg.releasetime = cputicks() 74 | } 75 | gp := sg.g 76 | gp.param = nil 77 | if raceenabled { 78 | raceacquireg(gp, unsafe.Pointer(c)) 79 | } 80 | // 形成链表 81 | gp.schedlink.set(glist) 82 | glist = gp 83 | } 84 | // 解锁 85 | unlock(&c.lock) 86 | 87 | // Ready all Gs now that we've dropped the channel lock. 88 | // 遍历链表 89 | for glist != nil { 90 | // 取最后一个 91 | gp := glist 92 | // 向前走一步,下一个唤醒的 g 93 | glist = glist.schedlink.ptr() 94 | gp.schedlink = 0 95 | // 唤醒相应 goroutine 96 | goready(gp, 3) 97 | } 98 | } 99 | ``` 100 | 101 | close 逻辑比较简单,对于一个 channel,recvq 和 sendq 中分别保存了阻塞的发送者和接收者。关闭 channel 后,对于等待接收者而言,会收到一个相应类型的零值。对于等待发送者,会直接 panic。所以,在不了解 channel 还有没有接收者的情况下,不能贸然关闭 channel。 102 | 103 | close 函数先上一把大锁,接着把所有挂在这个 channel 上的 sender 和 receiver 全都连成一个 sudog 链表,再解锁。最后,再将所有的 sudog 全都唤醒。 104 | 105 | 唤醒之后,该干嘛干嘛。sender 会继续执行 chansend 函数里 goparkunlock 函数之后的代码,很不幸,检测到 channel 已经关闭了,panic。receiver 则比较幸运,进行一些扫尾工作后,返回。这里,selected 返回 true,而返回值 received 则要根据 channel 是否关闭,返回不同的值。如果 channel 关闭,received 为 false,否则为 true。这我们分析的这种情况下,received 返回 false。 106 | -------------------------------------------------------------------------------- /content/channel/6-从一个关闭的 channel 仍然能读出数据吗.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 406 3 | title: "从一个关闭的 channel 仍然能读出数据吗" 4 | slug: /read-on-close 5 | --- 6 | 7 | 从一个有缓冲的 channel 里读数据,当 channel 被关闭,依然能读出有效值。只有当返回的 ok 为 false 时,读出的数据才是无效的。 8 | 9 | ```golang 10 | func main() { 11 | ch := make(chan int, 5) 12 | ch <- 18 13 | close(ch) 14 | x, ok := <-ch 15 | if ok { 16 | fmt.Println("received: ", x) 17 | } 18 | 19 | x, ok = <-ch 20 | if !ok { 21 | fmt.Println("channel closed, data invalid.") 22 | } 23 | } 24 | ``` 25 | 26 | 运行结果: 27 | 28 | ```golang 29 | received: 18 30 | channel closed, data invalid. 31 | ``` 32 | 33 | 先创建了一个有缓冲的 channel,向其发送一个元素,然后关闭此 channel。之后两次尝试从 channel 中读取数据,第一次仍然能正常读出值。第二次返回的 ok 为 false,说明 channel 已关闭,且通道里没有数据。 34 | 35 | 具体过程可以参考“从 channel 接收数据的过程是怎样的”一节。 36 | -------------------------------------------------------------------------------- /content/channel/7-操作 channel 的情况总结.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 407 3 | title: "操作 channel 的情况总结" 4 | slug: /ops 5 | --- 6 | 7 | 总结一下操作 channel 的结果: 8 | 9 | |操作|nil channel|closed channel|not nil, not closed channel| 10 | |---|---|---|---| 11 | |close|panic|panic|正常关闭| 12 | |读 <- ch|阻塞|读到对应类型的零值|阻塞或正常读取数据。缓冲型 channel 为空或非缓冲型 channel 没有等待发送者时会阻塞| 13 | |写 ch <-|阻塞|panic|阻塞或正常写入数据。非缓冲型 channel 没有等待接收者或缓冲型 channel buf 满时会被阻塞| 14 | 15 | 总结一下,发生 panic 的情况有三种:向一个关闭的 channel 进行写操作;关闭一个 nil 的 channel;重复关闭一个 channel。 16 | 17 | 读、写一个 nil channel 都会被阻塞。 18 | -------------------------------------------------------------------------------- /content/channel/9-channel 发送和接收元素的本质是什么.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 409 3 | title: "channel 发送和接收元素的本质是什么" 4 | slug: /principal 5 | --- 6 | 7 | Channel 发送和接收元素的本质是什么? 8 | 9 | > All transfer of value on the go channels happens with the copy of value. 10 | 11 | 就是说 channel 的发送和接收操作本质上都是 “值的拷贝”,无论是从 sender goroutine 的栈到 chan buf,还是从 chan buf 到 receiver goroutine,或者是直接从 sender goroutine 到 receiver goroutine。 12 | 13 | 举一个例子: 14 | 15 | ``` 16 | type user struct { 17 | name string 18 | age int8 19 | } 20 | 21 | var u = user{name: "Ankur", age: 25} 22 | var g = &u 23 | 24 | func modifyUser(pu *user) { 25 | fmt.Println("modifyUser Received Vaule", pu) 26 | pu.name = "Anand" 27 | } 28 | 29 | func printUser(u <-chan *user) { 30 | time.Sleep(2 * time.Second) 31 | fmt.Println("printUser goRoutine called", <-u) 32 | } 33 | 34 | func main() { 35 | c := make(chan *user, 5) 36 | c <- g 37 | fmt.Println(g) 38 | // modify g 39 | g = &user{name: "Ankur Anand", age: 100} 40 | go printUser(c) 41 | go modifyUser(g) 42 | time.Sleep(5 * time.Second) 43 | fmt.Println(g) 44 | } 45 | ``` 46 | 47 | 运行结果: 48 | 49 | ```shell 50 | &{Ankur 25} 51 | modifyUser Received Vaule &{Ankur Anand 100} 52 | printUser goRoutine called &{Ankur 25} 53 | &{Anand 100} 54 | ``` 55 | 56 | 这里就是一个很好的 `share memory by communicating` 的例子。 57 | 58 | ![output](../assets/12.png) 59 | 60 | 一开始构造一个结构体 u,地址是 0x56420,图中地址上方就是它的内容。接着把 `&u` 赋值给指针 `g`,g 的地址是 0x565bb0,它的内容就是一个地址,指向 u。 61 | 62 | main 程序里,先把 g 发送到 c,根据 `copy value` 的本质,进入到 chan buf 里的就是 `0x56420`,它是指针 g 的值(不是它指向的内容),所以打印从 channel 接收到的元素时,它就是 `&{Ankur 25}`。因此,这里并不是将指针 g “发送” 到了 channel 里,只是拷贝它的值而已。 63 | 64 | 再强调一次: 65 | 66 | > Remember all transfer of value on the go channels happens with the copy of value. 67 | 68 | # 参考资料 69 | 【深入 channel 底层】https://codeburst.io/diving-deep-into-the-golang-channels-549fd4ed21a8 70 | -------------------------------------------------------------------------------- /content/channel/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 400 3 | bookFlatSection: true 4 | bookCollapseSection: true 5 | title: "通道" 6 | slug: / 7 | --- -------------------------------------------------------------------------------- /content/channel/assets/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/channel/assets/0.png -------------------------------------------------------------------------------- /content/channel/assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/channel/assets/1.png -------------------------------------------------------------------------------- /content/channel/assets/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/channel/assets/10.png -------------------------------------------------------------------------------- /content/channel/assets/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/channel/assets/11.png -------------------------------------------------------------------------------- /content/channel/assets/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/channel/assets/12.png -------------------------------------------------------------------------------- /content/channel/assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/channel/assets/2.png -------------------------------------------------------------------------------- /content/channel/assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/channel/assets/3.png -------------------------------------------------------------------------------- /content/channel/assets/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/channel/assets/4.png -------------------------------------------------------------------------------- /content/channel/assets/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/channel/assets/5.png -------------------------------------------------------------------------------- /content/channel/assets/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/channel/assets/6.png -------------------------------------------------------------------------------- /content/channel/assets/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/channel/assets/7.png -------------------------------------------------------------------------------- /content/channel/assets/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/channel/assets/8.png -------------------------------------------------------------------------------- /content/channel/assets/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/channel/assets/9.png -------------------------------------------------------------------------------- /content/compile/2-GoRoot 和 GoPath 有什么用.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 602 3 | title: "GoRoot 和 GoPath 有什么用" 4 | slug: /gopath 5 | --- 6 | 7 | GoRoot 是 Go 的安装路径。mac 或 unix 是在 `/usr/local/go` 路径上,来看下这里都装了些什么: 8 | 9 | ![/usr/local/go](../assets/1.png) 10 | 11 | bin 目录下面: 12 | 13 | ![bin](../assets/2.png) 14 | 15 | pkg 目录下面: 16 | 17 | ![pkg](../assets/3.png) 18 | 19 | Go 工具目录如下,其中比较重要的有编译器 `compile`,链接器 `link`: 20 | 21 | ![pkg/tool](../assets/4.png) 22 | 23 | GoPath 的作用在于提供一个可以寻找 `.go` 源码的路径,它是一个工作空间的概念,可以设置多个目录。Go 官方要求,GoPath 下面需要包含三个文件夹: 24 | 25 | ```shell 26 | src 27 | pkg 28 | bin 29 | ``` 30 | 31 | src 存放源文件,pkg 存放源文件编译后的库文件,后缀为 `.a`;bin 则存放可执行文件。 -------------------------------------------------------------------------------- /content/compile/5-Go 程序启动过程是怎样的.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 605 3 | title: "Go 程序启动过程是怎样的" 4 | slug: /booting 5 | --- 6 | 7 | 我们从一个 `Hello World` 的例子开始: 8 | 9 | ```golang 10 | package main 11 | 12 | import "fmt" 13 | 14 | func main() { 15 | fmt.Println("hello world") 16 | } 17 | ``` 18 | 19 | 在项目根目录下执行: 20 | 21 | ```shell 22 | go build -gcflags "-N -l" -o hello src/main.go 23 | ``` 24 | 25 | `-gcflags "-N -l"` 是为了关闭编译器优化和函数内联,防止后面在设置断点的时候找不到相对应的代码位置。 26 | 27 | 得到了可执行文件 hello,执行: 28 | 29 | ```shell 30 | [qcrao@qcrao hello-world]$ gdb hello 31 | ``` 32 | 33 | 进入 gdb 调试模式,执行 `info files`,得到可执行文件的文件头,列出了各种段: 34 | 35 | ![gdb info](../assets/20.png) 36 | 37 | 同时,我们也得到了入口地址:0x450e20。 38 | 39 | ```shell 40 | (gdb) b *0x450e20 41 | Breakpoint 1 at 0x450e20: file /usr/local/go/src/runtime/rt0_linux_amd64.s, line 8. 42 | ``` 43 | 44 | 这就是 Go 程序的入口地址,我是在 linux 上运行的,所以入口文件为 `src/runtime/rt0_linux_amd64.s`,runtime 目录下有各种不同名称的程序入口文件,支持各种操作系统和架构,代码为: 45 | 46 | ```asm 47 | TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8 48 | LEAQ 8(SP), SI // argv 49 | MOVQ 0(SP), DI // argc 50 | MOVQ $main(SB), AX 51 | JMP AX 52 | ``` 53 | 54 | 主要是把 argc,argv 从内存拉到了寄存器。这里 LEAQ 是计算内存地址,然后把内存地址本身放进寄存器里,也就是把 argv 的地址放到了 SI 寄存器中。最后跳转到: 55 | 56 | ```golang 57 | TEXT main(SB),NOSPLIT,$-8 58 | MOVQ $runtime·rt0_go(SB), AX 59 | JMP AX 60 | ``` 61 | 62 | 继续跳转到 `runtime·rt0_go(SB)`,位置:`/usr/local/go/src/runtime/asm_amd64.s`,代码: 63 | 64 | ```ams 65 | TEXT runtime·rt0_go(SB),NOSPLIT,$0 66 | // 省略很多 CPU 相关的特性标志位检查的代码 67 | // 主要是看不懂,^_^ 68 | 69 | // ……………………………… 70 | 71 | // 下面是最后调用的一些函数,比较重要 72 | // 初始化执行文件的绝对路径 73 | CALL runtime·args(SB) 74 | // 初始化 CPU 个数和内存页大小 75 | CALL runtime·osinit(SB) 76 | // 初始化命令行参数、环境变量、gc、栈空间、内存管理、所有 P 实例、HASH算法等 77 | CALL runtime·schedinit(SB) 78 | 79 | // 要在 main goroutine 上运行的函数 80 | MOVQ $runtime·mainPC(SB), AX // entry 81 | PUSHQ AX 82 | PUSHQ $0 // arg size 83 | 84 | // 新建一个 goroutine,该 goroutine 绑定 runtime.main,放在 P 的本地队列,等待调度 85 | CALL runtime·newproc(SB) 86 | POPQ AX 87 | POPQ AX 88 | 89 | // 启动M,开始调度goroutine 90 | CALL runtime·mstart(SB) 91 | 92 | MOVL $0xf1, 0xf1 // crash 93 | RET 94 | 95 | 96 | DATA runtime·mainPC+0(SB)/8,$runtime·main(SB) 97 | GLOBL runtime·mainPC(SB),RODATA,$8 98 | ``` 99 | 100 | 参考文献里的一篇文章【探索 golang 程序启动过程】研究得比较深入,总结下: 101 | 102 | >1. 检查运行平台的CPU,设置好程序运行需要相关标志。 103 | 2. TLS的初始化。 104 | 3. runtime.args、runtime.osinit、runtime.schedinit 三个方法做好程序运行需要的各种变量与调度器。 105 | 4. runtime.newproc创建新的goroutine用于绑定用户写的main方法。 106 | 5. runtime.mstart开始goroutine的调度。 107 | 108 | 最后用一张图来总结 go bootstrap 过程吧: 109 | 110 | ![golang bootstrap](../assets/21.png) 111 | 112 | main 函数里执行的一些重要的操作包括:新建一个线程执行 sysmon 函数,定期垃圾回收和调度抢占;启动 gc;执行所有的 init 函数等等。 113 | 114 | 上面是启动过程,看一下退出过程: 115 | 116 | >当 main 函数执行结束之后,会执行 exit(0) 来退出进程。若执行 exit(0) 后,进程没有退出,main 函数最后的代码会一直访问非法地址: 117 | 118 | ```golang 119 | exit(0) 120 | for { 121 | var x *int32 122 | *x = 0 123 | } 124 | ``` 125 | 126 | >正常情况下,一旦出现非法地址访问,系统会把进程杀死,用这样的方法确保进程退出。 127 | 128 | 关于程序退出这一段的阐述来自群聊《golang runtime 阅读》,又是一个高阶的读源码的组织,github 主页见参考资料。 129 | 130 | 当然 Go 程序启动这一部分其实还会涉及到 fork 一个新进程、装载可执行文件,控制权转移等问题。还是推荐看前面的两本书,我觉得我不会写得更好,就不叙述了。 -------------------------------------------------------------------------------- /content/compile/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 600 3 | bookFlatSection: true 4 | bookCollapseSection: true 5 | title: "编译" 6 | slug: / 7 | --- -------------------------------------------------------------------------------- /content/compile/assets/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/compile/assets/0.png -------------------------------------------------------------------------------- /content/compile/assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/compile/assets/1.png -------------------------------------------------------------------------------- /content/compile/assets/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/compile/assets/10.png -------------------------------------------------------------------------------- /content/compile/assets/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/compile/assets/11.png -------------------------------------------------------------------------------- /content/compile/assets/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/compile/assets/12.png -------------------------------------------------------------------------------- /content/compile/assets/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/compile/assets/13.png -------------------------------------------------------------------------------- /content/compile/assets/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/compile/assets/14.png -------------------------------------------------------------------------------- /content/compile/assets/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/compile/assets/15.png -------------------------------------------------------------------------------- /content/compile/assets/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/compile/assets/16.png -------------------------------------------------------------------------------- /content/compile/assets/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/compile/assets/17.png -------------------------------------------------------------------------------- /content/compile/assets/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/compile/assets/18.png -------------------------------------------------------------------------------- /content/compile/assets/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/compile/assets/19.png -------------------------------------------------------------------------------- /content/compile/assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/compile/assets/2.png -------------------------------------------------------------------------------- /content/compile/assets/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/compile/assets/20.png -------------------------------------------------------------------------------- /content/compile/assets/21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/compile/assets/21.png -------------------------------------------------------------------------------- /content/compile/assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/compile/assets/3.png -------------------------------------------------------------------------------- /content/compile/assets/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/compile/assets/4.png -------------------------------------------------------------------------------- /content/compile/assets/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/compile/assets/5.png -------------------------------------------------------------------------------- /content/compile/assets/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/compile/assets/6.png -------------------------------------------------------------------------------- /content/compile/assets/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/compile/assets/7.png -------------------------------------------------------------------------------- /content/compile/assets/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/compile/assets/8.png -------------------------------------------------------------------------------- /content/compile/assets/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/compile/assets/9.png -------------------------------------------------------------------------------- /content/interface/1-Go 语言与鸭子类型的关系.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 301 3 | title: "Go 语言与鸭子类型的关系" 4 | slug: /duck-typing 5 | --- 6 | 7 | 先直接来看维基百科里的定义: 8 | > If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck. 9 | 10 | 翻译过来就是:如果某个东西长得像鸭子,像鸭子一样游泳,像鸭子一样嘎嘎叫,那它就可以被看成是一只鸭子。 11 | 12 | `Duck Typing`,鸭子类型,是动态编程语言的一种对象推断策略,它更关注对象能如何被使用,而不是对象的类型本身。Go 语言作为一门静态语言,它通过通过接口的方式完美支持鸭子类型。 13 | 14 | 例如,在动态语言 python 中,定义一个这样的函数: 15 | 16 | ```python 17 | def hello_world(coder): 18 | coder.say_hello() 19 | ``` 20 | 21 | 当调用此函数的时候,可以传入任意类型,只要它实现了 `say_hello()` 函数就可以。如果没有实现,运行过程中会出现错误。 22 | 23 | 而在静态语言如 Java, C++ 中,必须要显示地声明实现了某个接口,之后,才能用在任何需要这个接口的地方。如果你在程序中调用 `hello_world` 函数,却传入了一个根本就没有实现 `say_hello()` 的类型,那在编译阶段就不会通过。这也是静态语言比动态语言更安全的原因。 24 | 25 | 动态语言和静态语言的差别在此就有所体现。静态语言在编译期间就能发现类型不匹配的错误,不像动态语言,必须要运行到那一行代码才会报错。插一句,这也是我不喜欢用 `python` 的一个原因。当然,静态语言要求程序员在编码阶段就要按照规定来编写程序,为每个变量规定数据类型,这在某种程度上,加大了工作量,也加长了代码量。动态语言则没有这些要求,可以让人更专注在业务上,代码也更短,写起来更快,这一点,写 python 的同学比较清楚。 26 | 27 | Go 语言作为一门现代静态语言,是有后发优势的。它引入了动态语言的便利,同时又会进行静态语言的类型检查,写起来是非常 Happy 的。Go 采用了折中的做法:不要求类型显示地声明实现了某个接口,只要实现了相关的方法即可,编译器就能检测到。 28 | 29 | 来看个例子: 30 | 31 | 先定义一个接口,和使用此接口作为参数的函数: 32 | 33 | ```golang 34 | type IGreeting interface { 35 | sayHello() 36 | } 37 | 38 | func sayHello(i IGreeting) { 39 | i.sayHello() 40 | } 41 | ``` 42 | 43 | 再来定义两个结构体: 44 | 45 | ```golang 46 | type Go struct {} 47 | func (g Go) sayHello() { 48 | fmt.Println("Hi, I am GO!") 49 | } 50 | 51 | type PHP struct {} 52 | func (p PHP) sayHello() { 53 | fmt.Println("Hi, I am PHP!") 54 | } 55 | ``` 56 | 57 | 最后,在 main 函数里调用 sayHello() 函数: 58 | 59 | ```golang 60 | func main() { 61 | golang := Go{} 62 | php := PHP{} 63 | 64 | sayHello(golang) 65 | sayHello(php) 66 | } 67 | ``` 68 | 69 | 程序输出: 70 | 71 | ```shell 72 | Hi, I am GO! 73 | Hi, I am PHP! 74 | ``` 75 | 76 | 在 main 函数中,调用调用 sayHello() 函数时,传入了 `golang, php` 对象,它们并没有显式地声明实现了 IGreeting 类型,只是实现了接口所规定的 sayHello() 函数。实际上,编译器在调用 sayHello() 函数时,会隐式地将 `golang, php` 对象转换成 IGreeting 类型,这也是静态语言的类型检查功能。 77 | 78 | 顺带再提一下动态语言的特点: 79 | > 变量绑定的类型是不确定的,在运行期间才能确定 80 | > 函数和方法可以接收任何类型的参数,且调用时不检查参数类型 81 | > 不需要实现接口 82 | 83 | 总结一下,鸭子类型是一种动态语言的风格,在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由它"当前方法和属性的集合"决定。Go 作为一种静态语言,通过接口实现了 `鸭子类型`,实际上是 Go 的编译器在其中作了隐匿的转换工作。 84 | 85 | # 参考资料 86 | 【wikipedia】https://en.wikipedia.org/wiki/Duck_test 87 | 88 | 【Golang 与鸭子类型,讲得比较好】https://blog.csdn.net/cszhouwei/article/details/33741731 89 | 90 | 【各种面向对象的名词】https://cyent.github.io/golang/other/oo/ 91 | 92 | 【多态、鸭子类型特性】https://www.jb51.net/article/116025.htm 93 | 94 | 【鸭子类型、动态静态语言】https://www.jianshu.com/p/650485b78d11 -------------------------------------------------------------------------------- /content/interface/10-Go 接口与 C++ 接口有何异同.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 310 3 | title: "Go 接口与 C++ 接口有何异同" 4 | slug: /compare-to-cpp 5 | --- 6 | 7 | 接口定义了一种规范,描述了类的行为和功能,而不做具体实现。 8 | 9 | C++ 的接口是使用抽象类来实现的,如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。纯虚函数是通过在声明中使用 "= 0" 来指定的。例如: 10 | 11 | ```C++ 12 | class Shape 13 | { 14 | public: 15 | // 纯虚函数 16 | virtual double getArea() = 0; 17 | private: 18 | string name; // 名称 19 | }; 20 | ``` 21 | 22 | 设计抽象类的目的,是为了给其他类提供一个可以继承的适当的基类。抽象类不能被用于实例化对象,它只能作为接口使用。 23 | 24 | 派生类需要明确地声明它继承自基类,并且需要实现基类中所有的纯虚函数。 25 | 26 | C++ 定义接口的方式称为“侵入式”,而 Go 采用的是 “非侵入式”,不需要显式声明,只需要实现接口定义的函数,编译器自动会识别。 27 | 28 | C++ 和 Go 在定义接口方式上的不同,也导致了底层实现上的不同。C++ 通过虚函数表来实现基类调用派生类的函数;而 Go 通过 `itab` 中的 `fun` 字段来实现接口变量调用实体类型的函数。C++ 中的虚函数表是在编译期生成的;而 Go 的 `itab` 中的 `fun` 字段是在运行期间动态生成的。原因在于,Go 中实体类型可能会无意中实现 N 多接口,很多接口并不是本来需要的,所以不能为类型实现的所有接口都生成一个 `itab`, 这也是“非侵入式”带来的影响;这在 C++ 中是不存在的,因为派生需要显示声明它继承自哪个基类。 29 | 30 | # 参考资料 31 | 【和 C++ 的对比】https://www.jianshu.com/p/b38b1719636e -------------------------------------------------------------------------------- /content/interface/3-iface 和 eface 的区别是什么.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 303 3 | title: "iface 和 eface 的区别是什么" 4 | slug: /iface-eface 5 | --- 6 | 7 | `iface` 和 `eface` 都是 Go 中描述接口的底层结构体,区别在于 `iface` 描述的接口包含方法,而 `eface` 则是不包含任何方法的空接口:`interface{}`。 8 | 9 | 从源码层面看一下: 10 | 11 | ```golang 12 | type iface struct { 13 | tab *itab 14 | data unsafe.Pointer 15 | } 16 | 17 | type itab struct { 18 | inter *interfacetype 19 | _type *_type 20 | link *itab 21 | hash uint32 // copy of _type.hash. Used for type switches. 22 | bad bool // type does not implement interface 23 | inhash bool // has this itab been added to hash? 24 | unused [2]byte 25 | fun [1]uintptr // variable sized 26 | } 27 | ``` 28 | 29 | `iface` 内部维护两个指针,`tab` 指向一个 `itab` 实体, 它表示接口的类型以及赋给这个接口的实体类型。`data` 则指向接口具体的值,一般而言是一个指向堆内存的指针。 30 | 31 | 再来仔细看一下 `itab` 结构体:`_type` 字段描述了实体的类型,包括内存对齐方式,大小等;`inter` 字段则描述了接口的类型。`fun` 字段放置和接口方法对应的具体数据类型的方法地址,实现接口调用方法的动态分派,一般在每次给接口赋值发生转换时会更新此表,或者直接拿缓存的 itab。 32 | 33 | 这里只会列出实体类型和接口相关的方法,实体类型的其他方法并不会出现在这里。如果你学过 C++ 的话,这里可以类比虚函数的概念。 34 | 35 | 另外,你可能会觉得奇怪,为什么 `fun` 数组的大小为 1,要是接口定义了多个方法可怎么办?实际上,这里存储的是第一个方法的函数指针,如果有更多的方法,在它之后的内存空间里继续存储。从汇编角度来看,通过增加地址就能获取到这些函数指针,没什么影响。顺便提一句,这些方法是按照函数名称的字典序进行排列的。 36 | 37 | 再看一下 `interfacetype` 类型,它描述的是接口的类型: 38 | 39 | ```golang 40 | type interfacetype struct { 41 | typ _type 42 | pkgpath name 43 | mhdr []imethod 44 | } 45 | ``` 46 | 47 | 可以看到,它包装了 `_type` 类型,`_type` 实际上是描述 Go 语言中各种数据类型的结构体。我们注意到,这里还包含一个 `mhdr` 字段,表示接口所定义的函数列表, `pkgpath` 记录定义了接口的包名。 48 | 49 | 这里通过一张图来看下 `iface` 结构体的全貌: 50 | 51 | ![iface 结构体全景](../assets/0.png) 52 | 53 | 接着来看一下 `eface` 的源码: 54 | 55 | ```golang 56 | type eface struct { 57 | _type *_type 58 | data unsafe.Pointer 59 | } 60 | ``` 61 | 62 | 相比 `iface`,`eface` 就比较简单了。只维护了一个 `_type` 字段,表示空接口所承载的具体的实体类型。`data` 描述了具体的值。 63 | 64 | ![eface 结构体全景](../assets/1.png) 65 | 66 | 我们来看个例子: 67 | 68 | ```golang 69 | package main 70 | 71 | import "fmt" 72 | 73 | func main() { 74 | x := 200 75 | var any interface{} = x 76 | fmt.Println(any) 77 | 78 | g := Gopher{"Go"} 79 | var c coder = g 80 | fmt.Println(c) 81 | } 82 | 83 | type coder interface { 84 | code() 85 | debug() 86 | } 87 | 88 | type Gopher struct { 89 | language string 90 | } 91 | 92 | func (p Gopher) code() { 93 | fmt.Printf("I am coding %s language\n", p.language) 94 | } 95 | 96 | func (p Gopher) debug() { 97 | fmt.Printf("I am debuging %s language\n", p.language) 98 | } 99 | ``` 100 | 101 | 执行命令,打印出汇编语言: 102 | 103 | ```shell 104 | go tool compile -S ./src/main.go 105 | ``` 106 | 107 | 可以看到,main 函数里调用了两个函数: 108 | 109 | ```shell 110 | func convT2E64(t *_type, elem unsafe.Pointer) (e eface) 111 | func convT2I(tab *itab, elem unsafe.Pointer) (i iface) 112 | ``` 113 | 114 | 上面两个函数的参数和 `iface` 及 `eface` 结构体的字段是可以联系起来的:两个函数都是将参数`组装`一下,形成最终的接口。 115 | 116 | 作为补充,我们最后再来看下 `_type` 结构体: 117 | 118 | ```golang 119 | type _type struct { 120 | // 类型大小 121 | size uintptr 122 | ptrdata uintptr 123 | // 类型的 hash 值 124 | hash uint32 125 | // 类型的 flag,和反射相关 126 | tflag tflag 127 | // 内存对齐相关 128 | align uint8 129 | fieldalign uint8 130 | // 类型的编号,有bool, slice, struct 等等等等 131 | kind uint8 132 | alg *typeAlg 133 | // gc 相关 134 | gcdata *byte 135 | str nameOff 136 | ptrToThis typeOff 137 | } 138 | ``` 139 | 140 | Go 语言各种数据类型都是在 `_type` 字段的基础上,增加一些额外的字段来进行管理的: 141 | 142 | ```golang 143 | type arraytype struct { 144 | typ _type 145 | elem *_type 146 | slice *_type 147 | len uintptr 148 | } 149 | 150 | type chantype struct { 151 | typ _type 152 | elem *_type 153 | dir uintptr 154 | } 155 | 156 | type slicetype struct { 157 | typ _type 158 | elem *_type 159 | } 160 | 161 | type structtype struct { 162 | typ _type 163 | pkgPath name 164 | fields []structfield 165 | } 166 | ``` 167 | 168 | 这些数据类型的结构体定义,是反射实现的基础。 169 | 170 | # 参考资料 171 | 【有汇编分析,不错】http://legendtkl.com/2017/07/01/golang-interface-implement/ 172 | 173 | 【interface 源码解读 很不错 包含反射】http://wudaijun.com/2018/01/go-interface-implement/ -------------------------------------------------------------------------------- /content/interface/4-接口的动态类型和动态值.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 304 3 | title: "接口的动态类型和动态值" 4 | slug: /dynamic-typing 5 | --- 6 | 7 | 从源码里可以看到:`iface`包含两个字段:`tab` 是接口表指针,指向类型信息;`data` 是数据指针,则指向具体的数据。它们分别被称为`动态类型`和`动态值`。而接口值包括`动态类型`和`动态值`。 8 | 9 | 【引申1】接口类型和 `nil` 作比较 10 | 11 | 接口值的零值是指`动态类型`和`动态值`都为 `nil`。当仅且当这两部分的值都为 `nil` 的情况下,这个接口值就才会被认为 `接口值 == nil`。 12 | 13 | 来看个例子: 14 | 15 | ```golang 16 | package main 17 | 18 | import "fmt" 19 | 20 | type Coder interface { 21 | code() 22 | } 23 | 24 | type Gopher struct { 25 | name string 26 | } 27 | 28 | func (g Gopher) code() { 29 | fmt.Printf("%s is coding\n", g.name) 30 | } 31 | 32 | func main() { 33 | var c Coder 34 | fmt.Println(c == nil) 35 | fmt.Printf("c: %T, %v\n", c, c) 36 | 37 | var g *Gopher 38 | fmt.Println(g == nil) 39 | 40 | c = g 41 | fmt.Println(c == nil) 42 | fmt.Printf("c: %T, %v\n", c, c) 43 | } 44 | ``` 45 | 46 | 输出: 47 | 48 | ```shell 49 | true 50 | c: , 51 | true 52 | false 53 | c: *main.Gopher, 54 | ``` 55 | 56 | 一开始,`c` 的 动态类型和动态值都为 `nil`,`g` 也为 `nil`,当把 `g` 赋值给 `c` 后,`c` 的动态类型变成了 `*main.Gopher`,仅管 `c` 的动态值仍为 `nil`,但是当 `c` 和 `nil` 作比较的时候,结果就是 `false` 了。 57 | 58 | 【引申2】 59 | 来看一个例子,看一下它的输出: 60 | 61 | ```golang 62 | package main 63 | 64 | import "fmt" 65 | 66 | type MyError struct {} 67 | 68 | func (i MyError) Error() string { 69 | return "MyError" 70 | } 71 | 72 | func main() { 73 | err := Process() 74 | fmt.Println(err) 75 | 76 | fmt.Println(err == nil) 77 | } 78 | 79 | func Process() error { 80 | var err *MyError = nil 81 | return err 82 | } 83 | ``` 84 | 85 | 函数运行结果: 86 | 87 | ```shell 88 | 89 | false 90 | ``` 91 | 92 | 这里先定义了一个 `MyError` 结构体,实现了 `Error` 函数,也就实现了 `error` 接口。`Process` 函数返回了一个 `error` 接口,这块隐含了类型转换。所以,虽然它的值是 `nil`,其实它的类型是 `*MyError`,最后和 `nil` 比较的时候,结果为 `false`。 93 | 94 | 【引申3】如何打印出接口的动态类型和值? 95 | 96 | 直接看代码: 97 | 98 | ```golang 99 | package main 100 | 101 | import ( 102 | "unsafe" 103 | "fmt" 104 | ) 105 | 106 | type iface struct { 107 | itab, data uintptr 108 | } 109 | 110 | func main() { 111 | var a interface{} = nil 112 | 113 | var b interface{} = (*int)(nil) 114 | 115 | x := 5 116 | var c interface{} = (*int)(&x) 117 | 118 | ia := *(*iface)(unsafe.Pointer(&a)) 119 | ib := *(*iface)(unsafe.Pointer(&b)) 120 | ic := *(*iface)(unsafe.Pointer(&c)) 121 | 122 | fmt.Println(ia, ib, ic) 123 | 124 | fmt.Println(*(*int)(unsafe.Pointer(ic.data))) 125 | } 126 | ``` 127 | 128 | 代码里直接定义了一个 `iface` 结构体,用两个指针来描述 `itab` 和 `data`,之后将 a, b, c 在内存中的内容强制解释成我们自定义的 `iface`。最后就可以打印出动态类型和动态值的地址。 129 | 130 | 运行结果如下: 131 | 132 | ```shell 133 | {0 0} {17426912 0} {17426912 842350714568} 134 | 5 135 | ``` 136 | 137 | a 的动态类型和动态值的地址均为 0,也就是 nil;b 的动态类型和 c 的动态类型一致,都是 `*int`;最后,c 的动态值为 5。 138 | 139 | # 参考资料 140 | 【一个包含NIL指针的接口不是NIL接口】https://i6448038.github.io/2018/07/18/golang-mistakes/ -------------------------------------------------------------------------------- /content/interface/5-编译器自动检测类型是否实现接口.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 305 3 | title: "编译器自动检测类型是否实现接口" 4 | slug: /detect-impl 5 | --- 6 | 7 | 经常看到一些开源库里会有一些类似下面这种奇怪的用法: 8 | 9 | ```golang 10 | var _ io.Writer = (*myWriter)(nil) 11 | ``` 12 | 13 | 这时候会有点懵,不知道作者想要干什么,实际上这就是此问题的答案。编译器会由此检查 `*myWriter` 类型是否实现了 `io.Writer` 接口。 14 | 15 | 来看一个例子: 16 | 17 | ```golang 18 | package main 19 | 20 | import "io" 21 | 22 | type myWriter struct { 23 | 24 | } 25 | 26 | /*func (w myWriter) Write(p []byte) (n int, err error) { 27 | return 28 | }*/ 29 | 30 | func main() { 31 | // 检查 *myWriter 类型是否实现了 io.Writer 接口 32 | var _ io.Writer = (*myWriter)(nil) 33 | 34 | // 检查 myWriter 类型是否实现了 io.Writer 接口 35 | var _ io.Writer = myWriter{} 36 | } 37 | ``` 38 | 39 | 注释掉为 myWriter 定义的 Write 函数后,运行程序: 40 | 41 | ```golang 42 | src/main.go:14:6: cannot use (*myWriter)(nil) (type *myWriter) as type io.Writer in assignment: 43 | *myWriter does not implement io.Writer (missing Write method) 44 | src/main.go:15:6: cannot use myWriter literal (type myWriter) as type io.Writer in assignment: 45 | myWriter does not implement io.Writer (missing Write method) 46 | ``` 47 | 48 | 报错信息:*myWriter/myWriter 未实现 io.Writer 接口,也就是未实现 Write 方法。 49 | 50 | 解除注释后,运行程序不报错。 51 | 52 | 实际上,上述赋值语句会发生隐式地类型转换,在转换的过程中,编译器会检测等号右边的类型是否实现了等号左边接口所规定的函数。 53 | 54 | 总结一下,可通过在代码中添加类似如下的代码,用来检测类型是否实现了接口: 55 | 56 | ```golang 57 | var _ io.Writer = (*myWriter)(nil) 58 | var _ io.Writer = myWriter{} 59 | ``` 60 | -------------------------------------------------------------------------------- /content/interface/9-如何用 interface 实现多态.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 309 3 | title: "如何用 interface 实现多态" 4 | slug: /polymorphism 5 | --- 6 | 7 | `Go` 语言并没有设计诸如虚函数、纯虚函数、继承、多重继承等概念,但它通过接口却非常优雅地支持了面向对象的特性。 8 | 9 | 多态是一种运行期的行为,它有以下几个特点: 10 | 11 | >1. 一种类型具有多种类型的能力 12 | >2. 允许不同的对象对同一消息做出灵活的反应 13 | >3. 以一种通用的方式对待个使用的对象 14 | >4. 非动态语言必须通过继承和接口的方式来实现 15 | 16 | 看一个实现了多态的代码例子: 17 | 18 | ```golang 19 | package main 20 | 21 | import "fmt" 22 | 23 | func main() { 24 | qcrao := Student{age: 18} 25 | whatJob(&qcrao) 26 | 27 | growUp(&qcrao) 28 | fmt.Println(qcrao) 29 | 30 | stefno := Programmer{age: 100} 31 | whatJob(stefno) 32 | 33 | growUp(stefno) 34 | fmt.Println(stefno) 35 | } 36 | 37 | func whatJob(p Person) { 38 | p.job() 39 | } 40 | 41 | func growUp(p Person) { 42 | p.growUp() 43 | } 44 | 45 | type Person interface { 46 | job() 47 | growUp() 48 | } 49 | 50 | type Student struct { 51 | age int 52 | } 53 | 54 | func (p Student) job() { 55 | fmt.Println("I am a student.") 56 | return 57 | } 58 | 59 | func (p *Student) growUp() { 60 | p.age += 1 61 | return 62 | } 63 | 64 | type Programmer struct { 65 | age int 66 | } 67 | 68 | func (p Programmer) job() { 69 | fmt.Println("I am a programmer.") 70 | return 71 | } 72 | 73 | func (p Programmer) growUp() { 74 | // 程序员老得太快 ^_^ 75 | p.age += 10 76 | return 77 | } 78 | ``` 79 | 80 | 代码里先定义了 1 个 `Person` 接口,包含两个函数: 81 | 82 | ```golang 83 | job() 84 | growUp() 85 | ``` 86 | 87 | 然后,又定义了 2 个结构体,`Student` 和 `Programmer`,同时,类型 `*Student`、`Programmer` 实现了 `Person` 接口定义的两个函数。注意,`*Student` 类型实现了接口, `Student` 类型却没有。 88 | 89 | 之后,我又定义了函数参数是 `Person` 接口的两个函数: 90 | 91 | ```golang 92 | func whatJob(p Person) 93 | func growUp(p Person) 94 | ``` 95 | 96 | `main` 函数里先生成 `Student` 和 `Programmer` 的对象,再将它们分别传入到函数 `whatJob` 和 `growUp`。函数中,直接调用接口函数,实际执行的时候是看最终传入的实体类型是什么,调用的是实体类型实现的函数。于是,不同对象针对同一消息就有多种表现,`多态`就实现了。 97 | 98 | 更深入一点来说的话,在函数 `whatJob()` 或者 `growUp()` 内部,接口 `person` 绑定了实体类型 `*Student` 或者 `Programmer`。根据前面分析的 `iface` 源码,这里会直接调用 `fun` 里保存的函数,类似于: `s.tab->fun[0]`,而因为 `fun` 数组里保存的是实体类型实现的函数,所以当函数传入不同的实体类型时,调用的实际上是不同的函数实现,从而实现多态。 99 | 100 | 运行一下代码: 101 | 102 | ```shell 103 | I am a student. 104 | {19} 105 | I am a programmer. 106 | {100} 107 | ``` 108 | 109 | # 参考资料 110 | 【各种面向对象的名词】https://cyent.github.io/golang/other/oo/ 111 | 112 | 【多态与鸭子类型】https://www.jb51.net/article/116025.htm -------------------------------------------------------------------------------- /content/interface/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 300 3 | bookFlatSection: true 4 | bookCollapseSection: true 5 | title: "接口" 6 | slug: / 7 | --- -------------------------------------------------------------------------------- /content/interface/assets/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/interface/assets/0.png -------------------------------------------------------------------------------- /content/interface/assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/interface/assets/1.png -------------------------------------------------------------------------------- /content/map/10-可以对 map 的元素取地址吗.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 210 3 | title: "可以对 map 的元素取地址吗" 4 | slug: /element-address 5 | --- 6 | 7 | 无法对 map 的 key 或 value 进行取址。以下代码不能通过编译: 8 | 9 | ```golang 10 | package main 11 | 12 | import "fmt" 13 | 14 | func main() { 15 | m := make(map[string]int) 16 | 17 | fmt.Println(&m["qcrao"]) 18 | } 19 | ``` 20 | 21 | 编译报错: 22 | 23 | ```shell 24 | ./main.go:8:14: cannot take the address of m["qcrao"] 25 | ``` 26 | 27 | 如果通过其他 hack 的方式,例如 unsafe.Pointer 等获取到了 key 或 value 的地址,也不能长期持有,因为一旦发生扩容,key 和 value 的位置就会改变,之前保存的地址也就失效了。 28 | -------------------------------------------------------------------------------- /content/map/11-如何比较两个 map 相等.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 211 3 | title: "如何比较两个 map 相等" 4 | slug: /compare 5 | --- 6 | 7 | 8 | map 深度相等的条件: 9 | 10 | ```shell 11 | 1、都为 nil 12 | 2、非空、长度相等,指向同一个 map 实体对象 13 | 3、相应的 key 指向的 value “深度”相等 14 | ``` 15 | 16 | 直接将使用 map1 == map2 是错误的。这种写法只能比较 map 是否为 nil。 17 | 18 | ```golang 19 | package main 20 | 21 | import "fmt" 22 | 23 | func main() { 24 | var m map[string]int 25 | var n map[string]int 26 | 27 | fmt.Println(m == nil) 28 | fmt.Println(n == nil) 29 | 30 | // 不能通过编译 31 | //fmt.Println(m == n) 32 | } 33 | ``` 34 | 35 | 输出结果: 36 | 37 | ```golang 38 | true 39 | true 40 | ``` 41 | 42 | 因此只能是遍历map 的每个元素,比较元素是否都是深度相等。 43 | 44 | -------------------------------------------------------------------------------- /content/map/12-map 是线程安全的吗.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 212 3 | title: "map 是线程安全的吗" 4 | slug: /thread-safety 5 | --- 6 | 7 | map 不是线程安全的。 8 | 9 | 在查找、赋值、遍历、删除的过程中都会检测写标志,一旦发现写标志置位(等于1),则直接 panic。赋值和删除函数在检测完写标志是复位之后,先将写标志位置位,才会进行之后的操作。 10 | 11 | 检测写标志: 12 | 13 | ```golang 14 | if h.flags&hashWriting == 0 { 15 | throw("concurrent map writes") 16 | } 17 | ``` 18 | 19 | 设置写标志: 20 | 21 | ```golang 22 | h.flags |= hashWriting 23 | ``` 24 | -------------------------------------------------------------------------------- /content/map/2-如何实现两种 get 操作.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 202 3 | title: "如何实现两种 get 操作" 4 | slug: /get 5 | --- 6 | 7 | Go 语言中读取 map 有两种语法:带 comma 和 不带 comma。当要查询的 key 不在 map 里,带 comma 的用法会返回一个 bool 型变量提示 key 是否在 map 中;而不带 comma 的语句则会返回一个 key 对应 value 类型的零值。如果 value 是 int 型就会返回 0,如果 value 是 string 类型,就会返回空字符串。 8 | 9 | ```golang 10 | package main 11 | 12 | import "fmt" 13 | 14 | func main() { 15 | ageMap := make(map[string]int) 16 | ageMap["qcrao"] = 18 17 | 18 | // 不带 comma 用法 19 | age1 := ageMap["stefno"] 20 | fmt.Println(age1) 21 | 22 | // 带 comma 用法 23 | age2, ok := ageMap["stefno"] 24 | fmt.Println(age2, ok) 25 | } 26 | ``` 27 | 28 | 运行结果: 29 | 30 | ```shell 31 | 0 32 | 0 false 33 | ``` 34 | 35 | 以前一直觉得好神奇,怎么实现的?这其实是编译器在背后做的工作:分析代码后,将两种语法对应到底层两个不同的函数。 36 | 37 | ```golang 38 | // src/runtime/hashmap.go 39 | func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer 40 | func mapaccess2(t *maptype, h *hmap, key unsafe.Pointer) (unsafe.Pointer, bool) 41 | ``` 42 | 43 | 源码里,函数命名不拘小节,直接带上后缀 1,2,完全不理会《代码大全》里的那一套命名的做法。从上面两个函数的声明也可以看出差别了,`mapaccess2` 函数返回值多了一个 bool 型变量,两者的代码也是完全一样的,只是在返回值后面多加了一个 false 或者 true。 44 | 45 | 另外,根据 key 的不同类型,编译器还会将查找、插入、删除的函数用更具体的函数替换,以优化效率: 46 | 47 | |key 类型|查找| 48 | |---|---| 49 | |uint32|mapaccess1_fast32(t *maptype, h *hmap, key uint32) unsafe.Pointer| 50 | |uint32|mapaccess2_fast32(t *maptype, h *hmap, key uint32) (unsafe.Pointer, bool)| 51 | |uint64|mapaccess1_fast64(t *maptype, h *hmap, key uint64) unsafe.Pointer| 52 | |uint64|mapaccess2_fast64(t *maptype, h *hmap, key uint64) (unsafe.Pointer, bool)| 53 | |string|mapaccess1_faststr(t *maptype, h *hmap, ky string) unsafe.Pointer| 54 | |string|mapaccess2_faststr(t *maptype, h *hmap, ky string) (unsafe.Pointer, bool)| 55 | 56 | 这些函数的参数类型直接是具体的 uint32、unt64、string,在函数内部由于提前知晓了 key 的类型,所以内存布局是很清楚的,因此能节省很多操作,提高效率。 57 | 58 | 上面这些函数都是在文件 `src/runtime/hashmap_fast.go` 里。 -------------------------------------------------------------------------------- /content/map/4-map 的赋值过程是怎样的.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 204 3 | title: "赋值过程" 4 | slug: /assign 5 | --- 6 | 7 | 通过汇编语言可以看到,向 map 中插入或者修改 key,最终调用的是 `mapassign` 函数。 8 | 9 | 实际上插入或修改 key 的语法是一样的,只不过前者操作的 key 在 map 中不存在,而后者操作的 key 存在 map 中。 10 | 11 | mapassign 有一个系列的函数,根据 key 类型的不同,编译器会将其优化为相应的“快速函数”。 12 | 13 | |key 类型|插入| 14 | |---|---| 15 | |uint32|mapassign_fast32(t *maptype, h *hmap, key uint32) unsafe.Pointer| 16 | |uint64|mapassign_fast64(t *maptype, h *hmap, key uint64) unsafe.Pointer| 17 | |string|mapassign_faststr(t *maptype, h *hmap, ky string) unsafe.Pointer| 18 | 19 | 我们只用研究最一般的赋值函数 `mapassign`。 20 | 21 | 整体来看,流程非常得简单:对 key 计算 hash 值,根据 hash 值按照之前的流程,找到要赋值的位置(可能是插入新 key,也可能是更新老 key),对相应位置进行赋值。 22 | 23 | 源码大体和之前讲的类似,核心还是一个双层循环,外层遍历 bucket 和它的 overflow bucket,内层遍历整个 bucket 的各个 cell。限于篇幅,这部分代码的注释我也不展示了,有兴趣的可以去看,保证理解了这篇文章内容后,能够看懂。 24 | 25 | 我这里会针对这个过程提几点重要的。 26 | 27 | 函数首先会检查 map 的标志位 flags。如果 flags 的写标志位此时被置 1 了,说明有其他协程在执行“写”操作,进而导致程序 panic。这也说明了 map 对协程是不安全的。 28 | 29 | 通过前文我们知道扩容是渐进式的,如果 map 处在扩容的过程中,那么当 key 定位到了某个 bucket 后,需要确保这个 bucket 对应的老 bucket 完成了迁移过程。即老 bucket 里的 key 都要迁移到新的 bucket 中来(分裂到 2 个新 bucket),才能在新的 bucket 中进行插入或者更新的操作。 30 | 31 | 上面说的操作是在函数靠前的位置进行的,只有进行完了这个搬迁操作后,我们才能放心地在新 bucket 里定位 key 要安置的地址,再进行之后的操作。 32 | 33 | 现在到了定位 key 应该放置的位置了,所谓找准自己的位置很重要。准备两个指针,一个(`inserti`)指向 key 的 hash 值在 tophash 数组所处的位置,另一个(`insertk`)指向 cell 的位置(也就是 key 最终放置的地址),当然,对应 value 的位置就很容易定位出来了。这三者实际上都是关联的,在 tophash 数组中的索引位置决定了 key 在整个 bucket 中的位置(共 8 个 key),而 value 的位置需要“跨过” 8 个 key 的长度。 34 | 35 | 在循环的过程中,inserti 和 insertk 分别指向第一个找到的空闲的 cell。如果之后在 map 没有找到 key 的存在,也就是说原来 map 中没有此 key,这意味着插入新 key。那最终 key 的安置地址就是第一次发现的“空位”(tophash 是 empty)。 36 | 37 | 如果这个 bucket 的 8 个 key 都已经放置满了,那在跳出循环后,发现 inserti 和 insertk 都是空,这时候需要在 bucket 后面挂上 overflow bucket。当然,也有可能是在 overflow bucket 后面再挂上一个 overflow bucket。这就说明,太多 key hash 到了此 bucket。 38 | 39 | 在正式安置 key 之前,还要检查 map 的状态,看它是否需要进行扩容。如果满足扩容的条件,就主动触发一次扩容操作。 40 | 41 | 这之后,整个之前的查找定位 key 的过程,还得再重新走一次。因为扩容之后,key 的分布都发生了变化。 42 | 43 | 最后,会更新 map 相关的值,如果是插入新 key,map 的元素数量字段 count 值会加 1;在函数之初设置的 `hashWriting` 写标志出会清零。 44 | 45 | 另外,有一个重要的点要说一下。前面说的找到 key 的位置,进行赋值操作,实际上并不准确。我们看 `mapassign` 函数的原型就知道,函数并没有传入 value 值,所以赋值操作是什么时候执行的呢? 46 | 47 | ```golang 48 | func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer 49 | ``` 50 | 51 | 答案还得从汇编语言中寻找。我直接揭晓答案,有兴趣可以私下去研究一下。`mapassign` 函数返回的指针就是指向的 key 所对应的 value 值位置,有了地址,就很好操作赋值了。 -------------------------------------------------------------------------------- /content/map/5-map 的删除过程是怎样的.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 205 3 | title: "删除过程" 4 | slug: /delete 5 | --- 6 | 7 | 8 | 写操作底层的执行函数是 `mapdelete`: 9 | 10 | ```golang 11 | func mapdelete(t *maptype, h *hmap, key unsafe.Pointer) 12 | ``` 13 | 14 | 根据 key 类型的不同,删除操作会被优化成更具体的函数: 15 | 16 | |key 类型|删除| 17 | |---|---| 18 | |uint32|mapdelete_fast32(t *maptype, h *hmap, key uint32)| 19 | |uint64|mapdelete_fast64(t *maptype, h *hmap, key uint64)| 20 | |string|mapdelete_faststr(t *maptype, h *hmap, ky string)| 21 | 22 | 当然,我们只关心 `mapdelete` 函数。它首先会检查 h.flags 标志,如果发现写标位是 1,直接 panic,因为这表明有其他协程同时在进行写操作。 23 | 24 | 计算 key 的哈希,找到落入的 bucket。检查此 map 如果正在扩容的过程中,直接触发一次搬迁操作。 25 | 26 | 删除操作同样是两层循环,核心还是找到 key 的具体位置。寻找过程都是类似的,在 bucket 中挨个 cell 寻找。 27 | 28 | 找到对应位置后,对 key 或者 value 进行“清零”操作: 29 | 30 | ```golang 31 | // 对 key 清零 32 | if t.indirectkey { 33 | *(*unsafe.Pointer)(k) = nil 34 | } else { 35 | typedmemclr(t.key, k) 36 | } 37 | 38 | // 对 value 清零 39 | if t.indirectvalue { 40 | *(*unsafe.Pointer)(v) = nil 41 | } else { 42 | typedmemclr(t.elem, v) 43 | } 44 | ``` 45 | 46 | 最后,将 count 值减 1,将对应位置的 tophash 值置成 `Empty`。 47 | 48 | 这块源码同样比较简单,感兴起直接去看代码。 -------------------------------------------------------------------------------- /content/map/7-map 中的 key 为什么是无序的.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 207 3 | title: "key 为什么是无序的" 4 | slug: /unordered 5 | --- 6 | 7 | 8 | map 在扩容后,会发生 key 的搬迁,原来落在同一个 bucket 中的 key,搬迁后,有些 key 就要远走高飞了(bucket 序号加上了 2^B)。而遍历的过程,就是按顺序遍历 bucket,同时按顺序遍历 bucket 中的 key。搬迁后,key 的位置发生了重大的变化,有些 key 飞上高枝,有些 key 则原地不动。这样,遍历 map 的结果就不可能按原来的顺序了。 9 | 10 | 当然,如果我就一个 hard code 的 map,我也不会向 map 进行插入删除的操作,按理说每次遍历这样的 map 都会返回一个固定顺序的 key/value 序列吧。的确是这样,但是 Go 杜绝了这种做法,因为这样会给新手程序员带来误解,以为这是一定会发生的事情,在某些情况下,可能会酿成大错。 11 | 12 | 当然,Go 做得更绝,当我们在遍历 map 时,并不是固定地从 0 号 bucket 开始遍历,每次都是从一个随机值序号的 bucket 开始遍历,并且是从这个 bucket 的一个随机序号的 cell 开始遍历。这样,即使你是一个写死的 map,仅仅只是遍历它,也不太可能会返回一个固定序列的 key/value 对了。 13 | 14 | 多说一句,“迭代 map 的结果是无序的”这个特性是从 go 1.0 开始加入的。 -------------------------------------------------------------------------------- /content/map/9-可以边遍历边删除吗.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 209 3 | title: "可以边遍历边删除吗" 4 | slug: /delete-on-range 5 | --- 6 | 7 | map 并不是一个线程安全的数据结构。同时读写一个 map 是未定义的行为,如果被检测到,会直接 panic。 8 | 9 | 上面说的是发生在多个协程同时读写同一个 map 的情况下。 如果在同一个协程内边遍历边删除,并不会检测到同时读写,理论上是可以这样做的。但是,遍历的结果就可能不会是相同的了,有可能结果遍历结果集中包含了删除的 key,也有可能不包含,这取决于删除 key 的时间:是在遍历到 key 所在的 bucket 时刻前或者后。 10 | 11 | 一般而言,这可以通过读写锁来解决:`sync.RWMutex`。 12 | 13 | 读之前调用 `RLock()` 函数,读完之后调用 `RUnlock()` 函数解锁;写之前调用 `Lock()` 函数,写完之后,调用 `Unlock()` 解锁。 14 | 15 | 另外,`sync.Map` 是线程安全的 map,也可以使用。 -------------------------------------------------------------------------------- /content/map/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 200 3 | bookFlatSection: true 4 | bookCollapseSection: true 5 | title: "哈希表" 6 | slug: / 7 | --- -------------------------------------------------------------------------------- /content/map/assets/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/map/assets/0.png -------------------------------------------------------------------------------- /content/map/assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/map/assets/1.png -------------------------------------------------------------------------------- /content/map/assets/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/map/assets/10.png -------------------------------------------------------------------------------- /content/map/assets/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/map/assets/11.png -------------------------------------------------------------------------------- /content/map/assets/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/map/assets/12.png -------------------------------------------------------------------------------- /content/map/assets/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/map/assets/13.png -------------------------------------------------------------------------------- /content/map/assets/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/map/assets/14.png -------------------------------------------------------------------------------- /content/map/assets/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/map/assets/15.png -------------------------------------------------------------------------------- /content/map/assets/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/map/assets/16.png -------------------------------------------------------------------------------- /content/map/assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/map/assets/2.png -------------------------------------------------------------------------------- /content/map/assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/map/assets/3.png -------------------------------------------------------------------------------- /content/map/assets/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/map/assets/4.png -------------------------------------------------------------------------------- /content/map/assets/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/map/assets/5.png -------------------------------------------------------------------------------- /content/map/assets/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/map/assets/6.png -------------------------------------------------------------------------------- /content/map/assets/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/map/assets/7.png -------------------------------------------------------------------------------- /content/map/assets/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/map/assets/8.png -------------------------------------------------------------------------------- /content/map/assets/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/map/assets/9.png -------------------------------------------------------------------------------- /content/memgc/5-总结.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 805 3 | title: "总结" 4 | slug: /summary 5 | --- 6 | 7 | # 总结 8 | 9 | GC 是一个复杂的系统工程,本文讨论的二十个问题尽管已经展现了一个相对全面的 Go GC。 10 | 但它们仍然只是 GC 这一宏观问题的一小部分较为重要的内容,还有非常多的细枝末节、研究进展无法在有限的篇幅内完整讨论。 11 | 12 | 从 Go 诞生之初,Go 团队就一直在对 GC 的表现进行实验与优化,但仍然有诸多未解决的公开问题,我们不妨对 GC 未来的改进拭目以待。 13 | 14 | # 进一步阅读的主要参考文献 15 | 16 | - [1] Ian Lance Taylor. Why golang garbage-collector not implement Generational and Compact gc? May 2017. https://groups.google.com/forum/#!msg/golang-nuts/KJiyv2mV2pU/wdBUH1mHCAAJ 17 | - [2] Go Team. `debug.GCStats`. Last access: Jan, 2020. https://golang.org/pkg/runtime/debug/#GCStats 18 | - [3] Go Team. `runtime.MemStats`. Last access: Jan, 2020. https://golang.org/pkg/runtime/#MemStats 19 | - [4] Austin Clements, Rick Hudson. Proposal: Eliminate STW stack re-scanning. Oct, 2016. https://github.com/golang/proposal/blob/master/design/17503-eliminate-rescan.md 20 | - [5] Austin Clements. Go 1.5 concurrent garbage collector pacing. Mar, 2015. https://docs.google.com/document/d/1wmjrocXIWTr1JxU-3EQBI6BK6KgtiFArkG47XK73xIQ/edit# 21 | - [6] Austin Clements. Proposal: Separate soft and hard heap size goal. Oct, 2017. https://github.com/golang/proposal/blob/master/design/14951-soft-heap-limit.md 22 | - [7] Go Team. HTTP pprof. Last access: Jan, 2020. https://golang.org/pkg/net/http/pprof/ 23 | - [8] Go Team. Runtime pprof. Last access: Jan, 2020. https://golang.org/pkg/runtime/pprof/ 24 | - [9] Go Team. Package trace. Last access: Jan, 2020. https://golang.org/pkg/runtime/trace/ 25 | - [10] Caleb Spare. proposal: runtime: add a mechanism for specifying a minimum target heap size. Last access: Jan, 2020. https://github.com/golang/go/issues/23044 26 | - [11] Austin Clements, Rick Hudson. Proposal: Concurrent stack re-scanning. Oct, 2016. https://github.com/golang/proposal/blob/master/design/17505-concurrent-rescan.md 27 | - [12] Rick Hudson, Austin Clements. Request Oriented Collector (ROC) Algorithm. Jun, 2016. https://docs.google.com/document/d/1gCsFxXamW8RRvOe5hECz98Ftk-tcRRJcDFANj2VwCB0/edit 28 | - [13] Rick Hudson. runtime: constants and data structures for generational GC. Mar, 2019. https://go-review.googlesource.com/c/go/+/137476/12 29 | - [14] Austin Clements. Sub-millisecond GC pauses. Oct, 2016. https://groups.google.com/d/msg/golang-dev/Ab1sFeoZg_8/_DaL0E8fAwAJ 30 | - [15] Austin Clements. runtime: error message: P has cached GC work at end of mark termination. Nov, 2018. https://github.com/golang/go/issues/27993#issuecomment-441719687 31 | 32 | # 其他参考文献 33 | 34 | - [16] Dmitry Soshnikov. Writing a Memory Allocator. Feb. 2019. http://dmitrysoshnikov.com/compilers/writing-a-memory-allocator/#more-3590 35 | - [17] William Kennedy. Garbage Collection In Go : Part II - GC Traces. May 2019. https://www.ardanlabs.com/blog/2019/05/garbage-collection-in-go-part2-gctraces.html 36 | - [18] Rhys Hiltner. An Introduction to go tool trace. Last access: Jan, 2020. https://about.sourcegraph.com/go/an-introduction-to-go-tool-trace-rhys-hiltner 37 | - [19] 煎鱼. 用 GODEBUG 看 GC. Sep, 2019. https://segmentfault.com/a/1190000020255157 38 | - [20] 煎鱼. Go 大杀器之跟踪剖析 trace. Last access: Jan, 2020. https://eddycjy.gitbook.io/golang/di-9-ke-gong-ju/go-tool-trace 39 | 40 | -------------------------------------------------------------------------------- /content/memgc/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 800 3 | bookFlatSection: true 4 | bookCollapseSection: true 5 | title: "垃圾回收器" 6 | slug: / 7 | --- -------------------------------------------------------------------------------- /content/memgc/assets/gc-blueprint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc-blueprint.png -------------------------------------------------------------------------------- /content/memgc/assets/gc-leak1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc-leak1.png -------------------------------------------------------------------------------- /content/memgc/assets/gc-mark-assist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc-mark-assist.png -------------------------------------------------------------------------------- /content/memgc/assets/gc-mark-sweep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc-mark-sweep.png -------------------------------------------------------------------------------- /content/memgc/assets/gc-mutator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc-mutator.png -------------------------------------------------------------------------------- /content/memgc/assets/gc-pacing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc-pacing.png -------------------------------------------------------------------------------- /content/memgc/assets/gc-process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc-process.png -------------------------------------------------------------------------------- /content/memgc/assets/gc-trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc-trace.png -------------------------------------------------------------------------------- /content/memgc/assets/gc-trace2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc-trace2.png -------------------------------------------------------------------------------- /content/memgc/assets/gc-trigger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc-trigger.png -------------------------------------------------------------------------------- /content/memgc/assets/gc-trigger2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc-trigger2.png -------------------------------------------------------------------------------- /content/memgc/assets/gc-trigger3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc-trigger3.png -------------------------------------------------------------------------------- /content/memgc/assets/gc-tuning-ex1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc-tuning-ex1-1.png -------------------------------------------------------------------------------- /content/memgc/assets/gc-tuning-ex1-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc-tuning-ex1-2.png -------------------------------------------------------------------------------- /content/memgc/assets/gc-tuning-ex1-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc-tuning-ex1-3.png -------------------------------------------------------------------------------- /content/memgc/assets/gc-tuning-ex1-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc-tuning-ex1-4.png -------------------------------------------------------------------------------- /content/memgc/assets/gc-tuning-ex2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc-tuning-ex2-1.png -------------------------------------------------------------------------------- /content/memgc/assets/gc-tuning-ex2-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc-tuning-ex2-2.png -------------------------------------------------------------------------------- /content/memgc/assets/gc-tuning-ex2-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc-tuning-ex2-3.png -------------------------------------------------------------------------------- /content/memgc/assets/gc-tuning-ex3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc-tuning-ex3.png -------------------------------------------------------------------------------- /content/memgc/assets/gc-wb-dijkstra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc-wb-dijkstra.png -------------------------------------------------------------------------------- /content/memgc/assets/gc-wb-yuasa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc-wb-yuasa.png -------------------------------------------------------------------------------- /content/memgc/assets/gc1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc1.png -------------------------------------------------------------------------------- /content/memgc/assets/gc2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc2.png -------------------------------------------------------------------------------- /content/memgc/assets/gc3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/memgc/assets/gc3.png -------------------------------------------------------------------------------- /content/memgc/code/11/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | "runtime/trace" 7 | "sync/atomic" 8 | ) 9 | 10 | var stop uint64 11 | 12 | // 通过对象 P 的释放状态,来确定 GC 是否已经完成 13 | func gcfinished() *int { 14 | p := 1 15 | runtime.SetFinalizer(&p, func(_ *int) { 16 | println("gc finished") 17 | atomic.StoreUint64(&stop, 1) // 通知停止分配 18 | }) 19 | return &p 20 | } 21 | 22 | func allocate() { 23 | // 每次调用分配 0.25MB 24 | _ = make([]byte, int((1<<20)*0.25)) 25 | } 26 | 27 | func main() { 28 | f, _ := os.Create("trace.out") 29 | defer f.Close() 30 | trace.Start(f) 31 | defer trace.Stop() 32 | 33 | gcfinished() 34 | 35 | // 当完成 GC 时停止分配 36 | for n := 1; atomic.LoadUint64(&stop) != 1; n++ { 37 | println("#allocate: ", n) 38 | allocate() 39 | } 40 | println("terminate") 41 | } 42 | -------------------------------------------------------------------------------- /content/memgc/code/14/1/after/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "runtime" 7 | "runtime/trace" 8 | "sync" 9 | "sync/atomic" 10 | "time" 11 | ) 12 | 13 | var ( 14 | stop int32 15 | count int64 16 | sum time.Duration 17 | ) 18 | 19 | func concat() { 20 | wg := sync.WaitGroup{} 21 | for n := 0; n < 100; n++ { 22 | wg.Add(8) 23 | for i := 0; i < 8; i++ { 24 | go func() { 25 | s := make([]byte, 0, 20) 26 | s = append(s, "Go GC"...) 27 | s = append(s, ' ') 28 | s = append(s, "Hello"...) 29 | s = append(s, ' ') 30 | s = append(s, "World"...) 31 | _ = string(s) 32 | wg.Done() 33 | }() 34 | } 35 | wg.Wait() 36 | } 37 | } 38 | 39 | func main() { 40 | f, _ := os.Create("trace.out") 41 | defer f.Close() 42 | trace.Start(f) 43 | defer trace.Stop() 44 | 45 | go func() { 46 | var t time.Time 47 | for atomic.LoadInt32(&stop) == 0 { 48 | t = time.Now() 49 | runtime.GC() 50 | sum += time.Since(t) 51 | count++ 52 | } 53 | fmt.Printf("GC spend avg: %v\n", time.Duration(int64(sum)/count)) 54 | }() 55 | 56 | concat() 57 | atomic.StoreInt32(&stop, 1) 58 | } 59 | -------------------------------------------------------------------------------- /content/memgc/code/14/1/before/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "runtime" 7 | "runtime/trace" 8 | "sync/atomic" 9 | "time" 10 | ) 11 | 12 | var ( 13 | stop int32 14 | count int64 15 | sum time.Duration 16 | ) 17 | 18 | func concat() { 19 | for n := 0; n < 100; n++ { 20 | for i := 0; i < 8; i++ { 21 | go func() { 22 | s := "Go GC" 23 | s += " " + "Hello" 24 | s += " " + "World" 25 | _ = s 26 | }() 27 | } 28 | } 29 | } 30 | 31 | func main() { 32 | f, _ := os.Create("trace.out") 33 | defer f.Close() 34 | trace.Start(f) 35 | defer trace.Stop() 36 | 37 | go func() { 38 | var t time.Time 39 | for atomic.LoadInt32(&stop) == 0 { 40 | t = time.Now() 41 | runtime.GC() 42 | sum += time.Since(t) 43 | count++ 44 | } 45 | fmt.Printf("GC spend avg: %v\n", time.Duration(int64(sum)/count)) 46 | }() 47 | 48 | concat() 49 | atomic.StoreInt32(&stop, 1) 50 | } 51 | -------------------------------------------------------------------------------- /content/memgc/code/14/2/after/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | _ "net/http/pprof" 7 | "sync" 8 | ) 9 | 10 | var bufPool = sync.Pool{ 11 | New: func() interface{} { 12 | return make([]byte, 10<<20) 13 | }, 14 | } 15 | 16 | func main() { 17 | go func() { 18 | http.ListenAndServe("localhost:6060", nil) 19 | }() 20 | http.HandleFunc("/example2", func(w http.ResponseWriter, r *http.Request) { 21 | b := bufPool.Get().([]byte) 22 | for idx := range b { 23 | b[idx] = 0 24 | } 25 | fmt.Fprintf(w, "done, %v", r.URL.Path[1:]) 26 | bufPool.Put(b) 27 | }) 28 | http.ListenAndServe(":8080", nil) 29 | } 30 | -------------------------------------------------------------------------------- /content/memgc/code/14/2/before/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | _ "net/http/pprof" 7 | ) 8 | 9 | func newBuf() []byte { 10 | return make([]byte, 10<<20) 11 | } 12 | 13 | func main() { 14 | go func() { 15 | http.ListenAndServe("localhost:6060", nil) 16 | }() 17 | http.HandleFunc("/example2", func(w http.ResponseWriter, r *http.Request) { 18 | b := newBuf() 19 | for idx := range b { 20 | b[idx] = 1 21 | } 22 | fmt.Fprintf(w, "done, %v", r.URL.Path[1:]) 23 | }) 24 | http.ListenAndServe(":8080", nil) 25 | } 26 | -------------------------------------------------------------------------------- /content/memgc/code/20/1.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "runtime" 7 | "runtime/trace" 8 | "time" 9 | ) 10 | 11 | const ( 12 | windowSize = 200000 13 | msgCount = 1000000 14 | ) 15 | 16 | var ( 17 | best time.Duration = time.Second 18 | bestAt time.Time 19 | worst time.Duration 20 | worstAt time.Time 21 | 22 | start = time.Now() 23 | ) 24 | 25 | func main() { 26 | f, _ := os.Create("trace.out") 27 | defer f.Close() 28 | trace.Start(f) 29 | defer trace.Stop() 30 | 31 | for i := 0; i < 5; i++ { 32 | measure() 33 | worst = 0 34 | best = time.Second 35 | runtime.GC() 36 | } 37 | } 38 | 39 | func measure() { 40 | var c channel 41 | for i := 0; i < msgCount; i++ { 42 | c.sendMsg(i) 43 | } 44 | fmt.Printf("Best send delay %v at %v, worst send delay: %v at %v. Wall clock: %v \n", best, bestAt.Sub(start), worst, worstAt.Sub(start), time.Since(start)) 45 | } 46 | 47 | type channel [windowSize][]byte 48 | 49 | func (c *channel) sendMsg(id int) { 50 | start := time.Now() 51 | 52 | // 模拟发送 53 | (*c)[id%windowSize] = newMsg(id) 54 | 55 | end := time.Now() 56 | elapsed := end.Sub(start) 57 | if elapsed > worst { 58 | worst = elapsed 59 | worstAt = end 60 | } 61 | if elapsed < best { 62 | best = elapsed 63 | bestAt = end 64 | } 65 | } 66 | 67 | func newMsg(n int) []byte { 68 | m := make([]byte, 1024) 69 | for i := range m { 70 | m[i] = byte(n) 71 | } 72 | return m 73 | } 74 | -------------------------------------------------------------------------------- /content/memgc/code/20/4.txt: -------------------------------------------------------------------------------- 1 | goos: darwin 2 | goarch: amd64 3 | BenchmarkGCLargeGs 4 | BenchmarkGCLargeGs/#g-100 5 | BenchmarkGCLargeGs/#g-100-12 6670 181526 ns/op 6 | BenchmarkGCLargeGs/#g-100-12 6050 188918 ns/op 7 | BenchmarkGCLargeGs/#g-100-12 6156 195642 ns/op 8 | BenchmarkGCLargeGs/#g-100-12 6067 197367 ns/op 9 | BenchmarkGCLargeGs/#g-100-12 6164 194289 ns/op 10 | BenchmarkGCLargeGs/#g-1000 11 | BenchmarkGCLargeGs/#g-1000-12 3544 328971 ns/op 12 | BenchmarkGCLargeGs/#g-1000-12 3628 331038 ns/op 13 | BenchmarkGCLargeGs/#g-1000-12 3621 332077 ns/op 14 | BenchmarkGCLargeGs/#g-1000-12 3603 332605 ns/op 15 | BenchmarkGCLargeGs/#g-1000-12 3477 332496 ns/op 16 | BenchmarkGCLargeGs/#g-10000 17 | BenchmarkGCLargeGs/#g-10000-12 939 1211196 ns/op 18 | BenchmarkGCLargeGs/#g-10000-12 927 1228460 ns/op 19 | BenchmarkGCLargeGs/#g-10000-12 925 1211521 ns/op 20 | BenchmarkGCLargeGs/#g-10000-12 920 1227664 ns/op 21 | BenchmarkGCLargeGs/#g-10000-12 906 1232928 ns/op 22 | BenchmarkGCLargeGs/#g-100000 23 | BenchmarkGCLargeGs/#g-100000-12 100 11134261 ns/op 24 | BenchmarkGCLargeGs/#g-100000-12 108 10581869 ns/op 25 | BenchmarkGCLargeGs/#g-100000-12 110 11042436 ns/op 26 | BenchmarkGCLargeGs/#g-100000-12 100 10828657 ns/op 27 | BenchmarkGCLargeGs/#g-100000-12 100 10927736 ns/op 28 | BenchmarkGCLargeGs/#g-1000000 29 | BenchmarkGCLargeGs/#g-1000000-12 42 31126262 ns/op 30 | BenchmarkGCLargeGs/#g-1000000-12 33 32082860 ns/op 31 | BenchmarkGCLargeGs/#g-1000000-12 33 33453187 ns/op 32 | BenchmarkGCLargeGs/#g-1000000-12 34 32805587 ns/op 33 | BenchmarkGCLargeGs/#g-1000000-12 34 32812612 ns/op 34 | PASS 35 | ok _/Users/changkun/dev/Go-Questions/GC/code/20 55.959s 36 | -------------------------------------------------------------------------------- /content/memgc/code/20/4_test.go: -------------------------------------------------------------------------------- 1 | package main_test 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "sync" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func BenchmarkGCLargeGs(b *testing.B) { 12 | wg := sync.WaitGroup{} 13 | 14 | for ng := 100; ng <= 1000000; ng *= 10 { 15 | b.Run(fmt.Sprintf("#g-%d", ng), func(b *testing.B) { 16 | // Prepare loads of goroutines and wait 17 | // all goroutines terminate. 18 | wg.Add(ng) 19 | for i := 0; i < ng; i++ { 20 | go func() { 21 | time.Sleep(100 * time.Millisecond) 22 | wg.Done() 23 | }() 24 | } 25 | wg.Wait() 26 | 27 | // Run GC once for cleanup 28 | runtime.GC() 29 | 30 | // Now record GC scalability 31 | b.ResetTimer() 32 | for i := 0; i < b.N; i++ { 33 | runtime.GC() 34 | } 35 | }) 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /content/memgc/code/5/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "runtime" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | go func() { 10 | for { 11 | } 12 | }() 13 | 14 | time.Sleep(time.Millisecond) 15 | runtime.GC() 16 | println("OK") 17 | } 18 | -------------------------------------------------------------------------------- /content/memgc/code/6/gc.txt: -------------------------------------------------------------------------------- 1 | $ GODEBUG=gctrace=1 ./main 2 | 3 | gc 1 @0.000s 2%: 0.009+0.23+0.004 ms clock, 0.11+0.083/0.019/0.14+0.049 ms cpu, 4->6->2 MB, 5 MB goal, 12 P 4 | scvg: 8 KB released 5 | scvg: inuse: 3, idle: 60, sys: 63, released: 57, consumed: 6 (MB) 6 | gc 2 @0.001s 2%: 0.018+1.1+0.029 ms clock, 0.22+0.047/0.074/0.048+0.34 ms cpu, 4->7->3 MB, 5 MB goal, 12 P 7 | scvg: inuse: 3, idle: 60, sys: 63, released: 56, consumed: 7 (MB) 8 | gc 3 @0.003s 2%: 0.018+0.59+0.011 ms clock, 0.22+0.073/0.008/0.042+0.13 ms cpu, 5->6->1 MB, 6 MB goal, 12 P 9 | scvg: 8 KB released 10 | scvg: inuse: 2, idle: 61, sys: 63, released: 56, consumed: 7 (MB) 11 | gc 4 @0.003s 4%: 0.019+0.70+0.054 ms clock, 0.23+0.051/0.047/0.085+0.65 ms cpu, 4->6->2 MB, 5 MB goal, 12 P 12 | scvg: 8 KB released 13 | scvg: inuse: 3, idle: 60, sys: 63, released: 56, consumed: 7 (MB) 14 | scvg: 8 KB released 15 | scvg: inuse: 4, idle: 59, sys: 63, released: 56, consumed: 7 (MB) 16 | gc 5 @0.004s 12%: 0.021+0.26+0.49 ms clock, 0.26+0.046/0.037/0.11+5.8 ms cpu, 4->7->3 MB, 5 MB goal, 12 P 17 | scvg: inuse: 5, idle: 58, sys: 63, released: 56, consumed: 7 (MB) 18 | gc 6 @0.005s 12%: 0.020+0.17+0.004 ms clock, 0.25+0.080/0.070/0.053+0.051 ms cpu, 5->6->1 MB, 6 MB goal, 12 P 19 | scvg: 8 KB released 20 | scvg: inuse: 5, idle: 58, sys: 63, released: 56, consumed: 7 -------------------------------------------------------------------------------- /content/memgc/code/6/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "runtime" 7 | "runtime/debug" 8 | "runtime/trace" 9 | "time" 10 | ) 11 | 12 | func printGCStats() { 13 | t := time.NewTicker(time.Second) 14 | s := debug.GCStats{} 15 | for { 16 | select { 17 | case <-t.C: 18 | debug.ReadGCStats(&s) 19 | fmt.Printf("gc %d last@%v, PauseTotal %v\n", s.NumGC, s.LastGC, s.PauseTotal) 20 | } 21 | } 22 | } 23 | 24 | func printMemStats() { 25 | t := time.NewTicker(time.Second) 26 | s := runtime.MemStats{} 27 | 28 | for { 29 | select { 30 | case <-t.C: 31 | runtime.ReadMemStats(&s) 32 | fmt.Printf("gc %d last@%v, next_heap_size@%vMB\n", s.NumGC, time.Unix(int64(time.Duration(s.LastGC).Seconds()), 0), s.NextGC/(1<<20)) 33 | } 34 | } 35 | } 36 | 37 | func allocate() { 38 | _ = make([]byte, 1<<20) 39 | } 40 | 41 | func main() { 42 | // go printGCStats() 43 | // go printMemStats() 44 | 45 | f, _ := os.Create("trace.out") 46 | defer f.Close() 47 | trace.Start(f) 48 | defer trace.Stop() 49 | 50 | for n := 1; n < 100000; n++ { 51 | allocate() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /content/memgc/code/7/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "runtime/trace" 6 | ) 7 | 8 | var cache = map[interface{}]interface{}{} 9 | 10 | func keepalloc() { 11 | for i := 0; i < 10000; i++ { 12 | m := make([]byte, 1<<10) 13 | cache[i] = m 14 | } 15 | } 16 | 17 | func keepalloc2() { 18 | for i := 0; i < 100000; i++ { 19 | go func() { 20 | select {} 21 | }() 22 | } 23 | } 24 | 25 | var ch = make(chan struct{}) 26 | 27 | func keepalloc3() { 28 | for i := 0; i < 100000; i++ { 29 | // 没有接收方,goroutine 会一直阻塞 30 | go func() { ch <- struct{}{} }() 31 | } 32 | } 33 | 34 | func main() { 35 | f, _ := os.Create("trace.out") 36 | defer f.Close() 37 | trace.Start(f) 38 | defer trace.Stop() 39 | keepalloc() 40 | keepalloc2() 41 | keepalloc3() 42 | } 43 | -------------------------------------------------------------------------------- /content/sched/1-goroutine和线程的区别.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 701 3 | title: "goroutine 和线程的区别" 4 | slug: /goroutine-vs-thread 5 | --- 6 | 7 | 谈到 goroutine,绕不开的一个话题是:它和 thread 有什么区别? 8 | 9 | 参考资料【How Goroutines Work】告诉我们可以从三个角度区别:内存消耗、创建与销毀、切换。 10 | 11 | - 内存占用 12 | 13 | 创建一个 goroutine 的栈内存消耗为 2 KB,实际运行过程中,如果栈空间不够用,会自动进行扩容。创建一个 thread 则需要消耗 1 MB 栈内存,而且还需要一个被称为 “a guard page” 的区域用于和其他 thread 的栈空间进行隔离。 14 | 15 | 对于一个用 Go 构建的 HTTP Server 而言,对到来的每个请求,创建一个 goroutine 用来处理是非常轻松的一件事。而如果用一个使用线程作为并发原语的语言构建的服务,例如 Java 来说,每个请求对应一个线程则太浪费资源了,很快就会出 OOM 错误(OutOfMemoryError)。 16 | 17 | - 创建和销毀 18 | 19 | Thread 创建和销毀都会有巨大的消耗,因为要和操作系统打交道,是内核级的,通常解决的办法就是线程池。而 goroutine 因为是由 Go runtime 负责管理的,创建和销毁的消耗非常小,是用户级。 20 | 21 | - 切换 22 | 23 | 当 threads 切换时,需要保存各种寄存器,以便将来恢复: 24 | 25 | > 16 general purpose registers, PC (Program Counter), SP (Stack Pointer), segment registers, 16 XMM registers, FP coprocessor state, 16 AVX registers, all MSRs etc. 26 | 27 | 而 goroutines 切换只需保存三个寄存器:Program Counter, Stack Pointer and BP。 28 | 29 | 一般而言,线程切换会消耗 1000-1500 纳秒,一个纳秒平均可以执行 12-18 条指令。所以由于线程切换,执行指令的条数会减少 12000-18000。 30 | 31 | Goroutine 的切换约为 200 ns,相当于 2400-3600 条指令。 32 | 33 | 因此,goroutines 切换成本比 threads 要小得多。 -------------------------------------------------------------------------------- /content/sched/12-schedule 循环如何运转.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 712 3 | title: "schedule 循环如何运转" 4 | slug: /sched-loop-exec 5 | --- 6 | 7 | 上一节,我们讲完 main goroutine 以及普通 goroutine 的退出过程。main goroutine 退出后直接调用 exit(0) 使得整个进程退出,而普通 goroutine 退出后,则进行了一系列的调用,最终又切到 g0 栈,执行 schedule 函数。 8 | 9 | 从前面的文章我们知道,普通 goroutine(gp)就是在 schedule 函数中被选中,然后才有机会执行。而现在,gp 执行完之后,再次进入 schedule 函数,形成一个循环。这个循环太长了,我们有必要再重新梳理一下。 10 | 11 | ![调度循环](../assets/3.png) 12 | 13 | 如图所示,rt0_go 负责 Go 程序启动的所有初始化,中间进行了很多初始化工作,调用 mstart 之前,已经切换到了 g0 栈,图中不同色块表示使用不同的栈空间。 14 | 15 | 接着调用 gogo 函数,完成从 g0 栈到用户 goroutine 栈的切换,包括 main goroutine 和普通 goroutine。 16 | 17 | 之后,执行 main 函数或者用户自定义的 goroutine 任务。 18 | 19 | 执行完成后,main goroutine 直接调用 eixt(0) 退出,普通 goroutine 则调用 goexit -> goexit1 -> mcall,完成普通 goroutine 退出后的清理工作,然后切换到 g0 栈,调用 goexit0 函数,将普通 goroutine 添加到缓存池中,再调用 schedule 函数进行新一轮的调度。 20 | 21 | ```shell 22 | schedule() -> execute() -> gogo() -> goroutine 任务 -> goexit() -> goexit1() -> mcall() -> goexit0() -> schedule() 23 | ``` 24 | 25 | > 可以看出,一轮调度从调用 schedule 函数开始,经过一系列过程再次调用 schedule 函数来进行新一轮的调度,从一轮调度到新一轮调度的过程称之为一个调度循环。 26 | 27 | > 这里说的调度循环是指某一个工作线程的调度循环,而同一个Go 程序中存在多个工作线程,每个工作线程都在进行着自己的调度循环。 28 | 29 | > 从前面的代码分析可以得知,上面调度循环中的每一个函数调用都没有返回,虽然 `goroutine 任务-> goexit() -> goexit1() -> mcall()` 是在 g2 的栈空间执行的,但剩下的函数都是在 g0 的栈空间执行的。 30 | 31 | > 那么问题就来了,在一个复杂的程序中,调度可能会进行无数次循环,也就是说会进行无数次没有返回的函数调用,大家都知道,每调用一次函数都会消耗一定的栈空间,而如果一直这样无返回的调用下去无论 g0 有多少栈空间终究是会耗尽的,那么这里是不是有问题?其实没有问题!关键点就在于,每次执行 mcall 切换到 g0 栈时都是切换到 g0.sched.sp 所指的固定位置,这之所以行得通,正是因为从 schedule 函数开始之后的一系列函数永远都不会返回,所以重用这些函数上一轮调度时所使用过的栈内存是没有问题的。 32 | 33 | 我再解释一下:栈空间在调用函数时会自动“增大”,而函数返回时,会自动“减小”,这里的增大和减小是指栈顶指针 SP 的变化。上述这些函数都没有返回,说明调用者不需要用到被调用者的返回值,有点像“尾递归”。 34 | 35 | 因为 g0 一直没有动过,所有它之前保存的 sp 还能继续使用。每一次调度循环都会覆盖上一次调度循环的栈数据,完美! 36 | 37 | # 参考资料 38 | 【阿波张 非 main goroutine 的退出及调度循环】https://mp.weixin.qq.com/s/XttP9q7-PO7VXhskaBzGqA -------------------------------------------------------------------------------- /content/sched/15-一个调度相关的陷阱.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 715 3 | title: "一个调度相关的陷阱" 4 | slug: /sched-trap 5 | --- 6 | 7 | > 注:这个陷阱已经在 Go 1.14 中基于信号实现了强制抢占而解决。 8 | 9 | 由于 Go 语言是协作式的调度,不会像线程那样,在时间片用完后,由 CPU 中断任务强行将其调度走。对于 Go 语言中运行时间过长的 goroutine,Go scheduler 有一个后台线程在持续监控,一旦发现 goroutine 运行超过 10 ms,会设置 goroutine 的“抢占标志位”,之后调度器会处理。但是设置标志位的时机只有在函数“序言”部分,对于没有函数调用的就没有办法了。 10 | 11 | > Golang implements a co-operative partially preemptive scheduler. 12 | 13 | 所以在某些极端情况下,会掉进一些陷阱。下面这个例子来自参考资料【scheduler 的陷阱】。 14 | 15 | ```golang 16 | func main() { 17 | var x int 18 | threads := runtime.GOMAXPROCS(0) 19 | for i := 0; i < threads; i++ { 20 | go func() { 21 | for { x++ } 22 | }() 23 | } 24 | time.Sleep(time.Second) 25 | fmt.Println("x =", x) 26 | } 27 | ``` 28 | 29 | 运行结果是:在死循环里出不来,不会输出最后的那条打印语句。 30 | 31 | 为什么?上面的例子会启动和机器的 CPU 核心数相等的 goroutine,每个 goroutine 都会执行一个无限循环。 32 | 33 | 创建完这些 goroutines 后,main 函数里执行一条 `time.Sleep(time.Second)` 语句。Go scheduler 看到这条语句后,简直高兴坏了,要来活了。这是调度的好时机啊,于是主 goroutine 被调度走。先前创建的 `threads` 个 goroutines,刚好“一个萝卜一个坑”,把 M 和 P 都占满了。 34 | 35 | 在这些 goroutine 内部,又没有调用一些诸如 `channel`,`time.sleep` 这些会引发调度器工作的事情。麻烦了,只能任由这些无限循环执行下去了。 36 | 37 | 解决的办法也有,把 threads 减小 1: 38 | 39 | ```golang 40 | func main() { 41 | var x int 42 | threads := runtime.GOMAXPROCS(0) - 1 43 | for i := 0; i < threads; i++ { 44 | go func() { 45 | for { x++ } 46 | }() 47 | } 48 | time.Sleep(time.Second) 49 | fmt.Println("x =", x) 50 | } 51 | ``` 52 | 53 | 运行结果: 54 | 55 | ```shell 56 | x = 0 57 | ``` 58 | 59 | 不难理解了吧,主 goroutine 休眠一秒后,被 go schduler 重新唤醒,调度到 M 上继续执行,打印一行语句后,退出。主 goroutine 退出后,其他所有的 goroutine 都必须跟着退出。所谓“覆巢之下 焉有完卵”,一损俱损。 60 | 61 | 至于为什么最后打印出的 x 为 0,之前的文章[《曹大谈内存重排》](https://qcrao.com/2019/06/17/cch-says-memory-reorder/)里有讲到过,这里不再深究了。 62 | 63 | 还有一种解决办法是在 for 循环里加一句: 64 | 65 | ```golang 66 | go func() { 67 | time.Sleep(time.Second) 68 | for { x++ } 69 | }() 70 | ``` 71 | 72 | 同样可以让 main goroutine 有机会调度执行。 73 | -------------------------------------------------------------------------------- /content/sched/3-goroutine 调度时机有哪些.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 703 3 | title: "goroutine 调度时机有哪些" 4 | slug: /when 5 | --- 6 | 7 | 在四种情形下,goroutine 可能会发生调度,但也并不一定会发生,只是说 Go scheduler 有机会进行调度。 8 | 9 | |情形|说明| 10 | |---|---| 11 | |使用关键字 `go`|go 创建一个新的 goroutine,Go scheduler 会考虑调度| 12 | |GC| 由于进行 GC 的 goroutine 也需要在 M 上运行,因此肯定会发生调度。当然,Go scheduler 还会做很多其他的调度,例如调度不涉及堆访问的 goroutine 来运行。GC 不管栈上的内存,只会回收堆上的内存| 13 | |系统调用|当 goroutine 进行系统调用时,会阻塞 M,所以它会被调度走,同时一个新的 goroutine 会被调度上来| 14 | |内存同步访问|atomic,mutex,channel 操作等会使 goroutine 阻塞,因此会被调度走。等条件满足后(例如其他 goroutine 解锁了)还会被调度上来继续运行| -------------------------------------------------------------------------------- /content/sched/4-什么是M:N模型.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 704 3 | title: "什么是M:N模型" 4 | slug: /mn-model 5 | --- 6 | 7 | 我们都知道,Go runtime 会负责 goroutine 的生老病死,从创建到销毁,都一手包办。Runtime 会在程序启动的时候,创建 M 个线程(CPU 执行调度的单位),之后创建的 N 个 goroutine 都会依附在这 M 个线程上执行。这就是 M:N 模型: 8 | 9 | ![M:N scheduling](../assets/11.png) 10 | 11 | 在同一时刻,一个线程上只能跑一个 goroutine。当 goroutine 发生阻塞(例如上篇文章提到的向一个 channel 发送数据,被阻塞)时,runtime 会把当前 goroutine 调度走,让其他 goroutine 来执行。目的就是不让一个线程闲着,榨干 CPU 的每一滴油水。 -------------------------------------------------------------------------------- /content/sched/5-什么是workstealing.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 705 3 | title: "什么是工作窃取" 4 | slug: /work-steal 5 | --- 6 | 7 | Go scheduler 的职责就是将所有处于 runnable 的 goroutines 均匀分布到在 P 上运行的 M。 8 | 9 | 当一个 P 发现自己的 LRQ 已经没有 G 时,会从其他 P “偷” 一些 G 来运行。看看这是什么精神!自己的工作做完了,为了全局的利益,主动为别人分担。这被称为 `Work-stealing`,Go 从 1.1 开始实现。 10 | 11 | Go scheduler 使用 M:N 模型,在任一时刻,M 个 goroutines(G) 要分配到 N 个内核线程(M),这些 M 跑在个数最多为 GOMAXPROCS 的逻辑处理器(P)上。每个 M 必须依附于一个 P,每个 P 在同一时刻只能运行一个 M。如果 P 上的 M 阻塞了,那它就需要其他的 M 来运行 P 的 LRQ 里的 goroutines。 12 | 13 | ![GPM relatioship](../assets/12.png) 14 | 15 | 个人感觉,上面这张图比常见的那些用三角形表示 M,圆形表示 G,矩形表示 P 的那些图更生动形象。 16 | 17 | 实际上,Go scheduler 每一轮调度要做的工作就是找到处于 runnable 的 goroutines,并执行它。找的顺序如下: 18 | 19 | ```golang 20 | runtime.schedule() { 21 | // only 1/61 of the time, check the global runnable queue for a G. 22 | // if not found, check the local queue. 23 | // if not found, 24 | // try to steal from other Ps. 25 | // if not, check the global runnable queue. 26 | // if not found, poll network. 27 | } 28 | ``` 29 | 30 | 找到一个可执行的 goroutine 后,就会一直执行下去,直到被阻塞。 31 | 32 | 当 P2 上的一个 G 执行结束,它就会去 LRQ 获取下一个 G 来执行。如果 LRQ 已经空了,就是说本地可运行队列已经没有 G 需要执行,并且这时 GRQ 也没有 G 了。这时,P2 会随机选择一个 P(称为 P1),P2 会从 P1 的 LRQ “偷”过来一半的 G。 33 | 34 | ![Work Stealing](../assets/13.png) 35 | 36 | 这样做的好处是,有更多的 P 可以一起工作,加速执行完所有的 G。 -------------------------------------------------------------------------------- /content/sched/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 700 3 | bookFlatSection: true 4 | bookCollapseSection: true 5 | title: "调度器" 6 | slug: / 7 | --- -------------------------------------------------------------------------------- /content/sched/assets/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/0.png -------------------------------------------------------------------------------- /content/sched/assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/1.png -------------------------------------------------------------------------------- /content/sched/assets/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/10.png -------------------------------------------------------------------------------- /content/sched/assets/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/11.png -------------------------------------------------------------------------------- /content/sched/assets/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/12.png -------------------------------------------------------------------------------- /content/sched/assets/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/13.png -------------------------------------------------------------------------------- /content/sched/assets/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/14.png -------------------------------------------------------------------------------- /content/sched/assets/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/15.png -------------------------------------------------------------------------------- /content/sched/assets/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/16.png -------------------------------------------------------------------------------- /content/sched/assets/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/17.png -------------------------------------------------------------------------------- /content/sched/assets/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/18.png -------------------------------------------------------------------------------- /content/sched/assets/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/19.png -------------------------------------------------------------------------------- /content/sched/assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/2.png -------------------------------------------------------------------------------- /content/sched/assets/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/20.png -------------------------------------------------------------------------------- /content/sched/assets/21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/21.png -------------------------------------------------------------------------------- /content/sched/assets/22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/22.png -------------------------------------------------------------------------------- /content/sched/assets/23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/23.png -------------------------------------------------------------------------------- /content/sched/assets/24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/24.png -------------------------------------------------------------------------------- /content/sched/assets/25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/25.png -------------------------------------------------------------------------------- /content/sched/assets/26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/26.png -------------------------------------------------------------------------------- /content/sched/assets/27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/27.png -------------------------------------------------------------------------------- /content/sched/assets/28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/28.png -------------------------------------------------------------------------------- /content/sched/assets/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/29.png -------------------------------------------------------------------------------- /content/sched/assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/3.png -------------------------------------------------------------------------------- /content/sched/assets/30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/30.png -------------------------------------------------------------------------------- /content/sched/assets/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/4.png -------------------------------------------------------------------------------- /content/sched/assets/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/5.png -------------------------------------------------------------------------------- /content/sched/assets/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/6.png -------------------------------------------------------------------------------- /content/sched/assets/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/7.png -------------------------------------------------------------------------------- /content/sched/assets/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/8.png -------------------------------------------------------------------------------- /content/sched/assets/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/sched/assets/9.png -------------------------------------------------------------------------------- /content/slice/1-数组和切片有什么异同.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 101 3 | title: "数组与切片有什么异同" 4 | slug: /vs-array 5 | --- 6 | 7 | slice 的底层数据是数组,slice 是对数组的封装,它描述一个数组的片段。两者都可以通过下标来访问单个元素。 8 | 9 | 数组是定长的,长度定义好之后,不能再更改。在 Go 中,数组是不常见的,因为其长度是类型的一部分,限制了它的表达能力,比如 [3]int 和 [4]int 就是不同的类型。 10 | 11 | 而切片则非常灵活,它可以动态地扩容。切片的类型和长度无关。 12 | 13 | 数组就是一片连续的内存, slice 实际上是一个结构体,包含三个字段:长度、容量、底层数组。 14 | 15 | ```golang 16 | // runtime/slice.go 17 | type slice struct { 18 | array unsafe.Pointer // 元素指针 19 | len int // 长度 20 | cap int // 容量 21 | } 22 | ``` 23 | 24 | slice 的数据结构如下: 25 | 26 | ![切片数据结构](../assets/0.png) 27 | 28 | 注意,底层数组是可以被多个 slice 同时指向的,因此对一个 slice 的元素进行操作是有可能影响到其他 slice 的。 29 | 30 | 【引申1】 31 | [3]int 和 [4]int 是同一个类型吗? 32 | 33 | 不是。因为数组的长度是类型的一部分,这是与 slice 不同的一点。 34 | 35 | 【引申2】 36 | 下面的代码输出是什么? 37 | 38 | 说明:例子来自雨痕大佬《Go学习笔记》第四版,P43页。这里我会进行扩展,并会作图详细分析。 39 | 40 | ```golang 41 | package main 42 | 43 | import "fmt" 44 | 45 | func main() { 46 | slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 47 | s1 := slice[2:5] 48 | s2 := s1[2:6:7] 49 | 50 | s2 = append(s2, 100) 51 | s2 = append(s2, 200) 52 | 53 | s1[2] = 20 54 | 55 | fmt.Println(s1) 56 | fmt.Println(s2) 57 | fmt.Println(slice) 58 | } 59 | ``` 60 | 61 | 结果: 62 | 63 | ```shell 64 | [2 3 20] 65 | [4 5 6 7 100 200] 66 | [0 1 2 3 20 5 6 7 100 9] 67 | ``` 68 | 69 | `s1` 从 `slice` 索引2(闭区间)到索引5(开区间,元素真正取到索引4),长度为3,容量默认到数组结尾,为8。 70 | `s2` 从 `s1` 的索引2(闭区间)到索引6(开区间,元素真正取到索引5),容量到索引7(开区间,真正到索引6),为5。 71 | 72 | ![slice origin](../assets/1.png) 73 | 74 | 接着,向 `s2` 尾部追加一个元素 100: 75 | 76 | ```golang 77 | s2 = append(s2, 100) 78 | ``` 79 | `s2` 容量刚好够,直接追加。不过,这会修改原始数组对应位置的元素。这一改动,数组和 `s1` 都可以看得到。 80 | 81 | ![append 100](../assets/2.png) 82 | 83 | 再次向 `s2` 追加元素200: 84 | 85 | ```golang 86 | s2 = append(s2, 200) 87 | ``` 88 | 89 | 这时,`s2` 的容量不够用,该扩容了。于是,`s2` 另起炉灶,将原来的元素复制新的位置,扩大自己的容量。并且为了应对未来可能的 `append` 带来的再一次扩容,`s2` 会在此次扩容的时候多留一些 `buffer`,将新的容量将扩大为原始容量的2倍,也就是10了。 90 | 91 | ![append 200](../assets/3.png) 92 | 93 | 最后,修改 `s1` 索引为2位置的元素: 94 | 95 | ```golang 96 | s1[2] = 20 97 | ``` 98 | 99 | 这次只会影响原始数组相应位置的元素。它影响不到 `s2` 了,人家已经远走高飞了。 100 | 101 | ![s1[2]=20](../assets/4.png) 102 | 103 | 再提一点,打印 `s1` 的时候,只会打印出 `s1` 长度以内的元素。所以,只会打印出3个元素,虽然它的底层数组不止3个元素。 104 | -------------------------------------------------------------------------------- /content/slice/3-切片作为函数参数.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 103 3 | title: "切片作为函数参数" 4 | slug: /as-func-param 5 | --- 6 | 7 | 前面我们说到,slice 其实是一个结构体,包含了三个成员:len, cap, array。分别表示切片长度,容量,底层数据的地址。 8 | 9 | 当 slice 作为函数参数时,就是一个普通的结构体。其实很好理解:若直接传 slice,在调用者看来,实参 slice 并不会被函数中的操作改变;若传的是 slice 的指针,在调用者看来,是会被改变原 slice 的。 10 | 11 | 值得注意的是,不管传的是 slice 还是 slice 指针,如果改变了 slice 底层数组的数据,会反应到实参 slice 的底层数据。为什么能改变底层数组的数据?很好理解:底层数据在 slice 结构体里是一个指针,尽管 slice 结构体自身不会被改变,也就是说底层数据地址不会被改变。 但是通过指向底层数据的指针,可以改变切片的底层数据,没有问题。 12 | 13 | 通过 slice 的 array 字段就可以拿到数组的地址。在代码里,是直接通过类似 `s[i]=10` 这种操作改变 slice 底层数组元素值。 14 | 15 | 另外,值得注意的是,Go 语言的函数参数传递,只有值传递,没有引用传递。 16 | 17 | 来看一个代码片段: 18 | 19 | ```golang 20 | package main 21 | 22 | func main() { 23 | s := []int{1, 1, 1} 24 | f(s) 25 | fmt.Println(s) 26 | } 27 | 28 | func f(s []int) { 29 | // i只是一个副本,不能改变s中元素的值 30 | /*for _, i := range s { 31 | i++ 32 | } 33 | */ 34 | 35 | for i := range s { 36 | s[i] += 1 37 | } 38 | } 39 | ``` 40 | 41 | 运行一下,程序输出: 42 | 43 | ```shell 44 | [2 2 2] 45 | ``` 46 | 47 | 果真改变了原始 slice 的底层数据。这里传递的是一个 slice 的副本,在 `f` 函数中,`s` 只是 `main` 函数中 `s` 的一个拷贝。在`f` 函数内部,对 `s` 的作用并不会改变外层 `main` 函数的 `s`。 48 | 49 | 要想真的改变外层 `slice`,只有将返回的新的 slice 赋值到原始 slice,或者向函数传递一个指向 slice 的指针。我们再来看一个例子: 50 | 51 | ```golang 52 | package main 53 | 54 | import "fmt" 55 | 56 | func myAppend(s []int) []int { 57 | // 这里 s 虽然改变了,但并不会影响外层函数的 s 58 | s = append(s, 100) 59 | return s 60 | } 61 | 62 | func myAppendPtr(s *[]int) { 63 | // 会改变外层 s 本身 64 | *s = append(*s, 100) 65 | return 66 | } 67 | 68 | func main() { 69 | s := []int{1, 1, 1} 70 | newS := myAppend(s) 71 | 72 | fmt.Println(s) 73 | fmt.Println(newS) 74 | 75 | s = newS 76 | 77 | myAppendPtr(&s) 78 | fmt.Println(s) 79 | } 80 | ``` 81 | 82 | 运行结果: 83 | 84 | ```shell 85 | [1 1 1] 86 | [1 1 1 100] 87 | [1 1 1 100 100] 88 | ``` 89 | 90 | `myAppend` 函数里,虽然改变了 `s`,但它只是一个值传递,并不会影响外层的 `s`,因此第一行打印出来的结果仍然是 `[1 1 1]`。 91 | 92 | 而 `newS` 是一个新的 `slice`,它是基于 `s` 得到的。因此它打印的是追加了一个 `100` 之后的结果: `[1 1 1 100]`。 93 | 94 | 最后,将 `newS` 赋值给了 `s`,`s` 这时才真正变成了一个新的slice。之后,再给 `myAppendPtr` 函数传入一个 `s 指针`,这回它真的被改变了:`[1 1 1 100 100]`。 95 | -------------------------------------------------------------------------------- /content/slice/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 100 3 | bookFlatSection: true 4 | bookCollapseSection: true 5 | title: "数组与切片" 6 | slug: / 7 | --- 8 | -------------------------------------------------------------------------------- /content/slice/assets/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/slice/assets/0.png -------------------------------------------------------------------------------- /content/slice/assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/slice/assets/1.png -------------------------------------------------------------------------------- /content/slice/assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/slice/assets/2.png -------------------------------------------------------------------------------- /content/slice/assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/slice/assets/3.png -------------------------------------------------------------------------------- /content/slice/assets/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/slice/assets/4.png -------------------------------------------------------------------------------- /content/stdlib/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 500 3 | bookFlatSection: true 4 | bookCollapseSection: true 5 | title: "标准库" 6 | slug: / 7 | --- -------------------------------------------------------------------------------- /content/stdlib/context/1-context 是什么.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 511 3 | title: "context 是什么" 4 | slug: /what 5 | --- 6 | 7 | Go 1.7 标准库引入 context,中文译作“上下文”,准确说它是 goroutine 的上下文,包含 goroutine 的运行状态、环境、现场等信息。 8 | 9 | context 主要用来在 goroutine 之间传递上下文信息,包括:取消信号、超时时间、截止时间、k-v 等。 10 | 11 | 随着 context 包的引入,标准库中很多接口因此加上了 context 参数,例如 database/sql 包。context 几乎成为了并发控制和超时控制的标准做法。 12 | 13 | >context.Context 类型的值可以协调多个 groutine 中的代码执行“取消”操作,并且可以存储键值对。最重要的是它是并发安全的。 14 | 15 | >与它协作的 API 都可以由外部控制执行“取消”操作,例如:取消一个 HTTP 请求的执行。 -------------------------------------------------------------------------------- /content/stdlib/context/3-context.Value 的查找过程是怎样的.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 513 3 | title: "context.Value 的查找过程是怎样的" 4 | slug: /find-value 5 | --- 6 | 7 | ```golang 8 | type valueCtx struct { 9 | Context 10 | key, val interface{} 11 | } 12 | ``` 13 | 14 | 它实现了两个方法: 15 | 16 | ```golang 17 | func (c *valueCtx) String() string { 18 | return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val) 19 | } 20 | 21 | func (c *valueCtx) Value(key interface{}) interface{} { 22 | if c.key == key { 23 | return c.val 24 | } 25 | return c.Context.Value(key) 26 | } 27 | ``` 28 | 29 | 由于它直接将 Context 作为匿名字段,因此仅管它只实现了 2 个方法,其他方法继承自父 context。但它仍然是一个 Context,这是 Go 语言的一个特点。 30 | 31 | 创建 valueCtx 的函数: 32 | 33 | ```golang 34 | func WithValue(parent Context, key, val interface{}) Context { 35 | if key == nil { 36 | panic("nil key") 37 | } 38 | if !reflect.TypeOf(key).Comparable() { 39 | panic("key is not comparable") 40 | } 41 | return &valueCtx{parent, key, val} 42 | } 43 | ``` 44 | 45 | 对 key 的要求是可比较,因为之后需要通过 key 取出 context 中的值,可比较是必须的。 46 | 47 | 通过层层传递 context,最终形成这样一棵树: 48 | 49 | ![valueCtx](../assets/2.png) 50 | 51 | 和链表有点像,只是它的方向相反:Context 指向它的父节点,链表则指向下一个节点。通过 WithValue 函数,可以创建层层的 valueCtx,存储 goroutine 间可以共享的变量。 52 | 53 | 取值的过程,实际上是一个递归查找的过程: 54 | 55 | ```golang 56 | func (c *valueCtx) Value(key interface{}) interface{} { 57 | if c.key == key { 58 | return c.val 59 | } 60 | return c.Context.Value(key) 61 | } 62 | ``` 63 | 64 | 它会顺着链路一直往上找,比较当前节点的 key 65 | 是否是要找的 key,如果是,则直接返回 value。否则,一直顺着 context 往前,最终找到根节点(一般是 emptyCtx),直接返回一个 nil。所以用 Value 方法的时候要判断结果是否为 nil。 66 | 67 | 因为查找方向是往上走的,所以,父节点没法获取子节点存储的值,子节点却可以获取父节点的值。 68 | 69 | `WithValue` 创建 context 节点的过程实际上就是创建链表节点的过程。两个节点的 key 值是可以相等的,但它们是两个不同的 context 节点。查找的时候,会向上查找到最后一个挂载的 context 节点,也就是离得比较近的一个父节点 context。所以,整体上而言,用 `WithValue` 构造的其实是一个低效率的链表。 70 | 71 | 如果你接手过项目,肯定经历过这样的窘境:在一个处理过程中,有若干子函数、子协程。各种不同的地方会向 context 里塞入各种不同的 k-v 对,最后在某个地方使用。 72 | 73 | 你根本就不知道什么时候什么地方传了什么值?这些值会不会被“覆盖”(底层是两个不同的 context 节点,查找的时候,只会返回一个结果)?你肯定会崩溃的。 74 | 75 | 而这也是 `context.Value` 最受争议的地方。很多人建议尽量不要通过 context 传值。 -------------------------------------------------------------------------------- /content/stdlib/context/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 510 3 | bookFlatSection: true 4 | bookCollapseSection: true 5 | title: "context" 6 | slug: / 7 | --- -------------------------------------------------------------------------------- /content/stdlib/context/assets/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/stdlib/context/assets/0.png -------------------------------------------------------------------------------- /content/stdlib/context/assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/stdlib/context/assets/1.png -------------------------------------------------------------------------------- /content/stdlib/context/assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/stdlib/context/assets/2.png -------------------------------------------------------------------------------- /content/stdlib/context/assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/stdlib/context/assets/3.png -------------------------------------------------------------------------------- /content/stdlib/context/assets/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/stdlib/context/assets/4.png -------------------------------------------------------------------------------- /content/stdlib/context/assets/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/stdlib/context/assets/5.png -------------------------------------------------------------------------------- /content/stdlib/reflect/1-什么是反射.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 521 3 | title: "什么是反射" 4 | slug: /what 5 | --- 6 | 7 | 维基百科上反射的定义: 8 | 9 | >在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。 10 | 11 | 难道不用反射就不能在运行时访问、检测和修改它本身的状态和行为吗? 12 | 13 | 问题的回答,其实要首先理解什么叫访问、检测和修改它本身状态或行为,它的本质是什么? 14 | 15 | 实际上,它的本质是程序在运行期探知对象的类型信息和内存结构。不用反射能行吗?可以的!使用汇编语言,直接和内层打交道,可以获取任何信息?但是,当编程迁移到高级语言上来之后,就不行了!只能通过`反射`来达到此项技能。 16 | 17 | 不同语言的反射模型不尽相同,有些语言还不支持反射。《Go 语言圣经》中是这样定义反射的: 18 | 19 | > Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。 -------------------------------------------------------------------------------- /content/stdlib/reflect/2-什么情况下需要使用反射.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 522 3 | title: "什么情况下需要使用反射" 4 | slug: /why 5 | --- 6 | 7 | 使用反射的常见场景有以下两种: 8 | 9 | 1. 不能明确接口调用哪个函数,需要根据传入的参数在运行时决定。 10 | 2. 不能明确传入函数的参数类型,需要在运行时处理任意对象。 11 | 12 | 【引申1】不推荐使用反射的理由有哪些? 13 | 14 | 1. 与反射相关的代码,经常是难以阅读的。在软件工程中,代码可读性也是一个非常重要的指标。 15 | 2. Go 语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接 panic,可能会造成严重的后果。 16 | 3. 反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性。 -------------------------------------------------------------------------------- /content/stdlib/reflect/4-Go 语言中反射有哪些应用.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 524 3 | title: "Go 语言中反射有哪些应用" 4 | slug: /application 5 | --- 6 | 7 | Go 语言中反射的应用非常广:IDE 中的代码自动补全功能、对象序列化(encoding/json)、fmt 相关函数的实现、ORM(全称是:Object Relational Mapping,对象关系映射)…… -------------------------------------------------------------------------------- /content/stdlib/reflect/5-如何比较两个对象完全相同.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 525 3 | title: "如何比较两个对象完全相同" 4 | slug: /compare 5 | --- 6 | 7 | Go 语言中提供了一个函数可以完成此项功能: 8 | 9 | ```golang 10 | func DeepEqual(x, y interface{}) bool 11 | ``` 12 | 13 | `DeepEqual` 函数的参数是两个 `interface`,实际上也就是可以输入任意类型,输出 true 或者 flase 表示输入的两个变量是否是“深度”相等。 14 | 15 | 先明白一点,如果是不同的类型,即使是底层类型相同,相应的值也相同,那么两者也不是“深度”相等。 16 | 17 | ```golang 18 | type MyInt int 19 | type YourInt int 20 | 21 | func main() { 22 | m := MyInt(1) 23 | y := YourInt(1) 24 | 25 | fmt.Println(reflect.DeepEqual(m, y)) // false 26 | } 27 | ``` 28 | 29 | 上面的代码中,m, y 底层都是 int,而且值都是 1,但是两者静态类型不同,前者是 `MyInt`,后者是 `YourInt`,因此两者不是“深度”相等。 30 | 31 | 在源码里,有对 DeepEqual 函数的非常清楚地注释,列举了不同类型,DeepEqual 的比较情形,这里做一个总结: 32 | 33 | |类型|深度相等情形| 34 | |---|---| 35 | |Array| 相同索引处的元素“深度”相等 | 36 | |Struct| 相应字段,包含导出和不导出,“深度”相等 | 37 | |Func| 只有两者都是 nil 时 | 38 | |Interface| 两者存储的具体值“深度”相等 | 39 | |Map|1、都为 nil;2、非空、长度相等,指向同一个 map 实体对象,或者相应的 key 指向的 value “深度”相等 | 40 | |Pointer|1、使用 == 比较的结果相等;2、指向的实体“深度”相等 | 41 | |Slice|1、都为 nil;2、非空、长度相等,首元素指向同一个底层数组的相同元素,即 &x[0] == &y[0] 或者 相同索引处的元素“深度”相等 | 42 | |numbers, bools, strings, and channels| 使用 == 比较的结果为真 | 43 | 44 | 一般情况下,DeepEqual 的实现只需要递归地调用 == 就可以比较两个变量是否是真的“深度”相等。 45 | 46 | 但是,有一些异常情况:比如 func 类型是不可比较的类型,只有在两个 func 类型都是 nil 的情况下,才是“深度”相等;float 类型,由于精度的原因,也是不能使用 == 比较的;包含 func 类型或者 float 类型的 struct, interface, array 等。 47 | 48 | 对于指针而言,当两个值相等的指针就是“深度”相等,因为两者指向的内容是相等的,即使两者指向的是 func 类型或者 float 类型,这种情况下不关心指针所指向的内容。 49 | 50 | 同样,对于指向相同 slice, map 的两个变量也是“深度”相等的,不关心 slice, map 具体的内容。 51 | 52 | 对于“有环”的类型,比如循环链表,比较两者是否“深度”相等的过程中,需要对已比较的内容作一个标记,一旦发现两个指针之前比较过,立即停止比较,并判定二者是深度相等的。这样做的原因是,及时停止比较,避免陷入无限循环。 53 | 54 | 来看源码: 55 | 56 | ```golang 57 | func DeepEqual(x, y interface{}) bool { 58 | if x == nil || y == nil { 59 | return x == y 60 | } 61 | v1 := ValueOf(x) 62 | v2 := ValueOf(y) 63 | if v1.Type() != v2.Type() { 64 | return false 65 | } 66 | return deepValueEqual(v1, v2, make(map[visit]bool), 0) 67 | } 68 | ``` 69 | 70 | 首先查看两者是否有一个是 nil 的情况,这种情况下,只有两者都是 nil,函数才会返回 true 71 | 72 | 接着,使用反射,获取x,y 的反射对象,并且立即比较两者的类型,根据前面的内容,这里实际上是动态类型,如果类型不同,直接返回 false。 73 | 74 | 最后,最核心的内容在子函数 `deepValueEqual` 中。 75 | 76 | 代码比较长,思路却比较简单清晰:核心是一个 switch 语句,识别输入参数的不同类型,分别递归调用 deepValueEqual 函数,一直递归到最基本的数据类型,比较 int,string 等可以直接得出 true 或者 false,再一层层地返回,最终得到“深度”相等的比较结果。 77 | 78 | 实际上,各种类型的比较套路比较相似,这里就直接节选一个稍微复杂一点的 `map` 类型的比较: 79 | 80 | ```golang 81 | // deepValueEqual 函数 82 | // …… 83 | 84 | case Map: 85 | if v1.IsNil() != v2.IsNil() { 86 | return false 87 | } 88 | if v1.Len() != v2.Len() { 89 | return false 90 | } 91 | if v1.Pointer() == v2.Pointer() { 92 | return true 93 | } 94 | for _, k := range v1.MapKeys() { 95 | val1 := v1.MapIndex(k) 96 | val2 := v2.MapIndex(k) 97 | if !val1.IsValid() || !val2.IsValid() || !deepValueEqual(v1.MapIndex(k), v2.MapIndex(k), visited, depth+1) { 98 | return false 99 | } 100 | } 101 | return true 102 | 103 | // …… 104 | ``` 105 | 106 | 和前文总结的表格里,比较 map 是否相等的思路比较一致,也不需要多说什么。说明一点,`visited` 是一个 map,记录递归过程中,比较过的“对”: 107 | 108 | ```golang 109 | type visit struct { 110 | a1 unsafe.Pointer 111 | a2 unsafe.Pointer 112 | typ Type 113 | } 114 | 115 | map[visit]bool 116 | ``` 117 | 118 | 比较过程中,一旦发现比较的“对”,已经在 map 里出现过的话,直接判定“深度”比较结果的是 `true`。 -------------------------------------------------------------------------------- /content/stdlib/reflect/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 520 3 | bookFlatSection: true 4 | bookCollapseSection: true 5 | title: "reflect" 6 | slug: / 7 | --- -------------------------------------------------------------------------------- /content/stdlib/reflect/assets/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/stdlib/reflect/assets/0.png -------------------------------------------------------------------------------- /content/stdlib/reflect/assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/stdlib/reflect/assets/1.png -------------------------------------------------------------------------------- /content/stdlib/reflect/assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/stdlib/reflect/assets/2.png -------------------------------------------------------------------------------- /content/stdlib/reflect/assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/stdlib/reflect/assets/3.png -------------------------------------------------------------------------------- /content/stdlib/reflect/assets/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/stdlib/reflect/assets/4.png -------------------------------------------------------------------------------- /content/stdlib/reflect/assets/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/stdlib/reflect/assets/5.png -------------------------------------------------------------------------------- /content/stdlib/reflect/assets/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/stdlib/reflect/assets/6.png -------------------------------------------------------------------------------- /content/stdlib/reflect/assets/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/stdlib/reflect/assets/7.png -------------------------------------------------------------------------------- /content/stdlib/unsafe/1-Go指针和unsafe.Pointer有什么区别.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 531 3 | title: "Go指针和unsafe.Pointer有什么区别" 4 | slug: /pointers 5 | --- 6 | 7 | Go 语言的作者之一 Ken Thompson 也是 C 语言的作者。所以,Go 可以看作 C 系语言,它的很多特性都和 C 类似,指针就是其中之一。 8 | 9 | 然而,Go 语言的指针相比 C 的指针有很多限制。这当然是为了安全考虑,要知道像 Java/Python 这些现代语言,生怕程序员出错,哪有什么指针(这里指的是显式的指针)?更别说像 C/C++ 还需要程序员自己清理“垃圾”。所以对于 Go 来说,有指针已经很不错了,仅管它有很多限制。 10 | 11 | 相比于 C 语言中指针的灵活,Go 的指针多了一些限制。但这也算是 Go 的成功之处:既可以享受指针带来的便利,又避免了指针的危险性。 12 | 13 | 限制一:`Go 的指针不能进行数学运算`。 14 | 15 | 来看一个简单的例子: 16 | 17 | ```golang 18 | a := 5 19 | p := &a 20 | 21 | p++ 22 | p = &a + 3 23 | ``` 24 | 25 | 上面的代码将不能通过编译,会报编译错误:`invalid operation`,也就是说不能对指针做数学运算。 26 | 27 | 限制二:`不同类型的指针不能相互转换`。 28 | 29 | 例如下面这个简短的例子: 30 | 31 | ```golang 32 | func main() { 33 | a := int(100) 34 | var f *float64 35 | 36 | f = &a 37 | } 38 | ``` 39 | 40 | 也会报编译错误: 41 | 42 | ```shell 43 | cannot use &a (type *int) as type *float64 in assignment 44 | ``` 45 | 46 | 限制三:`不同类型的指针不能使用 == 或 != 比较`。 47 | 48 | 只有在两个指针类型相同或者可以相互转换的情况下,才可以对两者进行比较。另外,指针可以通过 `==` 和 `!=` 直接和 `nil` 作比较。 49 | 50 | 限制四:`不同类型的指针变量不能相互赋值`。 51 | 52 | 这一点同限制三。 53 | 54 | unsafe.Pointer 在 unsafe 包: 55 | 56 | ```golang 57 | type ArbitraryType int 58 | 59 | type Pointer *ArbitraryType 60 | ``` 61 | 62 | 从命名来看,`Arbitrary` 是任意的意思,也就是说 Pointer 可以指向任意类型,实际上它类似于 C 语言里的 `void*`。 63 | 64 | unsafe 包提供了 2 点重要的能力: 65 | 66 | > 1. 任何类型的指针和 unsafe.Pointer 可以相互转换。 67 | > 2. uintptr 类型和 unsafe.Pointer 可以相互转换。 68 | 69 | ![type pointer uintptr](../assets/0.png) 70 | 71 | pointer 不能直接进行数学运算,但可以把它转换成 uintptr,对 uintptr 类型进行数学运算,再转换成 pointer 类型。 72 | 73 | ```golang 74 | // uintptr 是一个整数类型,它足够大,可以存储 75 | type uintptr uintptr 76 | ``` 77 | 78 | 还有一点要注意的是,uintptr 并没有指针的语义,意思就是 uintptr 所指向的对象会被 gc 无情地回收。而 unsafe.Pointer 有指针语义,可以保护它所指向的对象在“有用”的时候不会被垃圾回收。 79 | 80 | unsafe 包中的几个函数都是在编译期间执行完毕,毕竟,编译器对内存分配这些操作“了然于胸”。在 `/usr/local/go/src/cmd/compile/internal/gc/unsafe.go` 路径下,可以看到编译期间 Go 对 unsafe 包中函数的处理。 -------------------------------------------------------------------------------- /content/stdlib/unsafe/2-如何利用unsafe获取slice&map的长度.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 532 3 | title: "如何利用unsafe获取slice&map的长度" 4 | slug: /len 5 | --- 6 | 7 | # 获取 slice 长度 8 | 通过前面关于 slice 的[文章](https://mp.weixin.qq.com/s/MTZ0C9zYsNrb8wyIm2D8BA),我们知道了 slice header 的结构体定义: 9 | 10 | ```golang 11 | // runtime/slice.go 12 | type slice struct { 13 | array unsafe.Pointer // 元素指针 14 | len int // 长度 15 | cap int // 容量 16 | } 17 | ``` 18 | 19 | 调用 make 函数新建一个 slice,底层调用的是 makeslice 函数,返回的是 slice 结构体: 20 | 21 | ```golang 22 | func makeslice(et *_type, len, cap int) slice 23 | ``` 24 | 25 | 因此我们可以通过 unsafe.Pointer 和 uintptr 进行转换,得到 slice 的字段值。 26 | 27 | ```golang 28 | func main() { 29 | s := make([]int, 9, 20) 30 | var Len = *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + uintptr(8))) 31 | fmt.Println(Len, len(s)) // 9 9 32 | 33 | var Cap = *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + uintptr(16))) 34 | fmt.Println(Cap, cap(s)) // 20 20 35 | } 36 | ``` 37 | 38 | Len,cap 的转换流程如下: 39 | 40 | ```golang 41 | Len: &s => pointer => uintptr => pointer => *int => int 42 | Cap: &s => pointer => uintptr => pointer => *int => int 43 | ``` 44 | 45 | # 获取 map 长度 46 | 再来看一下上篇文章我们讲到的 map: 47 | 48 | ```golang 49 | type hmap struct { 50 | count int 51 | flags uint8 52 | B uint8 53 | noverflow uint16 54 | hash0 uint32 55 | 56 | buckets unsafe.Pointer 57 | oldbuckets unsafe.Pointer 58 | nevacuate uintptr 59 | 60 | extra *mapextra 61 | } 62 | ``` 63 | 64 | 和 slice 不同的是,makemap 函数返回的是 hmap 的指针,注意是指针: 65 | 66 | ```golang 67 | func makemap(t *maptype, hint int64, h *hmap, bucket unsafe.Pointer) *hmap 68 | ``` 69 | 70 | 我们依然能通过 unsafe.Pointer 和 uintptr 进行转换,得到 hamp 字段的值,只不过,现在 count 变成二级指针了: 71 | 72 | ```golang 73 | func main() { 74 | mp := make(map[string]int) 75 | mp["qcrao"] = 100 76 | mp["stefno"] = 18 77 | 78 | count := **(**int)(unsafe.Pointer(&mp)) 79 | fmt.Println(count, len(mp)) // 2 2 80 | } 81 | ``` 82 | 83 | count 的转换过程: 84 | 85 | ```golang 86 | &mp => pointer => **int => int 87 | ``` 88 | -------------------------------------------------------------------------------- /content/stdlib/unsafe/3-如何利用unsafe包修改私有成员.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 533 3 | title: "如何利用unsafe包修改私有成员" 4 | slug: /modify-private 5 | --- 6 | 7 | 对于一个结构体,通过 offset 函数可以获取结构体成员的偏移量,进而获取成员的地址,读写该地址的内存,就可以达到改变成员值的目的。 8 | 9 | 这里有一个内存分配相关的事实:结构体会被分配一块连续的内存,结构体的地址也代表了第一个成员的地址。 10 | 11 | 我们来看一个例子: 12 | 13 | ```golang 14 | package main 15 | 16 | import ( 17 | "fmt" 18 | "unsafe" 19 | ) 20 | 21 | type Programmer struct { 22 | name string 23 | language string 24 | } 25 | 26 | func main() { 27 | p := Programmer{"stefno", "go"} 28 | fmt.Println(p) 29 | 30 | name := (*string)(unsafe.Pointer(&p)) 31 | *name = "qcrao" 32 | 33 | lang := (*string)(unsafe.Pointer(uintptr(unsafe.Pointer(&p)) + unsafe.Offsetof(p.language))) 34 | *lang = "Golang" 35 | 36 | fmt.Println(p) 37 | } 38 | ``` 39 | 40 | 运行代码,输出: 41 | 42 | ```shell 43 | {stefno go} 44 | {qcrao Golang} 45 | ``` 46 | 47 | name 是结构体的第一个成员,因此可以直接将 &p 解析成 *string。这一点,在前面获取 map 的 count 成员时,用的是同样的原理。 48 | 49 | 对于结构体的私有成员,现在有办法可以通过 unsafe.Pointer 改变它的值了。 50 | 51 | 我把 Programmer 结构体升级,多加一个字段: 52 | 53 | ```golang 54 | type Programmer struct { 55 | name string 56 | age int 57 | language string 58 | } 59 | ``` 60 | 61 | 并且放在其他包,这样在 main 函数中,它的三个字段都是私有成员变量,不能直接修改。但我通过 unsafe.Sizeof() 函数可以获取成员大小,进而计算出成员的地址,直接修改内存。 62 | 63 | ```golang 64 | func main() { 65 | p := Programmer{"stefno", 18, "go"} 66 | fmt.Println(p) 67 | 68 | lang := (*string)(unsafe.Pointer(uintptr(unsafe.Pointer(&p)) + unsafe.Sizeof(int(0)) + unsafe.Sizeof(string("")))) 69 | *lang = "Golang" 70 | 71 | fmt.Println(p) 72 | } 73 | ``` 74 | 75 | 输出: 76 | 77 | ```shell 78 | {stefno 18 go} 79 | {stefno 18 Golang} 80 | ``` -------------------------------------------------------------------------------- /content/stdlib/unsafe/4-如何实现字符串和byte切片的零拷贝转换.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 534 3 | title: "如何实现字符串和byte切片的零拷贝转换" 4 | slug: /zero-conv 5 | --- 6 | 7 | 这是一个非常精典的例子。实现字符串和 bytes 切片之间的转换,要求是 `zero-copy`。想一下,一般的做法,都需要遍历字符串或 bytes 切片,再挨个赋值。 8 | 9 | 完成这个任务,我们需要了解 slice 和 string 的底层数据结构: 10 | 11 | ```golang 12 | type StringHeader struct { 13 | Data uintptr 14 | Len int 15 | } 16 | 17 | type SliceHeader struct { 18 | Data uintptr 19 | Len int 20 | Cap int 21 | } 22 | ``` 23 | 24 | 上面是反射包下的结构体,路径:src/reflect/value.go。只需要共享底层 Data 和 Len 就可以实现 `zero-copy`。 25 | 26 | ```golang 27 | func string2bytes(s string) []byte { 28 | return *(*[]byte)(unsafe.Pointer(&s)) 29 | } 30 | func bytes2string(b []byte) string{ 31 | return *(*string)(unsafe.Pointer(&b)) 32 | } 33 | ``` 34 | 35 | 原理上是利用指针的强转,代码比较简单,不作详细解释。 -------------------------------------------------------------------------------- /content/stdlib/unsafe/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 520 3 | bookFlatSection: true 4 | bookCollapseSection: true 5 | title: "unsafe" 6 | slug: / 7 | --- -------------------------------------------------------------------------------- /content/stdlib/unsafe/assets/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/content/stdlib/unsafe/assets/0.png -------------------------------------------------------------------------------- /scripts/downloader.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The golang.design Initiative Authors. 2 | // All rights reserved. Use of this source code is governed 3 | // by a GNU GPLv3 license that can be found in the LICENSE file. 4 | 5 | // Quick solution, use it at your own risk 6 | // written by Changkun Ou 7 | // 8 | // This scripts tries to extract all image urls from github, download 9 | // and save them to the ./assets folder, and then replaces all links 10 | // from original github user content url to ../assets. 11 | package main 12 | 13 | import ( 14 | "fmt" 15 | "io" 16 | "io/fs" 17 | "net/http" 18 | "os" 19 | "path/filepath" 20 | "regexp" 21 | "strings" 22 | ) 23 | 24 | func main() { 25 | re := regexp.MustCompile("https://user-images(.*)\\)") 26 | os.Mkdir("assets", os.ModePerm) // dont care error 27 | 28 | index := 0 29 | filepath.Walk(".", func(path string, info fs.FileInfo, err error) error { 30 | if !strings.Contains(info.Name(), ".md") { 31 | return nil 32 | } 33 | 34 | for { 35 | f, err := os.Open(info.Name()) 36 | if err != nil { 37 | panic(err) 38 | } 39 | b, err := io.ReadAll(f) 40 | if err != nil { 41 | panic(err) 42 | } 43 | f.Close() 44 | content := string(b) 45 | links := re.FindAllStringIndex(string(b), -1) 46 | if len(links) == 0 { 47 | break 48 | } 49 | 50 | posStart := links[0][0] 51 | posEnd := links[0][1] - 1 52 | src := content[posStart:posEnd] 53 | dst := fmt.Sprintf("assets/%d.png", index) 54 | 55 | // 1. download image 56 | resp, err := http.Get(src) 57 | if err != nil { 58 | panic(err) 59 | } 60 | defer resp.Body.Close() 61 | 62 | // 2. save in assets 63 | imgf, err := os.Create(dst) 64 | if err != nil { 65 | panic(err) 66 | } 67 | _, err = io.Copy(imgf, resp.Body) 68 | if err != nil { 69 | panic(err) 70 | } 71 | imgf.Close() 72 | 73 | // 3. replace links in doc 74 | content = content[:posStart] + "../" + dst + content[posEnd:] 75 | err = os.WriteFile(info.Name(), []byte(content), os.ModePerm) 76 | if err != nil { 77 | panic(err) 78 | } 79 | 80 | // 4. save the doc 81 | fmt.Println(src, dst) 82 | index++ 83 | } 84 | return nil 85 | }) 86 | } 87 | -------------------------------------------------------------------------------- /themes/book/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Alex Shpak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /themes/book/archetypes/docs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "{{ .Name | humanize | title }}" 3 | weight: 1 4 | # bookFlatSection: false 5 | # bookToc: true 6 | # bookHidden: false 7 | # bookCollapseSection: false 8 | # bookComments: true 9 | --- 10 | -------------------------------------------------------------------------------- /themes/book/archetypes/posts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "{{ replace .Name "-" " " | title }}" 3 | date: {{ .Date }} 4 | --- 5 | -------------------------------------------------------------------------------- /themes/book/assets/_custom.scss: -------------------------------------------------------------------------------- 1 | /* You can add custom styles here. */ 2 | 3 | // @import "plugins/numbered"; 4 | -------------------------------------------------------------------------------- /themes/book/assets/_defaults.scss: -------------------------------------------------------------------------------- 1 | // Used in layout 2 | $padding-1: 1px !default; 3 | $padding-4: 0.25rem !default; 4 | $padding-8: 0.5rem !default; 5 | $padding-16: 1rem !default; 6 | 7 | $font-size-base: 16px !default; 8 | $font-size-12: 0.75rem !default; 9 | $font-size-14: 0.875rem !default; 10 | $font-size-16: 1rem !default; 11 | 12 | $border-radius: $padding-4 !default; 13 | 14 | $body-font-weight: normal !default; 15 | 16 | $body-min-width: 20rem !default; 17 | $container-max-width: 80rem !default; 18 | 19 | $header-height: 3.5rem !default; 20 | $menu-width: 18rem !default; 21 | $toc-width: 16rem !default; 22 | 23 | $mobile-breakpoint: $menu-width + $body-min-width * 1.2 + $toc-width !default; 24 | 25 | $hint-colors: ( 26 | info: #6bf, 27 | warning: #fd6, 28 | danger: #f66, 29 | ) !default; 30 | 31 | // Themes 32 | @mixin theme-light { 33 | --gray-100: #f8f9fa; 34 | --gray-200: #e9ecef; 35 | --gray-500: #adb5bd; 36 | 37 | --color-link: #0055bb; 38 | --color-visited-link: #8440f1; 39 | 40 | --body-background: white; 41 | --body-font-color: black; 42 | 43 | --icon-filter: none; 44 | 45 | --hint-color-info: #6bf; 46 | --hint-color-warning: #fd6; 47 | --hint-color-danger: #f66; 48 | } 49 | 50 | @mixin theme-dark { 51 | --gray-100: rgba(255, 255, 255, 0.1); 52 | --gray-200: rgba(255, 255, 255, 0.2); 53 | --gray-500: rgba(255, 255, 255, 0.5); 54 | 55 | --color-link: #84b2ff; 56 | --color-visited-link: #b88dff; 57 | 58 | --body-background: #343a40; 59 | --body-font-color: #e9ecef; 60 | 61 | --icon-filter: brightness(0) invert(1); 62 | 63 | --hint-color-info: #6bf; 64 | --hint-color-warning: #fd6; 65 | --hint-color-danger: #f66; 66 | } 67 | -------------------------------------------------------------------------------- /themes/book/assets/_fonts.scss: -------------------------------------------------------------------------------- 1 | /* roboto-300italic - latin */ 2 | @font-face { 3 | font-family: 'Roboto'; 4 | font-style: italic; 5 | font-weight: 300; 6 | font-display: swap; 7 | src: local('Roboto Light Italic'), local('Roboto-LightItalic'), 8 | url('fonts/roboto-v19-latin-300italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ 9 | url('fonts/roboto-v19-latin-300italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 10 | } 11 | /* roboto-regular - latin */ 12 | @font-face { 13 | font-family: 'Roboto'; 14 | font-style: normal; 15 | font-weight: 400; 16 | font-display: swap; 17 | src: local('Roboto'), local('Roboto-Regular'), 18 | url('fonts/roboto-v19-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ 19 | url('fonts/roboto-v19-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 20 | } 21 | /* roboto-700 - latin */ 22 | @font-face { 23 | font-family: 'Roboto'; 24 | font-style: normal; 25 | font-weight: 700; 26 | font-display: swap; 27 | src: local('Roboto Bold'), local('Roboto-Bold'), 28 | url('fonts/roboto-v19-latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ 29 | url('fonts/roboto-v19-latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 30 | } 31 | 32 | /* roboto-mono-regular - latin */ 33 | @font-face { 34 | font-family: 'Roboto Mono'; 35 | font-style: normal; 36 | font-weight: 400; 37 | font-display: swap; 38 | src: local('Roboto Mono'), local('RobotoMono-Regular'), 39 | url('fonts/roboto-mono-v6-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ 40 | url('fonts/roboto-mono-v6-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 41 | } 42 | 43 | body { 44 | font-family: 'Roboto', sans-serif; 45 | } 46 | 47 | code { 48 | font-family: 'Roboto Mono', monospace; 49 | } 50 | -------------------------------------------------------------------------------- /themes/book/assets/_markdown.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | .markdown { 4 | line-height: 1.6; 5 | 6 | // remove padding at the beginning of page 7 | > :first-child { 8 | margin-top: 0; 9 | } 10 | 11 | h1, 12 | h2, 13 | h3, 14 | h4, 15 | h5, 16 | h6 { 17 | font-weight: normal; 18 | line-height: 1; 19 | margin-top: 1.5em; 20 | margin-bottom: $padding-16; 21 | 22 | a.anchor { 23 | opacity: 0; 24 | font-size: 0.75em; 25 | vertical-align: middle; 26 | text-decoration: none; 27 | } 28 | 29 | &:hover a.anchor, 30 | a.anchor:focus { 31 | opacity: initial; 32 | } 33 | } 34 | 35 | h4, 36 | h5, 37 | h6 { 38 | font-weight: bolder; 39 | } 40 | 41 | h5 { 42 | font-size: 0.875em; 43 | } 44 | 45 | h6 { 46 | font-size: 0.75em; 47 | } 48 | 49 | b, 50 | optgroup, 51 | strong { 52 | font-weight: bolder; 53 | } 54 | 55 | a { 56 | text-decoration: none; 57 | 58 | &:hover { 59 | text-decoration: underline; 60 | } 61 | &:visited { 62 | color: var(--color-visited-link); 63 | } 64 | } 65 | 66 | img { 67 | max-width: 100%; 68 | } 69 | 70 | code { 71 | padding: 0 $padding-4; 72 | background: var(--gray-200); 73 | border-radius: $border-radius; 74 | font-size: 0.875em; 75 | } 76 | 77 | pre { 78 | padding: $padding-16; 79 | background: var(--gray-100); 80 | border-radius: $border-radius; 81 | overflow-x: auto; 82 | 83 | code { 84 | padding: 0; 85 | background: none; 86 | } 87 | } 88 | 89 | blockquote { 90 | margin: $padding-16 0; 91 | padding: $padding-8 $padding-16 $padding-8 ($padding-16 - $padding-4); //to keep total left space 16dp 92 | 93 | border-inline-start: $padding-4 solid var(--gray-200); 94 | border-radius: $border-radius; 95 | 96 | :first-child { 97 | margin-top: 0; 98 | } 99 | :last-child { 100 | margin-bottom: 0; 101 | } 102 | } 103 | 104 | table { 105 | overflow: auto; 106 | display: block; 107 | border-spacing: 0; 108 | border-collapse: collapse; 109 | margin-top: $padding-16; 110 | margin-bottom: $padding-16; 111 | 112 | tr th, 113 | tr td { 114 | padding: $padding-8 $padding-16; 115 | border: $padding-1 solid var(--gray-200); 116 | } 117 | 118 | tr:nth-child(2n) { 119 | background: var(--gray-100); 120 | } 121 | } 122 | 123 | hr { 124 | height: $padding-1; 125 | border: none; 126 | background: var(--gray-200); 127 | } 128 | 129 | ul, 130 | ol { 131 | padding-inline-start: $padding-16 * 2; 132 | } 133 | 134 | dl { 135 | dt { 136 | font-weight: bolder; 137 | margin-top: $padding-16; 138 | } 139 | 140 | dd { 141 | margin-inline-start: $padding-16; 142 | margin-bottom: $padding-16; 143 | } 144 | } 145 | 146 | // Special case for highlighted code with line numbers 147 | .highlight table tr { 148 | td:nth-child(1) pre { 149 | margin: 0; 150 | padding-inline-end: 0; 151 | } 152 | td:nth-child(2) pre { 153 | margin: 0; 154 | padding-inline-start: 0; 155 | } 156 | } 157 | 158 | details { 159 | padding: $padding-16; 160 | border: $padding-1 solid var(--gray-200); 161 | border-radius: $border-radius; 162 | 163 | summary { 164 | line-height: 1; 165 | padding: $padding-16; 166 | margin: -$padding-16; 167 | cursor: pointer; 168 | } 169 | 170 | &[open] summary { 171 | margin-bottom: 0; 172 | } 173 | } 174 | 175 | figure { 176 | margin: $padding-16 0; 177 | figcaption p { 178 | margin-top: 0; 179 | } 180 | } 181 | } 182 | 183 | .markdown-inner { 184 | // Util class to remove extra margin in nested markdown content 185 | > :first-child { 186 | margin-top: 0; 187 | } 188 | > :last-child { 189 | margin-bottom: 0; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /themes/book/assets/_print.scss: -------------------------------------------------------------------------------- 1 | @media print { 2 | .book-menu, 3 | .book-footer, 4 | .book-toc { 5 | display: none; 6 | } 7 | 8 | .book-header, 9 | .book-header aside { 10 | display: block; 11 | } 12 | 13 | main { 14 | // Fix for https://bugzilla.mozilla.org/show_bug.cgi?id=939897 15 | display: block !important; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /themes/book/assets/_shortcodes.scss: -------------------------------------------------------------------------------- 1 | .markdown { 2 | // {{< expand "Label" "icon" >}} 3 | .book-expand { 4 | margin-top: $padding-16; 5 | margin-bottom: $padding-16; 6 | 7 | border: $padding-1 solid var(--gray-200); 8 | border-radius: $border-radius; 9 | 10 | overflow: hidden; 11 | 12 | .book-expand-head { 13 | background: var(--gray-100); 14 | padding: $padding-8 $padding-16; 15 | cursor: pointer; 16 | } 17 | 18 | .book-expand-content { 19 | display: none; 20 | padding: $padding-16; 21 | } 22 | 23 | input[type="checkbox"]:checked + .book-expand-content { 24 | display: block; 25 | } 26 | } 27 | 28 | // {{< tabs >}} 29 | .book-tabs { 30 | margin-top: $padding-16; 31 | margin-bottom: $padding-16; 32 | 33 | border: $padding-1 solid var(--gray-200); 34 | border-radius: $border-radius; 35 | 36 | overflow: hidden; 37 | 38 | display: flex; 39 | flex-wrap: wrap; 40 | 41 | label { 42 | display: inline-block; 43 | padding: $padding-8 $padding-16; 44 | border-bottom: $padding-1 transparent; 45 | cursor: pointer; 46 | } 47 | 48 | .book-tabs-content { 49 | order: 999; //Move content blocks to the end 50 | width: 100%; 51 | border-top: $padding-1 solid var(--gray-100); 52 | padding: $padding-16; 53 | display: none; 54 | } 55 | 56 | input[type="radio"]:checked + label { 57 | border-bottom: $padding-1 solid var(--color-link); 58 | } 59 | input[type="radio"]:checked + label + .book-tabs-content { 60 | display: block; 61 | } 62 | input[type="radio"]:focus + label { 63 | @include outline; 64 | } 65 | } 66 | 67 | // {{< columns >}} 68 | .book-columns { 69 | margin-left: -$padding-16; 70 | margin-right: -$padding-16; 71 | 72 | > div { 73 | margin: $padding-16 0; 74 | min-width: $body-min-width / 2; 75 | padding: 0 $padding-16; 76 | } 77 | } 78 | 79 | // {{< button >}} 80 | a.book-btn { 81 | display: inline-block; 82 | font-size: $font-size-14; 83 | color: var(--color-link); 84 | line-height: $padding-16 * 2; 85 | padding: 0 $padding-16; 86 | border: $padding-1 solid var(--color-link); 87 | border-radius: $border-radius; 88 | cursor: pointer; 89 | 90 | &:hover { 91 | text-decoration: none; 92 | } 93 | } 94 | 95 | // {{< hint >}} 96 | .book-hint { 97 | @each $name, $color in $hint-colors { 98 | &.#{$name} { 99 | border-color: $color; 100 | background-color: rgba($color, 0.1); 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /themes/book/assets/_utils.scss: -------------------------------------------------------------------------------- 1 | .flex { 2 | display: flex; 3 | } 4 | 5 | .flex-auto { 6 | flex: 1 1 auto; 7 | } 8 | 9 | .flex-even { 10 | flex: 1 1; 11 | } 12 | 13 | .flex-wrap { 14 | flex-wrap: wrap; 15 | } 16 | 17 | .justify-start { 18 | justify-content: flex-start; 19 | } 20 | 21 | .justify-end { 22 | justify-content: flex-end; 23 | } 24 | 25 | .justify-center { 26 | justify-content: center; 27 | } 28 | 29 | .justify-between { 30 | justify-content: space-between; 31 | } 32 | 33 | .align-center { 34 | align-items: center; 35 | } 36 | 37 | .mx-auto { 38 | margin: 0 auto; 39 | } 40 | 41 | .text-center { 42 | text-align: center; 43 | } 44 | 45 | .text-left { 46 | text-align: left; 47 | } 48 | 49 | .text-right { 50 | text-align: right; 51 | } 52 | 53 | .hidden { 54 | display: none; 55 | } 56 | 57 | input.toggle { 58 | height: 0; 59 | width: 0; 60 | overflow: hidden; 61 | opacity: 0; 62 | position: absolute; 63 | } 64 | 65 | .clearfix::after { 66 | content: ""; 67 | display: table; 68 | clear: both; 69 | } 70 | 71 | @mixin spin($duration) { 72 | animation: spin $duration ease infinite; 73 | @keyframes spin { 74 | 100% { 75 | transform: rotate(360deg); 76 | } 77 | } 78 | } 79 | 80 | @mixin fixed { 81 | position: fixed; 82 | top: 0; 83 | bottom: 0; 84 | overflow-x: hidden; 85 | overflow-y: auto; 86 | } 87 | 88 | @mixin outline { 89 | outline-style: auto; 90 | outline-color: currentColor; 91 | outline-color: -webkit-focus-ring-color; 92 | } 93 | -------------------------------------------------------------------------------- /themes/book/assets/_variables.scss: -------------------------------------------------------------------------------- 1 | /* You can override SASS variables here. */ 2 | 3 | // @import "plugins/dark"; 4 | -------------------------------------------------------------------------------- /themes/book/assets/book.scss: -------------------------------------------------------------------------------- 1 | @import "defaults"; 2 | @import "variables"; 3 | @import "themes/{{ default "light" .Site.Params.BookTheme }}"; 4 | 5 | @import "normalize"; 6 | @import "utils"; 7 | @import "main"; 8 | @import "fonts"; 9 | @import "print"; 10 | 11 | @import "markdown"; 12 | @import "shortcodes"; 13 | 14 | // Custom defined styles 15 | @import "custom"; 16 | -------------------------------------------------------------------------------- /themes/book/assets/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{ .Site.Title }}", 3 | "short_name": "{{ .Site.Title }}", 4 | "start_url": "{{ "/" | relURL }}", 5 | "scope": "{{ "/" | relURL }}", 6 | "display": "standalone", 7 | "background_color": "#000000", 8 | "theme_color": "#000000", 9 | "icons": [ 10 | { 11 | "src": "{{ "/favicon.svg" | relURL }}", 12 | "sizes": "512x512" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /themes/book/assets/menu-reset.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var menu = document.querySelector("aside.book-menu nav"); 3 | addEventListener("beforeunload", function(event) { 4 | localStorage.setItem("menu.scrollTop", menu.scrollTop); 5 | }); 6 | menu.scrollTop = localStorage.getItem("menu.scrollTop"); 7 | })(); 8 | -------------------------------------------------------------------------------- /themes/book/assets/mermaid.json: -------------------------------------------------------------------------------- 1 | { 2 | "flowchart": { 3 | "useMaxWidth":true 4 | }, 5 | "theme": "default" 6 | } 7 | -------------------------------------------------------------------------------- /themes/book/assets/plugins/_numbered.scss: -------------------------------------------------------------------------------- 1 | $startLevel: 1; 2 | $endLevel: 6; 3 | 4 | .book-page .markdown { 5 | @for $currentLevel from $startLevel through $endLevel { 6 | > h#{$currentLevel} { 7 | counter-increment: h#{$currentLevel}; 8 | counter-reset: h#{$currentLevel + 1}; 9 | 10 | $content: ""; 11 | @for $n from $startLevel through $currentLevel { 12 | $content: $content + 'counter(h#{$n})"."'; 13 | } 14 | 15 | &::before { 16 | content: unquote($content) " "; 17 | } 18 | } 19 | } 20 | } 21 | 22 | .book-toc nav ul { 23 | li { 24 | counter-increment: item; 25 | 26 | &:first-child { 27 | counter-reset: item; 28 | } 29 | 30 | &:before { 31 | content: counters(item, ".") ". "; 32 | float: left; 33 | margin-inline-end: $padding-4; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /themes/book/assets/plugins/_scrollbars.scss: -------------------------------------------------------------------------------- 1 | @import "defaults"; 2 | @import "variables"; 3 | 4 | // Webkit 5 | ::-webkit-scrollbar { 6 | width: $padding-8; 7 | } 8 | 9 | ::-webkit-scrollbar-thumb { 10 | background: transparent; 11 | border-radius: $padding-8; 12 | } 13 | 14 | :hover::-webkit-scrollbar-thumb { 15 | background: var(--gray-500); 16 | } 17 | 18 | // MS 19 | body { 20 | -ms-overflow-style: -ms-autohiding-scrollbar; 21 | } 22 | 23 | // Future 24 | .book-menu nav { 25 | scrollbar-color: transparent var(--gray-500); 26 | } 27 | -------------------------------------------------------------------------------- /themes/book/assets/search-data.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function () { 4 | const indexCfg = {{ with i18n "bookSearchConfig" }} 5 | {{ . }}; 6 | {{ else }} 7 | {}; 8 | {{ end }} 9 | 10 | indexCfg.doc = { 11 | id: 'id', 12 | field: ['title', 'content'], 13 | store: ['title', 'href', 'section'], 14 | }; 15 | 16 | const index = FlexSearch.create('balance', indexCfg); 17 | window.bookSearchIndex = index; 18 | 19 | {{- $pages := where .Site.Pages "Kind" "in" (slice "page" "section") -}} 20 | {{- $pages = where $pages "Params.booksearchexclude" "!=" true -}} 21 | {{- $pages = where $pages "Content" "not in" (slice nil "") -}} 22 | 23 | {{ range $index, $page := $pages }} 24 | index.add({ 25 | 'id': {{ $index }}, 26 | 'href': '{{ $page.RelPermalink }}', 27 | 'title': {{ (partial "docs/title" $page) | jsonify }}, 28 | 'section': {{ (partial "docs/title" $page.Parent) | jsonify }}, 29 | 'content': {{ $page.Plain | jsonify }} 30 | }); 31 | {{- end -}} 32 | })(); 33 | -------------------------------------------------------------------------------- /themes/book/assets/search.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | {{ $searchDataFile := printf "%s.search-data.js" .Language.Lang }} 4 | {{ $searchData := resources.Get "search-data.js" | resources.ExecuteAsTemplate $searchDataFile . | resources.Minify | resources.Fingerprint }} 5 | 6 | (function () { 7 | const input = document.querySelector('#book-search-input'); 8 | const results = document.querySelector('#book-search-results'); 9 | 10 | if (!input) { 11 | return 12 | } 13 | 14 | input.addEventListener('focus', init); 15 | input.addEventListener('keyup', search); 16 | 17 | document.addEventListener('keypress', focusSearchFieldOnKeyPress); 18 | 19 | /** 20 | * @param {Event} event 21 | */ 22 | function focusSearchFieldOnKeyPress(event) { 23 | if (input === document.activeElement) { 24 | return; 25 | } 26 | 27 | const characterPressed = String.fromCharCode(event.charCode); 28 | if (!isHotkey(characterPressed)) { 29 | return; 30 | } 31 | 32 | input.focus(); 33 | event.preventDefault(); 34 | } 35 | 36 | /** 37 | * @param {String} character 38 | * @returns {Boolean} 39 | */ 40 | function isHotkey(character) { 41 | const dataHotkeys = input.getAttribute('data-hotkeys') || ''; 42 | return dataHotkeys.indexOf(character) >= 0; 43 | } 44 | 45 | function init() { 46 | input.removeEventListener('focus', init); // init once 47 | input.required = true; 48 | 49 | loadScript('{{ "flexsearch.min.js" | relURL }}'); 50 | loadScript('{{ $searchData.RelPermalink }}', function () { 51 | input.required = false; 52 | search(); 53 | }); 54 | } 55 | 56 | function search() { 57 | while (results.firstChild) { 58 | results.removeChild(results.firstChild); 59 | } 60 | 61 | if (!input.value) { 62 | return; 63 | } 64 | 65 | const searchHits = window.bookSearchIndex.search(input.value, 10); 66 | searchHits.forEach(function (page) { 67 | const li = element('
  • '); 68 | const a = li.querySelector('a'), small = li.querySelector('small'); 69 | 70 | a.href = page.href; 71 | a.textContent = page.title; 72 | small.textContent = page.section; 73 | 74 | results.appendChild(li); 75 | }); 76 | } 77 | 78 | /** 79 | * @param {String} src 80 | * @param {Function} callback 81 | */ 82 | function loadScript(src, callback) { 83 | const script = document.createElement('script'); 84 | script.defer = true; 85 | script.async = false; 86 | script.src = src; 87 | script.onload = callback; 88 | 89 | document.head.appendChild(script); 90 | } 91 | 92 | /** 93 | * @param {String} content 94 | * @returns {Node} 95 | */ 96 | function element(content) { 97 | const div = document.createElement('div'); 98 | div.innerHTML = content; 99 | return div.firstChild; 100 | } 101 | })(); 102 | -------------------------------------------------------------------------------- /themes/book/assets/sw-register.js: -------------------------------------------------------------------------------- 1 | {{- $swJS := resources.Get "sw.js" | resources.ExecuteAsTemplate "sw.js" . -}} 2 | if (navigator.serviceWorker) { 3 | navigator.serviceWorker.register( 4 | "{{ $swJS.RelPermalink }}", 5 | { scope: "{{ "/" | relURL }}" } 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /themes/book/assets/sw.js: -------------------------------------------------------------------------------- 1 | const cacheName = self.location.pathname 2 | const pages = [ 3 | {{ if eq .Site.Params.BookServiceWorker "precache" }} 4 | {{ range .Site.AllPages -}} 5 | "{{ .RelPermalink }}", 6 | {{ end -}} 7 | {{ end }} 8 | ]; 9 | 10 | self.addEventListener("install", function (event) { 11 | self.skipWaiting(); 12 | 13 | caches.open(cacheName).then((cache) => { 14 | return cache.addAll(pages); 15 | }); 16 | }); 17 | 18 | self.addEventListener("fetch", (event) => { 19 | const request = event.request; 20 | if (request.method !== "GET") { 21 | return; 22 | } 23 | 24 | /** 25 | * @param {Response} response 26 | * @returns {Promise} 27 | */ 28 | function saveToCache(response) { 29 | if (cacheable(response)) { 30 | return caches 31 | .open(cacheName) 32 | .then((cache) => cache.put(request, response.clone())) 33 | .then(() => response); 34 | } else { 35 | return response; 36 | } 37 | } 38 | 39 | /** 40 | * @param {Error} error 41 | */ 42 | function serveFromCache(error) { 43 | return caches.open(cacheName).then((cache) => cache.match(request.url)); 44 | } 45 | 46 | /** 47 | * @param {Response} response 48 | * @returns {Boolean} 49 | */ 50 | function cacheable(response) { 51 | return response.type === "basic" && response.ok && !response.headers.has("Content-Disposition") 52 | } 53 | 54 | event.respondWith(fetch(request).then(saveToCache).catch(serveFromCache)); 55 | }); 56 | -------------------------------------------------------------------------------- /themes/book/assets/themes/_auto.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | @include theme-light; 3 | } 4 | 5 | @media (prefers-color-scheme: dark) { 6 | :root { 7 | @include theme-dark; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /themes/book/assets/themes/_dark.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | @include theme-dark; 3 | } 4 | -------------------------------------------------------------------------------- /themes/book/assets/themes/_light.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | @include theme-light; 3 | } 4 | -------------------------------------------------------------------------------- /themes/book/i18n/zh.yaml: -------------------------------------------------------------------------------- 1 | - id: Search 2 | translation: 搜索 3 | 4 | - id: Edit this page 5 | translation: 编辑本页 6 | 7 | - id: Last modified by 8 | translation: 最后修改者 9 | 10 | - id: Expand 11 | translation: 展开 12 | 13 | - id: bookSearchConfig 14 | translation: | 15 | { 16 | encode: false, 17 | tokenize: function(str) { 18 | return str.replace(/[\x00-\x7F]/g, '').split(''); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /themes/book/layouts/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ partial "docs/html-head" . }} 6 | {{ partial "docs/inject/head" . }} 7 | 8 | 18 | 19 | 20 | 21 |
    22 |
    23 |

    404

    24 |

    Page Not Found

    25 |

    26 | {{ .Site.Title }} 27 |

    28 |
    29 |
    30 | 31 | {{ partial "docs/inject/body" . }} 32 | {{ template "_internal/google_analytics_async.html" . }} 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /themes/book/layouts/_default/_markup/render-heading.html: -------------------------------------------------------------------------------- 1 | 2 | {{ .Text | safeHTML }} 3 | # 4 | 5 | -------------------------------------------------------------------------------- /themes/book/layouts/_default/_markup/render-image.html: -------------------------------------------------------------------------------- 1 | {{- if .Page.Site.Params.BookPortableLinks -}} 2 | {{- template "portable-image" . -}} 3 | {{- else -}} 4 | {{ .Text }} 5 | {{- end -}} 6 | 7 | {{- define "portable-image" -}} 8 | {{- $isRemote := or (in .Destination "://") (strings.HasPrefix .Destination "//") }} 9 | {{- if not $isRemote }} 10 | {{- $path := print .Page.File.Dir .Destination }} 11 | {{- if strings.HasPrefix .Destination "/" }} 12 | {{- $path = print "/static" .Destination }} 13 | {{- end }} 14 | {{- if not (fileExists $path) }} 15 | {{- warnf "Image '%s' not found in '%s'" .Destination .Page.File }} 16 | {{- end }} 17 | {{- end }} 18 | {{ .Text }} 19 | {{- end -}} 20 | -------------------------------------------------------------------------------- /themes/book/layouts/_default/_markup/render-link.html: -------------------------------------------------------------------------------- 1 | {{- if .Page.Site.Params.BookPortableLinks -}} 2 | {{- template "portable-link" . -}} 3 | {{- else -}} 4 | {{ .Text | safeHTML }} 5 | {{- end -}} 6 | 7 | {{- define "portable-link" -}} 8 | {{- $destination := .Destination }} 9 | {{- $isRemote := or (in .Destination ":") (strings.HasPrefix .Destination "//") }} 10 | {{- if not $isRemote }} 11 | {{- $url := urls.Parse .Destination }} 12 | {{- $path := strings.TrimSuffix "/_index.md" $url.Path }} 13 | {{- $path = strings.TrimSuffix "/_index" $path }} 14 | {{- $path = strings.TrimSuffix ".md" $path }} 15 | {{- $page := .Page.GetPage $path }} 16 | {{- if $page }} 17 | {{- $destination = $page.RelPermalink }} 18 | {{- if $url.Fragment }} 19 | {{- $destination = print $destination "#" $url.Fragment }} 20 | {{- end }} 21 | {{- else if fileExists (print .Page.File.Dir .Destination) }} 22 | 23 | {{- else -}} 24 | {{- warnf "Page '%s' not found in '%s'" .Destination .Page.File }} 25 | {{- end }} 26 | {{- end }} 27 | {{ .Text | safeHTML }} 28 | {{- end -}} 29 | -------------------------------------------------------------------------------- /themes/book/layouts/_default/baseof.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ hugo.Generator }} 6 | {{ partial "docs/html-head" . }} 7 | {{ partial "docs/inject/head" . }} 8 | 9 | 10 | 11 | 12 | 13 |
    14 | 19 | 20 |
    21 |
    22 | {{ template "header" . }} 23 |
    24 | 25 | {{ partial "docs/inject/content-before" . }} 26 | {{ template "main" . }} 27 | {{ partial "docs/inject/content-after" . }} 28 | 29 |
    30 | {{ template "footer" . }} 31 | {{ partial "docs/inject/footer" . }} 32 |
    33 | 34 | {{ template "comments" . }} 35 | 36 | 37 |
    38 | 39 | {{ if default true (default .Site.Params.BookToC .Params.BookToC) }} 40 | 45 | {{ end }} 46 |
    47 | 48 | {{ partial "docs/inject/body" . }} 49 | 50 | 51 | 52 | 53 | {{ define "menu" }} 54 | {{ partial "docs/menu" . }} 55 | {{ end }} 56 | 57 | {{ define "header" }} 58 | {{ partial "docs/header" . }} 59 | 60 | {{ if default true (default .Site.Params.BookToC .Params.BookToC) }} 61 | 64 | {{ end }} 65 | {{ end }} 66 | 67 | {{ define "footer" }} 68 | {{ partial "docs/footer" . }} 69 | {{ end }} 70 | 71 | {{ define "comments" }} 72 | {{ if and .Content (default true (default .Site.Params.BookComments .Params.BookComments)) }} 73 |
    74 | {{- partial "docs/comments" . -}} 75 |
    76 | {{ end }} 77 | {{ end }} 78 | 79 | {{ define "main" }} 80 |
    81 | {{- .Content -}} 82 | {{ $path := .Permalink | relURL }} 83 | {{ if eq $path "/go-questions/errata/" }} 84 | --- 85 |
    86 | 最后更新:{{ now.Format "2006.01.02" }} 87 | {{ end }} 88 |
    89 | {{ end }} 90 | 91 | {{ define "toc" }} 92 | {{ partial "docs/toc" . }} 93 | {{ end }} 94 | -------------------------------------------------------------------------------- /themes/book/layouts/_default/list.html: -------------------------------------------------------------------------------- 1 | {{ define "dummy" }}{{ end }} 2 | -------------------------------------------------------------------------------- /themes/book/layouts/_default/single.html: -------------------------------------------------------------------------------- 1 | {{ define "dummy" }}{{ end }} 2 | -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/brand.html: -------------------------------------------------------------------------------- 1 |

    2 | 3 | {{- with .Site.Params.BookLogo -}} 4 | Logo 5 | {{- end -}} 6 | {{ .Site.Title }} 7 | 8 |

    9 | -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/comments.html: -------------------------------------------------------------------------------- 1 | 2 | {{ template "_internal/disqus.html" . }} 3 | -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/date.html: -------------------------------------------------------------------------------- 1 | 5 | {{- $format := default "January 2, 2006" .Format -}} 6 | {{- return (.Date.Format $format) -}} 7 | -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/footer.html: -------------------------------------------------------------------------------- 1 |
    2 | {{ if .Site.IsMultiLingual }} 3 | {{ partial "docs/languages" . }} 4 | {{ end }} 5 | 6 | {{ if and .GitInfo .Site.Params.BookRepo }} 7 |
    8 | {{- $date := partial "docs/date" (dict "Date" .GitInfo.AuthorDate.Local "Format" .Site.Params.BookDateFormat) -}} 9 | 10 | Calendar 11 | {{ $date }} 12 | 13 |
    14 | {{ end }} 15 | 16 | {{ if and .File .Site.Params.BookRepo .Site.Params.BookEditPath }} 17 | 23 | {{ end }} 24 |
    25 | -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/header.html: -------------------------------------------------------------------------------- 1 |
    2 | 5 | 6 | {{ partial "docs/title" . }} 7 | 8 | 13 |
    14 | -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/html-head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{- template "_internal/opengraph.html" . -}} 7 | 8 | {{ partial "docs/title" . }} | {{ .Site.Title -}} 9 | 10 | {{- $manifest := resources.Get "manifest.json" | resources.ExecuteAsTemplate "manifest.json" . }} 11 | 12 | 13 | 14 | {{- range .Translations }} 15 | 16 | {{ end -}} 17 | 18 | 19 | {{- $styles := resources.Get "book.scss" | resources.ExecuteAsTemplate "book.scss" . | resources.ToCSS | resources.Minify | resources.Fingerprint }} 20 | 21 | 22 | {{- if default true .Site.Params.BookSearch }} 23 | {{- $searchJSFile := printf "%s.search.js" .Language.Lang }} 24 | {{- $searchJS := resources.Get "search.js" | resources.ExecuteAsTemplate $searchJSFile . | resources.Minify | resources.Fingerprint }} 25 | 26 | {{ end -}} 27 | 28 | {{- if .Site.Params.BookServiceWorker }} 29 | {{- $swJS := resources.Get "sw-register.js" | resources.ExecuteAsTemplate "sw.js" . | resources.Minify | resources.Fingerprint }} 30 | 31 | {{ end -}} 32 | 33 | {{- template "_internal/google_analytics_async.html" . -}} 34 | 35 | 36 | {{- with .OutputFormats.Get "rss" -}} 37 | {{ printf `` .Rel .MediaType.Type .Permalink $.Site.Title | safeHTML }} 38 | {{ end -}} 39 | 40 | {{ "" | safeHTML }} 44 | 45 | 46 | -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/inject/body.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/themes/book/layouts/partials/docs/inject/body.html -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/inject/content-after.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/themes/book/layouts/partials/docs/inject/content-after.html -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/inject/content-before.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/themes/book/layouts/partials/docs/inject/content-before.html -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/inject/footer.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | 16 | 17 | 18 | 19 | 25 | 26 | 页面总访问量: , 访问的用户数: 27 | -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/inject/head.html: -------------------------------------------------------------------------------- 1 | 29 | -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/inject/menu-after.html: -------------------------------------------------------------------------------- 1 |
    2 |
      3 |
    • 4 |
    • 5 |
    6 | 版权所有 © 2022 饶全成, 欧长坤, 楚秦等

    7 | 8 | The golang.design Initialtive -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/inject/menu-before.html: -------------------------------------------------------------------------------- 1 | 4 |
    -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/inject/toc-after.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/themes/book/layouts/partials/docs/inject/toc-after.html -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/inject/toc-before.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/themes/book/layouts/partials/docs/inject/toc-before.html -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/languages.html: -------------------------------------------------------------------------------- 1 | 2 | {{ $bookTranslatedOnly := default false .Site.Params.BookTranslatedOnly }} 3 | {{ $translations := dict }} 4 | {{ if (eq $bookTranslatedOnly false ) }} 5 | {{ range .Site.Home.AllTranslations }} 6 | {{ $translations = merge $translations (dict .Language.Lang .) }} 7 | {{ end }} 8 | {{ end }} 9 | {{ range .Translations }} 10 | {{ $translations = merge $translations (dict .Language.Lang .) }} 11 | {{ end }} 12 | 13 |
    14 |
      15 |
    • 16 | Languages 17 | {{ $.Site.Language.LanguageName }} 18 |
    • 19 |
    20 | 21 | 31 |
    32 | -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/menu-bundle.html: -------------------------------------------------------------------------------- 1 | {{ with .Site.GetPage .Site.Params.BookMenuBundle }} 2 | {{- $href := printf "href=\"%s\"" $.RelPermalink -}} 3 | {{- replace .Content $href (print $href "class=active") | safeHTML -}} 4 | {{ end }} 5 | -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/menu-filetree.html: -------------------------------------------------------------------------------- 1 | {{ $bookSection := default "" .Site.Params.BookSection }} 2 | {{ if eq $bookSection "*" }} 3 | {{ $bookSection = "/" }}{{/* Backward compatibility */}} 4 | {{ end }} 5 | 6 | {{ with .Site.GetPage $bookSection }} 7 | {{ template "book-section-children" (dict "Section" . "CurrentPage" $) }} 8 | {{ end }} 9 | 10 | {{ define "book-section-children" }}{{/* (dict "Section" .Section "CurrentPage" .CurrentPage) */}} 11 |
      12 | {{ range (where .Section.Pages "Params.bookhidden" "ne" true) }} 13 | {{ if .IsSection }} 14 |
    • 15 | {{ template "book-page-link" (dict "Page" . "CurrentPage" $.CurrentPage) }} 16 | {{ template "book-section-children" (dict "Section" . "CurrentPage" $.CurrentPage) }} 17 |
    • 18 | {{ else if and .IsPage .Content }} 19 |
    • 20 | {{ template "book-page-link" (dict "Page" . "CurrentPage" $.CurrentPage) }} 21 |
    • 22 | {{ end }} 23 | {{ end }} 24 |
    25 | {{ end }} 26 | 27 | {{ define "book-page-link" }}{{/* (dict "Page" .Page "CurrentPage" .CurrentPage) */}} 28 | {{ $current := eq .CurrentPage .Page }} 29 | {{ $ancestor := .Page.IsAncestor .CurrentPage }} 30 | 31 | {{ if .Page.Params.bookCollapseSection }} 32 | 33 | 39 | {{ else if .Page.Content }} 40 | 41 | {{- partial "docs/title" .Page -}} 42 | 43 | {{ else }} 44 | {{- partial "docs/title" .Page -}} 45 | {{ end }} 46 | {{ end }} 47 | -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/menu-hugo.html: -------------------------------------------------------------------------------- 1 | 5 | {{ if . }} 6 | {{ template "book-menu-hugo" . }} 7 | {{ end }} 8 | 9 | {{ define "book-menu-hugo" }} 10 | 28 | {{ end }} 29 | -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/menu.html: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | {{ $script := resources.Get "menu-reset.js" | resources.Minify }} 20 | {{ with $script.Content }} 21 | 22 | {{ end }} 23 | -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/post-meta.html: -------------------------------------------------------------------------------- 1 | {{ with .Date}} 2 |
    {{ partial "docs/date" (dict "Date" . "Format" $.Site.Params.BookDateFormat) }}
    3 | {{ end }} 4 | 5 | {{ range $taxonomy, $_ := .Site.Taxonomies }} 6 | {{ with $terms := $.GetTerms $taxonomy }} 7 |
    8 | {{ range $n, $term := $terms }}{{ if $n }}, {{ end }} 9 | {{ $term.Title }} 10 | {{- end }} 11 |
    12 | {{ end }} 13 | {{ end }} 14 | -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/search.html: -------------------------------------------------------------------------------- 1 | {{ if default true .Site.Params.BookSearch }} 2 | 7 | {{ end }} 8 | -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/taxonomy.html: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/title.html: -------------------------------------------------------------------------------- 1 | 5 | {{ $title := "" }} 6 | 7 | {{ if .Title }} 8 | {{ $title = .Title }} 9 | {{ else if and .IsSection .File }} 10 | {{ $title = path.Base .File.Dir | humanize | title }} 11 | {{ else if and .IsPage .File }} 12 | {{ $title = .File.BaseFileName | humanize | title }} 13 | {{ end }} 14 | 15 | {{ return $title }} 16 | -------------------------------------------------------------------------------- /themes/book/layouts/partials/docs/toc.html: -------------------------------------------------------------------------------- 1 | {{ partial "docs/inject/toc-before" . }} 2 | {{ .TableOfContents }} 3 | {{ partial "docs/inject/toc-after" . }} 4 | -------------------------------------------------------------------------------- /themes/book/layouts/posts/list.html: -------------------------------------------------------------------------------- 1 | {{ define "main" }} 2 | {{ range sort .Paginator.Pages }} 3 |
    4 |

    5 | {{ .Title }} 6 |

    7 | {{ partial "docs/post-meta" . }} 8 |

    9 | {{- .Summary -}} 10 | {{ if .Truncated }} 11 | ... 12 | {{ end }} 13 |

    14 |
    15 | {{ end }} 16 | 17 | {{ template "_internal/pagination.html" . }} 18 | {{ end }} 19 | 20 | {{ define "toc" }} 21 | {{ partial "docs/taxonomy" . }} 22 | {{ end }} 23 | -------------------------------------------------------------------------------- /themes/book/layouts/posts/single.html: -------------------------------------------------------------------------------- 1 | {{ define "main" }} 2 |
    3 |

    4 | {{ .Title }} 5 |

    6 | {{ partial "docs/post-meta" . }} 7 |

    8 | {{- .Content -}} 9 |

    10 |
    11 | {{ end }} 12 | 13 | {{ define "toc" }} 14 | {{ partial "docs/toc" . }} 15 | {{ end }} 16 | -------------------------------------------------------------------------------- /themes/book/layouts/shortcodes/button.html: -------------------------------------------------------------------------------- 1 | {{ $ref := "" }} 2 | {{ $target := "" }} 3 | {{ with .Get "href" }} 4 | {{ $ref = . }} 5 | {{ $target = "_blank" }} 6 | {{ end }} 7 | {{ with .Get "relref" }} 8 | {{ $ref = relref $ . }} 9 | {{ end }} 10 | 11 | {{ $.Inner }} 12 | 13 | -------------------------------------------------------------------------------- /themes/book/layouts/shortcodes/columns.html: -------------------------------------------------------------------------------- 1 |
    2 | {{ range split .Inner "<--->" }} 3 |
    4 | {{ . | markdownify }} 5 |
    6 | {{ end }} 7 |
    8 | -------------------------------------------------------------------------------- /themes/book/layouts/shortcodes/details.html: -------------------------------------------------------------------------------- 1 |
    2 | {{ cond .IsNamedParams (.Get "title") (.Get 0) }} 3 |
    4 | {{ .Inner | markdownify }} 5 |
    6 |
    7 | -------------------------------------------------------------------------------- /themes/book/layouts/shortcodes/expand.html: -------------------------------------------------------------------------------- 1 | {{ warnf "Expand shortcode is deprecated. Use 'details' instead." }} 2 |
    3 | 13 |
    14 | -------------------------------------------------------------------------------- /themes/book/layouts/shortcodes/hint.html: -------------------------------------------------------------------------------- 1 |
    2 | {{ .Inner | markdownify }} 3 |
    4 | -------------------------------------------------------------------------------- /themes/book/layouts/shortcodes/katex.html: -------------------------------------------------------------------------------- 1 | {{- if not (.Page.Scratch.Get "katex") -}} 2 | 3 | 4 | 5 | 6 | {{- .Page.Scratch.Set "katex" true -}} 7 | {{- end -}} 8 | 9 | 10 | {{ cond (in .Params "display") "\\[" "\\(" -}} 11 | {{- trim .Inner "\n" -}} 12 | {{- cond (in .Params "display") "\\]" "\\)" }} 13 | 14 | -------------------------------------------------------------------------------- /themes/book/layouts/shortcodes/mermaid.html: -------------------------------------------------------------------------------- 1 | {{ if not (.Page.Scratch.Get "mermaid") }} 2 | 3 | 4 | {{ with resources.Get "mermaid.json" }} 5 | 6 | {{ end }} 7 | {{ .Page.Scratch.Set "mermaid" true }} 8 | {{ end }} 9 | 10 |

    11 | {{- .Inner -}} 12 |

    13 | -------------------------------------------------------------------------------- /themes/book/layouts/shortcodes/section.html: -------------------------------------------------------------------------------- 1 |
    2 | {{ range .Page.Pages }} 3 |
    4 | {{ partial "docs/title" . }} 5 |
    6 |
    7 | {{ default .Summary .Description }} 8 |
    9 | {{ end }} 10 |
    11 | -------------------------------------------------------------------------------- /themes/book/layouts/shortcodes/tab.html: -------------------------------------------------------------------------------- 1 | {{ if .Parent }} 2 | {{ $name := .Get 0 }} 3 | {{ $group := printf "tabs-%s" (.Parent.Get 0) }} 4 | 5 | {{ if not (.Parent.Scratch.Get $group) }} 6 | {{ .Parent.Scratch.Set $group slice }} 7 | {{ end }} 8 | 9 | {{ .Parent.Scratch.Add $group (dict "Name" $name "Content" .Inner) }} 10 | {{ else }} 11 | {{ errorf "%q: 'tab' shortcode must be inside 'tabs' shortcode" .Page.Path }} 12 | {{ end}} 13 | -------------------------------------------------------------------------------- /themes/book/layouts/shortcodes/tabs.html: -------------------------------------------------------------------------------- 1 | {{ if .Inner }}{{ end }} 2 | {{ $id := .Get 0 }} 3 | {{ $group := printf "tabs-%s" $id }} 4 | 5 |
    6 | {{- range $index, $tab := .Scratch.Get $group -}} 7 | 8 | 11 |
    12 | {{- .Content | markdownify -}} 13 |
    14 | {{- end -}} 15 |
    16 | -------------------------------------------------------------------------------- /themes/book/layouts/taxonomy/list.html: -------------------------------------------------------------------------------- 1 | {{ define "main" }} 2 |
    3 |

    {{ .Title | title }}

    4 | {{ $taxonomies := index .Site.Taxonomies .Page.Type }} 5 | {{ range $taxonomies }} 6 | 7 | {{ end }} 8 |
    9 | {{ end }} 10 | 11 | {{ define "toc" }} 12 | {{ partial "docs/taxonomy" . }} 13 | {{ end }} 14 | -------------------------------------------------------------------------------- /themes/book/layouts/taxonomy/taxonomy.html: -------------------------------------------------------------------------------- 1 | {{ define "main" }} 2 | {{ range sort .Paginator.Pages }} 3 |
    4 |

    5 | {{ .Title }} 6 |

    7 | {{ partial "docs/post-meta" . }} 8 |

    9 | {{- .Summary -}} 10 | {{ if .Truncated }} 11 | ... 12 | {{ end }} 13 |

    14 |
    15 | {{ end }} 16 | 17 | {{ template "_internal/pagination.html" . }} 18 | {{ end }} 19 | 20 | {{ define "toc" }} 21 | {{ partial "docs/taxonomy" . }} 22 | {{ end }} 23 | -------------------------------------------------------------------------------- /themes/book/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/themes/book/static/favicon.png -------------------------------------------------------------------------------- /themes/book/static/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/book/static/fonts/roboto-mono-v6-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/themes/book/static/fonts/roboto-mono-v6-latin-regular.woff -------------------------------------------------------------------------------- /themes/book/static/fonts/roboto-mono-v6-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/themes/book/static/fonts/roboto-mono-v6-latin-regular.woff2 -------------------------------------------------------------------------------- /themes/book/static/fonts/roboto-v19-latin-300italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/themes/book/static/fonts/roboto-v19-latin-300italic.woff -------------------------------------------------------------------------------- /themes/book/static/fonts/roboto-v19-latin-300italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/themes/book/static/fonts/roboto-v19-latin-300italic.woff2 -------------------------------------------------------------------------------- /themes/book/static/fonts/roboto-v19-latin-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/themes/book/static/fonts/roboto-v19-latin-700.woff -------------------------------------------------------------------------------- /themes/book/static/fonts/roboto-v19-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/themes/book/static/fonts/roboto-v19-latin-700.woff2 -------------------------------------------------------------------------------- /themes/book/static/fonts/roboto-v19-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/themes/book/static/fonts/roboto-v19-latin-regular.woff -------------------------------------------------------------------------------- /themes/book/static/fonts/roboto-v19-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-design/go-questions/1d12236b03dab99683970ad7b1fabe26013c10b0/themes/book/static/fonts/roboto-v19-latin-regular.woff2 -------------------------------------------------------------------------------- /themes/book/static/katex/auto-render.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("katex")):"function"==typeof define&&define.amd?define(["katex"],t):"object"==typeof exports?exports.renderMathInElement=t(require("katex")):e.renderMathInElement=t(e.katex)}("undefined"!=typeof self?self:this,function(e){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=1)}([function(t,r){t.exports=e},function(e,t,r){"use strict";r.r(t);var n=r(0),o=r.n(n),a=function(e,t,r){for(var n=r,o=0,a=e.length;n -------------------------------------------------------------------------------- /themes/book/static/svg/edit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/book/static/svg/menu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/book/static/svg/toc.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/book/static/svg/translate.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/book/theme.toml: -------------------------------------------------------------------------------- 1 | # theme.toml template for a Hugo theme 2 | # See https://github.com/gohugoio/hugoThemes#themetoml for an example 3 | 4 | name = "Book" 5 | license = "MIT" 6 | licenselink = "https://github.com/alex-shpak/hugo-book/blob/master/LICENSE" 7 | description = "Hugo documentation theme as simple as plain book" 8 | homepage = "https://github.com/alex-shpak/hugo-book" 9 | tags = ["responsive", "clean", "documentation", "docs", "flexbox", "search", "mobile", "multilingual", "disqus"] 10 | features = [] 11 | min_version = "0.68" 12 | 13 | [author] 14 | name = "Alex Shpak" 15 | homepage = "https://github.com/alex-shpak/" 16 | --------------------------------------------------------------------------------