编程语言与软件工程2019golangweekly.com编辑精选
理解 Go 中的真实世界并发缺陷
Understanding Real-World Concurrency Bugs in Go
进阶 · 约 45 分钟读完前置知识:Go 并发基础、锁与竞态条件、实证研究方法
导读
这篇论文通过收集并分析真实 Go 项目(如 Docker、Kubernetes 等)中的并发 bug 报告,来考察 Go 并发模型在实际使用中会在哪些地方出错。核心发现与“消息传递比共享内存更不容易出错”的常见看法相反:在被研究的 Go 应用中,由消息传递(channel)使用不当导致的阻塞类 bug 比共享内存保护不当导致的更多;同时,共享内存同步原语在实际代码中的使用频率高于消息传递。
HN 关注点:它用真实数据挑战了 Go 语言“用 channel 做并发更简单安全”的宣传,引发读者对 channel 设计是否明智的争论。
后来怎么样
这篇论文系统性地挑战了“channel 比共享内存更安全”的直觉,成为讨论 Go 并发模型缺陷时广泛引用的实证研究,并推动了后续对 Go 并发 bug 的实证分析与工具研究。
HN 评论精选
这几条评论里:支持 3 补充 1
支持论文数据说明 channel 更容易写错且实际使用更少,认为加入 channel 是 Go 最大的设计失误。
对我来说有两点关键结论:(1)“与消息传递更不易出错的普遍看法相反,在我们研究的 Go 应用中,由错误的消息传递导致的阻塞 bug 比由错误的共享内存保护导致的更多。”(2)“共享内存同步操作的使用频率高于消息传递……”所以基本上:(1) 人们用消息传递编写正确的多线程例程更困难;(2) 在大型生产应用中,人们往往还是退回到共享内存原语。Go 设计者的初衷似乎是做一门简单易上手的语言。既然大多数程序员已经习惯用共享内存做多线程编程、而且这在概念上更简单,我认为设计者把 channel 这种相对新颖、实验性的方式(我知道 CSP 是 1978 年的,但我说的是被广泛采用的工业语言)放进语言里是个错误,这可能是 Go 设计上最大的一个错误。
c61查看英文原文
The two key takeaways for me are: (1) "Contrary to the common belief that message passing is less error-prone, more blocking bugs in our studied Go applications are caused by wrong message passing than by wrong shared memory protection." (2) "Shared memory synchronization operations are used more often than message passing, ..." So basically, (1) people have more trouble writing correct multithreaded routines using message passing, and (2) in large production applications people tend to fall back to using shared-memory primitives rather than message-passing anyway. It seems the intention of the Go language designers was to make a language that would be simple and easy for programmers to pick up. Given that most programmers are already accustomed to multithreaded programming using shared memory and that this is conceptually simpler, I think the language designers made a mistake by throwing channels, a relatively new and experimental approach (yes, I know CSP is from 1978, but I'm talking about widely-adopted industrial languages), into the language. I think it was the single biggest mistake in the design of Go.
补充从语言机制层面解释 channel 为何易错:可传引用、单向、关闭难,对比 Rust 的所有权设计。
确实如此。Go 的共享内存模型基本和 C/C++ 一样:有锁、有数据对象,语言并不知道哪个锁保护哪些数据——这正是 Rust 做对的大事。Go 的消息传递模型也容易出错,因为它允许传递引用,于是你可以通过 channel 造出共享数据;把 slice(底层数组的引用)传过 channel 就很容易无意间造成这种情况。更一般地说,Go 的 channel 是单向的,而你经常需要回传结果,就得在裸 channel 上自己搭双向机制,而且往往是在裸 channel 层面做,而不是封装成“对象”。另外,做一个能干净关闭的机制也相当棘手。
Animats查看英文原文
Well, yes. Go's shared memory model is essentially the same as that of C and C++. You have locks, and you have data objects, and the language has no idea which lock covers what data. That's the big thing Rust got right. Go's message passing model is somewhat error prone. It lets you pass references. So you can create shared data through the channels. It's easy to do this accidentally by passing slices, which are references to the underlying array, across a channel. More generally, Go channels are one-way. Since you often want an answer back, you have to create some two-way mechanism from the raw one-way channels. It seems to be common to do this at the raw channel level, rather than using some "object" like encapsulation. Creating a mechanism which shuts down cleanly is somewhat tricky, too.
支持肯定研究方法(基于 GitHub 的 bug 研究),呼吁对 Go 并发模型保持警惕。
这篇论文很不错,值得更多关注。GitHub 让这类 bug 研究变得容易多了。希望这项研究能让人们对 Go 的并发模型更谨慎——它和共享内存多线程一样容易出错,甚至更容易。
zzzcpan查看英文原文
This is pretty good and deserves more attention. Github makes bug studies so much more approachable. Hopefully the study will make people more cautious about Go's concurrency model, which is as error prone as shared memory multithreading, if not more.
支持认为 Go 并发带来的简单感是虚假的安全感。
这印证了我对“Go 的并发多么性感、多么简单”这类说法的怀疑。它最多给你一种虚假的安全感,而一旦你滥用这些特性、事情出了岔子,那就有得受了。
mirceal查看英文原文
confirms some of my suspicions to claims on how sexy and easy go is when it comes to concurrency. if anything it will give you a false a sense of security but god help you in case things go south and you’ve abused these features.
历次讨论
- 2019Hacker News 讨论帖165 分 · 70 评
同领域还值得读
Google 的软件工程(2017)
Software Engineering at Google (2017)
介绍了 Google 的软件工程实践,包括其单一代码库(monorepo)、几乎全部开发在主干进行而非分支、代码审查、构建系统 Blaze、协议缓冲区等工具与流程,并讨论了频繁重…
编程语言与软件工程2017 · 1,127 分 · 465 评 · 引用 15入门 · 约 45 分钟编辑精选Peter Naur:编程作为理论构建 (1985)
Peter Naur – Programming as Theory Building (1985)
Peter Naur 1985 年的经典论文《Programming as Theory Building》提出:编程的本质不是写代码或文档,而是在程序员头脑中构建对程序及其运行…
编程语言与软件工程2016 · 629 分 · 74 评入门 · 约 45 分钟编辑精选SciPy 1.0:Python 科学计算的基础算法
SciPy 1.0: fundamental algorithms for scientific computing in Python
这篇发表于 Nature Methods 的论文介绍了 SciPy 1.0——Python 科学计算生态的基石库,涵盖其核心算法模块(优化、积分、插值、线性代数等)、软件架构与社…
编程语言与软件工程2020 · 387 分 · 98 评入门 · 约 45 分钟编辑精选Lisp 的历史(1979)
History of Lisp (1979)
这是 Lisp 发明者 John McCarthy 于 1979 年为 HOPL(编程语言史)会议撰写的回顾文章,讲述 Lisp 的起源、设计动机与早期演进,文中也涉及 if 表…
编程语言与软件工程2018 · 352 分 · 44 评入门 · 约 45 分钟编辑精选使用 NumPy 进行数组编程
Array Programming with NumPy
这篇 Nature 文章介绍 NumPy 的数组编程模型及其在科学计算中的核心作用:以 ndarray 为中心,通过向量化操作让 Python 高效处理多维数组。
编程语言与软件工程2020 · 318 分 · 134 评入门 · 约 40 分钟编辑精选