惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 叶小钗
Martin Fowler
Martin Fowler
H
Help Net Security
月光博客
月光博客
Stack Overflow Blog
Stack Overflow Blog
V
Visual Studio Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
K
Kaspersky official blog
MongoDB | Blog
MongoDB | Blog
L
LINUX DO - 最新话题
S
Schneier on Security
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Cyber Attacks, Cyber Crime and Cyber Security
AI
AI
The Hacker News
The Hacker News
Latest news
Latest news
D
Darknet – Hacking Tools, Hacker News & Cyber Security
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
GbyAI
GbyAI
SecWiki News
SecWiki News
C
Check Point Blog
T
The Exploit Database - CXSecurity.com
J
Java Code Geeks
V2EX - 技术
V2EX - 技术
Y
Y Combinator Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
U
Unit 42
Attack and Defense Labs
Attack and Defense Labs
Security Latest
Security Latest
Help Net Security
Help Net Security
博客园 - Franky
L
LINUX DO - 热门话题
N
News and Events Feed by Topic
Cloudbric
Cloudbric
P
Proofpoint News Feed
Hacker News - Newest:
Hacker News - Newest: "LLM"
博客园 - 【当耐特】
Schneier on Security
Schneier on Security
Google Online Security Blog
Google Online Security Blog
C
Cisco Blogs
Webroot Blog
Webroot Blog
C
CERT Recently Published Vulnerability Notes
Simon Willison's Weblog
Simon Willison's Weblog
A
Arctic Wolf
O
OpenAI News
T
Threat Research - Cisco Blogs
Know Your Adversary
Know Your Adversary
C
CXSECURITY Database RSS Feed - CXSecurity.com
The Last Watchdog
The Last Watchdog

Random Thoughts

LLM 训练与推理的基本理解 昨日的世界不再重来 Magic Brush - 画出你自己的产品宇宙 写在 AI Coding 奇点之后 尼泊尔布恩山小环线纪行 2025 投资组合年报 三十而笠 我的 8 年职业生涯回顾 我的颈椎病康复之旅 - 关于选择的故事 熊野古道中边路纪行 新加坡旅居三年再回首 高加索三国 - 阿塞拜疆行记 2024 投资组合年报 重新思考 Go:了解程序在线上是如何运行的 欧游散记 —— 环勃朗峰之旅 欧游散记 —— 特摩索斯古城 重新思考 Go:Channel 不是「消息队列」 重新思考 Go:Slice 只是「操作视图」 那一天,我决定踏出一步 True Story 少数价值 A Snapshot of Myself - 2021 RPC 漫谈: 连接问题 RPC 漫谈:序列化问题 RPC 漫谈: 限流问题 科学,技术与工程 Pond: Golang 通用对象池 Golang for-range 内部实现 真理的有限性 Golang rand 库锁竞争优化 Meaningless or Meaningful 分布式文件系统的演化 从动物森友会聊主机游戏联机机制 创业公司的文化 伊朗见闻录 NodeJS 内存泄漏检测与定位 设计实现高性能本地内存缓存 Travel Map Linux I/O 栈浅析 SSD 背后的奥秘 什么是真正的编程能力 沉默与反沉默的理由 命令行里的设计艺术 自由意志下的选择 学习思维方式而非学习观点 从程序到人 —— 情头配对助手的前世今生 即刻多端实时通信实践 朝鲜 —— 小国寡民的主体思想实践 使用 Surge 提升多网络环境下的流畅开发体验 一份其实好吃的 LaTeX 入门餐 Kafka 的设计与实践思考 欧游散记 —— 民主专制下的德国 欧游散记 —— 维也纳 欧游散记 —— 西班牙 欧游散记 —— 德国国会大厦 社会矛盾讨论统一框架的一种可能性 欧游散记 —— 伪君子布拉格 Lemon : Koa 风格的 Python 异步 Web 框架 Golang : Make Programming Happy Again Podcast 闲言碎语 Kubernetes 中使用 API Gateway 替代 Ingress ElasticSearch 最佳实践 blog.joway.io Presentations
Golang Interface 内部实现
Joway · 2021-01-20 · via Random Thoughts

最近遇到一个由于 Golang Interface 底层实现,引发的线上 panic 问题,虽然根源在于赋值操作没有保护起来,却意外地发现了关于 interface 的一些有意思的底层细节。

假设我们现在有以下定义:

type Interface interface {
    Run()
}

type Implement struct {
    n int
}

func (i *Implement) Run() {
    fmt.Printf(i.n)
}

对于使用者而言,一个变量无论是 Interface 类型或是 *Implement 类型,差别都不大。

func main() {
    var i Interface
    fmt.Printf("%T\n", i) //<nil>
    i = &Implement{n: 1}
    fmt.Printf("%T\n", i) //*main.Implement

    var p *Implement
    fmt.Printf("%T\n", p) //*main.Implement
    p = &Implement{n: 1}
    fmt.Printf("%T\n", p) //*main.Implement
}

如果现在有这么一段代码:

func check(i Interface) {
    if i == nil {
        return
    }
    impl := i.(*Implement)
    fmt.Println(impl.n) //Invalid memory address or nil pointer dereference
}

这段代码从逻辑上来说,impl.n 永远都不会报空指针异常,因为 i 如果为空就会提前返回了。而且就算 i 为 nil,在 impl := i.(*Implement) 类型转换的时候就会直接 panic,而不是在下一行。但在线上环境上却的确在 impl.n 位置报了错误。

在探究了 interface 底层实现后发现,在上面的 main 函数的例子里,i 和 p 虽然在使用方式上是一致的,但在内部存储的结构体却是不同的。*Implement 类型内部存储的是一个指针,对他赋值也只是赋予一个指针。而 Interface 接口底层结构却是一个类型为 iface 的 struct :

type iface struct {
    tab  *itab
    data unsafe.Pointer
}

type itab struct {
    inter *interfacetype
    _type *_type
    hash  uint32 // copy of _type.hash. Used for type switches.
    _     [4]byte
    fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}

当对一个接口赋值时,即对该 struct 的 tabdata 字段分别赋值。而该操作并非是原子性的,有可能赋值到一半,也就是 .tab 有值而 .data 为空时,就被另一个 goroutine 抢走,并进行 != nil 的判断。而 golang 却只有在 iface 两个属性同时为 nil 时候才认为是 nil,所以 check 函数内的 if 条件判断失效。

同时由于 .tab 内已经有了类型信息,所以在 impl := i.(*Implement) 类型转换的时候也能够成功转换,并不会报空指针错误,即便该 interface 的 .data 字段是 nil 。只有当实际去调用 impl.n 的时候,才会发现 .data 为 nil,从而 panic。