https://www.alexisbouchez.com/blog/http-error-handling-in-go
在这篇短文中,我将与你分享一个我用来集中处理 HTTP 处理程序的错误的简单模式。
如果你写过一些 Go HTTP 服务器,你可能已经厌倦了一遍又一遍地编写相同的错误处理代码:
1 | func SomeHandler(w http.ResponseWriter, r *http.Request) { |
这段代码重复性很高,并且用样板代码塞满了你的处理程序,而不是业务逻辑。
https://www.alexisbouchez.com/blog/http-error-handling-in-go
在这篇短文中,我将与你分享一个我用来集中处理 HTTP 处理程序的错误的简单模式。
如果你写过一些 Go HTTP 服务器,你可能已经厌倦了一遍又一遍地编写相同的错误处理代码:
1 | func SomeHandler(w http.ResponseWriter, r *http.Request) { |
这段代码重复性很高,并且用样板代码塞满了你的处理程序,而不是业务逻辑。
Ron Buckton是微软的一位10x工程师,专注于TypeScript开发。他在微软工作了18年,其中10年致力于TypeScript,为其性能提升做出了巨大贡献,尤其是让构建速度和编辑器响应速度提升了10倍。然而,2025年5月14日,微软宣布裁员6000人,Ron不幸被解雇。他在X上发文表达失望,提到需要时间消化消息。

Ron Buckton是微软的一位资深软件开发工程师,专注于TypeScript的开发。TypeScript是由微软开发的自由与跨平台的编程语言,是JavaScript的超集,主要提供类型检查和静态检查功能。根据Ron Buckton的LinkedIn资料,他曾是TypeScript团队的成员,长期致力于该语言的优化与发展。他的GitHub账户显示,他参与了TypeScript编译器和语言服务的多个项目,体现了他在技术领域的深厚积累。
Ron在TypeScript团队工作了近十年,为其性能提升做出了显著贡献。根据一篇技术文章,他的努力让TypeScript的构建速度和编辑器响应速度提升了10倍,这一成果被开发者广泛认可。可以说,Ron是TypeScript的“灵魂人物”,他的代码如同语言的DNA,深深嵌入其中。
然而,2025年5月14日,微软宣布裁员6000人,作为组织调整的一部分,旨在适应动态市场。根据微软裁员报道,Ron不幸成为被解雇的员工之一。他在X上发文表示:“在微软工作了18年,其中十年都在TypeScript团队工作,不幸在最近的裁员中被解雇了。我需要几天时间来消化这个消息。感谢一路以来支持我的人。”这一消息引发了社区的广泛讨论,Hacker News上的讨论中,有人表达了对微软决策的质疑,认为放走这样的人才令人遗憾。
Ron的遭遇在网络上引发了诸多反应,网友的评论为故事增添了喜剧色彩。有人戏称:“Ron,你是TypeScript的英雄,任何公司都会抢着要你的!”也有人开玩笑:“微软真是瞎了眼,居然放走了这样的人才!”还有人调侃:“Ron,你现在有空了,不如来帮我优化一下我的个人网站吧?”这些评论既表达了对Ron的敬意,也通过幽默缓解了裁员带来的沉重感。
Ron的职业生涯中,与TypeScript首席架构师Anders Hejlsberg的合作令人难忘。Anders是编程界的传奇人物,创造了Turbo Pascal、Delphi、JScript、C#和TypeScript等多款语言。根据Ron的职业经历,他曾与Anders就TypeScript的未来方向展开无数次讨论。Anders曾对他说:“Ron,你对TypeScript的贡献无人能及,你就是这门语言的守护者。”然而,如今守护者却被自己的“孩子”抛弃,这其中的讽刺与无奈让人唏嘘。

Ron年近40,在编程界被视为大龄工程师。随着年龄增长,找一份合适的工作变得越来越难。根据行业分析,Tech行业的竞争日益激烈,年轻工程师如雨后春笋般涌现,而老一辈工程师则面临被淘汰的风险。Ron坐在家中,看着窗外的风景,叹了口气。他深知,编程世界瞬息万变,今天的英雄,明天可能就成为昨日的黄花。即使为公司奉献了十几二十年,也可能在一夜之间被扫地出门。
Ron的遭遇反映了Tech行业裁员潮对大龄工程师的冲击。根据最新报道,微软的裁员涉及全球3%的员工,这一决策被认为是适应AI和数据中心投资的组织调整。然而,对于像Ron这样为公司贡献了近20年的老兵来说,这种调整显得冷酷无情。
尽管如此,Ron并没有气馁。他决定利用这段时间,好好休息,陪陪家人,同时开始寻找新的机会。他相信,以他的能力和经验,一定会找到一个适合他的位置。或许,他还会继续为编程世界做出贡献,只是下一个舞台在哪里,还不得而知。
单指令多数据流(SIMD,Single Instruction Multiple Data)是一种并行计算技术,允许一条指令同时处理多个数据点。SIMD在现代CPU中广泛应用,能够显著提升计算密集型任务的性能,如图像处理、机器学习、科学计算等。随着Go语言在高性能计算领域的应用逐渐增多,SIMD支持成为了开发者关注的焦点。
当前很多主流和新型的语言都有相应的simd库了,比如C++、Rust、Zig等,但Go语言的simd官方支持还一直在讨论中(issue#67520)。Go语言的设计目标是简单性和可移植性,而SIMD的实现通常需要针对不同的硬件架构进行优化,这与Go的设计目标存在一定冲突。因此,Go语言对SIMD的支持一直备受争议。
最近几周这个issue的讨论有活跃起来, 希望能快点支持。
DeepSeek出街老火了,整个AI界都在热火朝天的讨论它。
同时,安全界也没闲着,来自美国的攻击使它不得不通知中国大陆以外的手机号的注册,同时大家也对它的网站和服务安全性进行了审视,这不Wiz Research就发现它们的数据库面向公网暴露并且无需任何身份即可访问。这两个域名oauth2callback.deepseek.com:9000和dev.deepseek.com:9000。
AI的核心技术既需要这些清北的天才去研究,产品也需要专业的人才去打磨。像DeepSeek这么专业的公司都可能出现这样的漏洞,相信互联网上这么数据库无密码暴露的实例也应该不在少数(实际只找到了2个)。
基于上一篇《扫描全国的公网IP要多久》,我们改造一下代码,让它使用 tcp_syn 的方式探测clickhopuse的9000端口。
首先声明,所有的技术都是为了给大家介绍使用Go语言开发底层的网络程序所做的演示,不是为了介绍安全和攻击方面的内容,所以也不会使用已经成熟的端口和IP扫描工具如zmap、rustscan、nmap、masscan、Advanced IP Scanner、Angry IP Scanner、unicornscan等工具。
同时,也不会追求快速,我仅仅在家中的100M的网络中,使用一台10多年前的4核Linux机器进行测试,尽可能让它能出结果。我一般晚上启动它,早上吃过早餐后来查看结果。
Go 语言的 io/fs 包是 Go 1.16 版本引入的一个标准库包,它定义了文件系统的抽象接口。这个包提供了一种统一的方式来访问不同类型的文件系统,包括本地文件系统、内存文件系统、zip 文件等。
自从加入百度负责物理网络的监控业务之后,我大部分的都是编写各种各样额度底层的网络程序。业余时间我也是编写一些有趣的网络程序,不仅仅是兴趣,也是为未来的某个业务探索一下技术方案。
而且这次,我想知道,就在我这一个10年前的小mini机器4核机器上,在家庭网络中扫描全国(中国大陆)的所有的公网IP地址需要多少时间。
利用它,我可以知道和全国各省市的运营商、云服务商的联通情况。有没有运营商的出口故障以及IP已没有被运营商或者有关部门劫持。
TL;DR: 一共扫描了3亿个地址(343142912),当前ping的通的IP 592万个(5923768),耗时1小时(1h2m57.973755197s)。
这次我重构了以前的一个扫描公网IP的程序。先前的程序使用gopacket收发包,也使用gopacket组装包。但是gopacket很讨厌的的一个地方是它依赖libpcap库,没有办法在禁用CGO的情况下。
事实上利用Go的扩展包icmp和ipv4,我们完全可以不使用gopacket实现这个功能,本文介绍具体的实现。
Go语言中Timer以及相关的Ticker、time.After、time.AfterFunc 等定时器最终是以四叉堆的数据形式存放的。
全局的 timer 堆也经历过三个阶段的重要升级。
常见的堆(heap)常常以二叉堆的形式实现。可是为什么Go timer使用四叉堆呢?
今天在准备《秘而不宣》系列下一篇文章时,思绪飘散了,突然想到使用 Heap 的功能再加 HashTable (Map) 的功能,可以构造一种新的数据结构,然后把我聚合程序中的数据聚合数据结构替换掉,总之思绪翩翩。然后在网上搜了一下,这种数据结构其实早就有了,名字叫 HeapMap。
treap 是一棵二叉树,它同时维护二叉搜索树 (BST) 和堆的属性, 所以由此得名 (tree + heap ⇒ treap)。
从形式上讲,treap (tree + heap) 是一棵二叉树,其节点包含两个值,一个 key 和一个 priority,这样 key 保持 BST 属性,priority 是一个保持 heap 属性的随机值(至于是最大堆还是最小堆并不重要)。相对于其他的平衡二叉搜索树,treap的特点是实现简单,且能基本实现随机平衡的结构。属于弱平衡树。
treap 由 Raimund Siedel 和 Cecilia Aragon 于 1989 年提出。
treap 通常也被称为“笛卡尔树”,因为它很容易嵌入到笛卡尔平面中:
具体来说,treap 是一种在二叉树中存储键值对 (X,Y) 的数据结构,其特点是:按 X 值满足二叉搜索树的性质,同时按 Y 值满足二叉堆的性质。如果树中某个节点包含值 (X₀,Y₀),那么:
X ≤ X₀ (BST 属性)X₀ ≤ X (BST 属性)在这种实现中, X是键(同时也是存储在 Treap 中的值),并且 Y称为优先级。如果没有优先级,则 treap 将是一个常规的二叉搜索树。
优先级(前提是每个节点的优先级都不相同)的特殊之处在于:它们可以确定性地决定树的最终结构(不会受到插入数据顺序的影响)。这一点是可以通过相关定理来证明的。
这里有个巧妙的设计:如果我们随机分配这些优先级值,就能在平均情况下得到一棵比较平衡的树(避免树退化成链表)。这样就能保证主要操作(如查找、插入、删除等)的时间复杂度保持在 O(log N) 水平。
正是因为这种随机分配优先级的特点,这种数据结构也被称为"随机二叉搜索树"。
Treap维护堆性质的方法用到了旋转,且只需要进行两种旋转操作,因此编程复杂度较红黑树、AVL树要小一些。
红黑树的操作:
插入
以最大堆为例
给节点随机分配一个优先级,先和二叉搜索树的插入一样,先把要插入的点插入到一个叶子上,然后跟维护堆一样进行以下操作:
删除
因为 treap满足堆性质,所以只需要把要删除的节点旋转到叶节点上,然后直接删除就可以了。具体的方法就是每次找到优先级最大的子叶,向与其相反的方向旋转,直到那个节点被旋转到了叶节点,然后直接删除。
查找
和一般的二叉搜索树一样,但是由于 treap的随机化结构,Treap中查找的期望复杂度是 O(logn)
以上是 treap 数据结构的背景知识,如果你想了解更多而关于 treap 的知识,你可以参考
在 Go 运行时 sema.go#semaRoot 中,定义了一个数据结构 semaRoot:
1 | type semaRoot struct { |
这是Go语言互斥锁(Mutex)底层实现中的关键数据结构,用于管理等待获取互斥锁的goroutine队列。我们已经知道,在获取 sync.Mutex 时,如果锁已经被其它 goroutine 获取,那么当前请求锁的 goroutine 会被 block 住,就会被放入到这样一个数据结构中 (所以你也知道这个数据结构中的 goroutine 都是唯一的,不重复)。
semaRoot 保存了一个平衡树,树中的 sudog 节点都有不同的地址 (s.elem) ,每个 sudog 可能通过 s.waitlink 指向一个链表,该链表包含等待相同地址的其他 sudog。对具有相同地址的 sudog 内部链表的操作时间复杂度都是O(1).。扫描顶层semaRoot列表的时间复杂度是 O(log n) ,其中 n 是具有被阻塞goroutine的不同地址的数量(这些地址会散列到给定的semaRoot)。
semaRoot 的 treap *sudog 其实就是一个 treap, 我们来看看它的实现。
增加一个等待的goroutine(sudog)到 semaRoot 的 treap 中,如果 lifo 为 true,则将 s 替换到 t 的位置,否则将 s 添加到 t 的等待列表的末尾。
1 | func (root *semaRoot) queue(addr *uint32, s *sudog, lifo bool) { |
① 是遍历 treap 的过程,当然它是通过搜索二叉树的方式实现。 addr就是我们一开始讲的treap的key,也就是 s.elem。
首先检查 addr 已经在 treap 中,如果存在,那么就把 s 加入到 addr 对应的 sudog 链表中,或者替换掉 addr 对应的 sudog。
这个addr, 如果对于sync.Mutex来说,就是 Mutex.sema字段的地址。
1 | type Mutex struct { |
所以对于阻塞在同一个sync.Mutex上的goroutine,他们的addr是相同的,所以他们会被加入到同一个sudog链表中。
如果是不同的sync.Mutex锁,他们的addr是不同的,那么他们会被加入到这个treap不同的节点。
进而,你可以知道,这个rootSema是维护多个sync.Mutex的等待队列的,可以快速找到不同的sync.Mutex的等待队列,也可以维护同一个sync.Mutex的等待队列。
这给了我们启发,如果你有类似的需求,可以参考这个实现。
③就是设置这个节点的优先级,它是一个随机值,用于保持treap的平衡。这里有个技巧就是总是把优先级最低位设置为1,这样保证优先级不为0.因为优先级经常和0做比较,我们将最低位设置为1,就表明优先级已经设置。
④ 就是将这个新加入的节点旋转到合适的位置,以保持treap的平衡。这里的旋转操作就是上面提到的左旋和右旋。稍后看。
对应的,还有出对的操作。这个操作就是从treap中移除一个节点,这个节点就是一个等待的goroutine(sudog)。
dequeue 搜索并找到在semaRoot中第一个因addr而阻塞的goroutine。
比如需要唤醒一个goroutine, 让它继续执行(比如直接将锁交给它,或者唤醒它去争抢锁)。
1 | func (root *semaRoot) dequeue(addr *uint32) (found *sudog, now, tailtime int64) { |
① 是遍历 treap 的过程,当然它是通过搜索二叉树的方式实现。 addr就是我们一开始讲的treap的key,也就是 s.elem。如果找到了,就跳到 Found 标签。如果没有找到,就返回 nil。
④是检查这个地址上是不是有多个等待的goroutine,如果有,就把这个节点替换成链表中的下一个节点。把这个节点从treap中移除并返回。
如果就一个goroutine,那么把这个移除掉后,需要旋转treap,直到这个节点被旋转到叶节点,然后删除这个节点。
这里的旋转操作就是上面提到的左旋和右旋。
rotateLeft 函数将以 x 为根的子树左旋,使其变为 y 为根的子树。
左旋之前的结构为 (x a (y b c)),旋转后变为 (y (x a b) c)。
1 | func (root *semaRoot) rotateLeft(x *sudog) { |
具体步骤:
y 设为 x 的父节点(②),x 设为 y 的左子节点(①)。b 设为 x 的右子节点(③),并更新其父节点为 x(④)。y 的父节点为 p(⑤),即 x 的原父节点。如果 p 为 nil,则 y 成为新的树根(⑥)。y 是 p 的左子节点还是右子节点,更新对应的指针(⑦)。
左旋为
rotateRight 旋转以节点 y 为根的树。
将 (y (x a b) c) 变为 (x a (y b c))。
1 | func (root *semaRoot) rotateRight(y *sudog) { |
具体步骤:
右旋为
理解了左旋和右旋,你就理解了出队代码中这一段为什么把当前节点旋转到叶结点中了:
1 | // 旋转s到叶节点,以便删除 |
整体上看,treap这个数据结构确实简单可维护。左旋和右旋的代码量很少,结合图看起来也容易理解。 出入队的代码也很简单,只是简单的二叉搜索树的操作,加上旋转操作。
这是我介绍的Go秘而不宣的数据结构第三篇,希望你喜欢。你还希望看到Go运行时和标准库中的哪些数据结构呢,欢迎留言。
我会不定期的从关注者列表并点赞文章的同学中选出一位,送出版商和出版社的老师赠送的书,欢迎参与。