新手问一个 go 实现并发素数筛的问题

2 天前
 RoccoShi
func Filter(in <-chan int, out chan<- int, prime int) {
	for {
		i := <-in
		if i%prime != 0 {
			out <- i
		}
	}
}

func Generate(ch chan<- int) {
	for i := 2; ; i++ {
		ch <- i
	}
}

func main() {
	ch := make(chan int)
	go Generate(ch)
	// print the first 10 prime numbers
	for i := 0; i < 10; i++ {
		prime := <-ch
		println("next prime = ", prime)
		ch1 := make(chan int)
		go Filter(ch, ch1, prime)
		ch = ch1
	}
}

func Filter(in <-chan int, out chan<- int, prime int) {
	for {
		i := <-in
		if i%prime != 0 {
			out <- i
		}
	}
}

func main() {
	ch := make(chan int)
	go func() {
		for i := 2; ; i++ {
			ch <- i
		}
	}()

	for i := 0; i < 10; i++ {
		prime := <-ch
		println("next prime = ", prime)
		ch1 := make(chan int)
		go Filter(ch, ch1, prime)
		ch = ch1
	}
}

运行后发现上面的代码可以实现并发素数筛的功能, 但是下面的闭包写法就不行, 有没有大佬解释一下?

795 次点击
所在节点    Go 编程语言
9 条回复
kkk9
2 天前
😅 func Filter(in int, out int, prime int) PLEASE!!!
hingle
2 天前
go func(ch chan<- int) {
for i := 2; ; i++ {
ch <- i
}
}(ch)
mainjzb
2 天前
ch = ch1
这行导致的

。。这写法
RoccoShi
2 天前
@hingle 这样确实是可以的, 但是我想问的其实是原来错误的写法里, 闭包内部的这个 ch 的生命周期到底是什么样的😂
RoccoShi
2 天前
顺便说一下, 源代码来自 rob pike 的一次分享: <amp-youtube data-videoid="sX8r6zATHGU" layout="responsive" width="480" height="270"></amp-youtube>&t=821s

以及可以在 reddit 上的这个回答查看正确版本的详细数据流分析: https://www.reddit.com/r/golang/comments/434zry/help_understanding_this_prime_sieve_concurrent/
grzhan
2 天前
这应该单纯是个闭包问题:

1. ch 变量本质上是个 *hchan ( https://github.com/golang/go/blob/ed035af7b7d7c1cd2e6f852e22a9b04fc2a2cc65/src/runtime/chan.go#L34 ),是指向 make(chan int) 也就是 runtime.makechan 创建的 hchan 的指针。

2. 直接使用闭包引用 ch ,ch 发生了内存逃逸(因为是 go func ,我 -gcflags="-m" 看了下确实逃逸到了堆),你在闭包 go func 中使用 ch ,就是在操作 ch 指向的 hchan 。

3. main 函数后续 ch = ch1 , 也就是 ch 指向了原本 ch1 指向的 hchan ,导致 go func 操作的 hchan 也发生了变化,进而整体程序没有按照预期执行。

4. 而正确的实现 Generate(ch),以及 func(ch){...}(ch) ,是函数传参,是将 ch 变量值复制传入到了 Generate 或者 go func 里。

如果你去看汇编,这次 ch 就分配在栈里( main.ch+40(SP) ),在运行 go func 之前就把它的值复制到寄存器里供函数使用了 ( MOVQ main.ch+40(SP), CX )。因此后续 main 里 ch 针对 hchan 的指向发生变化不会影响 go func 内部,值复制保证 go func 内部指向的 hchan 不变。


感兴趣自己可以看下汇编研究下,我也只是大致看了下:go tool compile -S main.go > assembly.s
RoccoShi
2 天前
@grzhan 👍👍解释的很清楚
grzhan
2 天前
说起来这个 Rob Pike 的素数筛法应该思路就是当年 CSP 1978 这篇论文提到的素数算法,以前欧长坤老板做过 CSP 1978 论文解读的分享,里面就有素数筛法的例子,论文当然是自己的 CSP 语言,而他写了个 Go 版本:
https://github.com/changkun/pkg/blob/f787593b4467793f8ee0b07583ea9ffde5adf2be/csp/csp.go#L833
kingcanfish
1 天前
@grzhan #8 https://swtch.com/~rsc/thread/ 补充下 Russ Cox 的文章 顺便提一嘴 xv6 有个实验就是实现这样的素数筛

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1084650

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX