限制并发数有好几种写法:固定 worker 读 channel、信号量、errgroup.SetLimit。它们都能把同时在做事的任务数限住,但 goroutine 数、内存和速度不一样。这篇用真实运行把它们并排比较,看任务慢和任务极短两种情形。
列:用时 ms / 峰值 goroutine 数 / 同时在做事的最大个数 / 累计分配 KiB / 进程向系统申请的内存 MiB 固定 N 个 worker 读任务 channel 200 ms / 52 / 50 / 73 KiB / 9 MiB 信号量:先占位再起 goroutine 201 ms / 52 / 50 / 378 KiB / 13 MiB 信号量:先起 goroutine 再在里面占位 201 ms / 2002 / 50 / 1475 KiB / 17 MiB errgroup.SetLimit(N) 201 ms / 52 / 50 / 398 KiB / 13 MiB 不限制,每个任务一个 goroutine 6 ms / 2002 / 2000 / 1315 KiB / 17 MiB
五种写法同时在做事的最大个数都符合预期(50;不限制是 2000)。但“先起 goroutine 再在里面抢信号量”会同时存在 2002 个 goroutine,虽然并发受限,内存多申请了几 MiB。先占位再起,或者固定 worker,峰值只有 52。
列:用时 ms / 峰值 goroutine 数 / 同时在做事的最大个数 / 累计分配 KiB / 进程向系统申请的内存 MiB 固定 N 个 worker 读任务 channel 414 ms / 10 / 4 / 27 KiB / 13 MiB 信号量:先占位再起 goroutine 649 ms / 126 / 4 / 31475 KiB / 17 MiB 信号量:先起 goroutine 再在里面占位 521 ms / 841 / 4 / 31864 KiB / 17 MiB errgroup.SetLimit(N) 646 ms / 145 / 4 / 39279 KiB / 17 MiB 不限制,每个任务一个 goroutine 417 ms / 170 / 6 / 23727 KiB / 21 MiB
100 万个 50 次加法的任务,固定 8 个 worker 用 414 ms、几乎不分配(27 KiB);每任务起一个 goroutine 的写法用 521–649 ms、累计分配 31–39 MB。不限制的写法 417 ms 看起来不慢,但同时有 170 个 goroutine,内存申请 21 MiB,而且没有限制,不能用在任务重的场景。注意这里“同时在做事的最大个数”只有 4–6,因为任务太短几乎不重叠。选择上:任务多且短,用固定 worker 读 channel;任务慢、数量少,errgroup.SetLimit 写起来最省事,性能与手写信号量一样;无论哪种,都别在 goroutine 里面再抢信号量。每种只测了 3 次、任务内容只有睡眠和极短加法两种,结果对真实业务不一定适用。