我可能遗漏了一些东西,或者没有理解Go如何处理并发(或者我对并发本身的了解),我已经设计了一些代码来理解多个生产者/消费者。
这是代码:
package main
import (
"fmt"
"time"
// "math/rand"
"sync"
)
var seq uint64 = 0
var generatorChan chan uint64
var requestChan chan uint64
func makeTimestamp() int64 {
return time.Now().UnixNano() / int64(time.Millisecond)
}
func generateStuff(genId int) {
var crap uint64
for {
crap = <-requestChan
// <- requestChan
seq = seq+1
fmt.Println("Gen ", genId, " - From : ", crap, " @", makeTimestamp())
generatorChan <- uint64(seq)
}
}
func concurrentPrint(id int, work *sync.WaitGroup) {
defer work.Done()
for i := 0; i < 5; i++ {
requestChan<-uint64(id)
fmt.Println("Conc", id, ": ", <-generatorChan)
}
}
func main() {
generatorChan = make(chan uint64)
requestChan = make(chan uint64)
var wg sync.WaitGroup
for i := 0; i < 20; i++ {
go generateStuff(i)
}
maximumWorker := 200
wg.Add(maximumWorker)
for i := 0; i < maximumWorker; i++ {
go concurrentPrint(i, &wg)
}
wg.Wait()
}
当它运行时,它打印(主要是按顺序)所有数字从1到1000(200个消费者得到每个数字5次)。 我希望有些消费者可以打印完全相同的数字,但似乎 requestChan 就像屏障一样,即使有20个goroutines服务于 generateStuff 通过增加全局变量来生成数字。
一般来说Go或Concurrency我有什么问题?
我会预料到类似 generateStuff 的两个例行程序的情况会一起醒来并同时增加seq,因此有两个消费者打印相同的数字二次。
编辑关于playgolang的代码:http://play.golang.org/p/eRzNXjdxtZ
答案 0 :(得分:1)
您有多个工作人员可以同时运行,并且所有工作人员同时尝试并发出请求。由于requestChan
是无缓冲的,因此它们都阻止等待读者同步并接受他们的请求。
您有多个生成器将通过requestChan
与请求者同步,生成结果,然后阻止未缓冲的generatorChan
,直到工作者读取结果。请注意,它可能是一个不同的工作人员。
没有其他同步,所以其他一切都是非确定性的。
seq
在任何其他发电机碰巧有机会运行之前。甚至可能只有一个处理器。seq
,从而导致各种问题。通常,如果不添加同步来强制执行其中一种行为,则无法确保实际发生这些行为。
请注意,对于数据竞争,这本身就是另一个非确定性事件。可能会得到任意值,程序崩溃等等。假设在竞争条件下,这个值可能只是一个或一些相对无害的结果,这是不安全的。
对于实验,您可以做的最好的事情是GOMAXPROCS
。通过环境变量(例如env GOMAXPROCS=16 go run foo.go
之后的env GOMAXPROCS=16 ./foo
或go build
)或通过调用程序中的runtime.GOMAXPROCS(16)
。默认值为1,这意味着可能隐藏数据竞赛或其他“奇怪”行为。
您还可以通过在不同位置添加对runtime.Gosched
或time.Sleep
的调用来稍微影响一些事情。
如果您使用竞争检测器(例如,使用go run -race foo.goo
或go build -race
),您还可以看到数据竞争。该程序不仅应该在退出时显示“Found 1 data race(s)”,而且还应该在首次检测到比赛时使用堆栈跟踪转储大量细节。
以下是您的实验代码的“清理”版本:
package main
import (
"log"
"sync"
"sync/atomic"
)
var seq uint64 = 0
var generatorChan = make(chan uint64)
var requestChan = make(chan uint64)
func generator(genID int) {
for reqID := range requestChan {
// If you want to see a data race:
//seq = seq + 1
// Else:
s := atomic.AddUint64(&seq, 1)
log.Printf("Gen: %2d, from %3d", genID, reqID)
generatorChan <- s
}
}
func worker(id int, work *sync.WaitGroup) {
defer work.Done()
for i := 0; i < 5; i++ {
requestChan <- uint64(id)
log.Printf("\t\t\tWorker: %3d got %4d", id, <-generatorChan)
}
}
func main() {
log.SetFlags(log.Lmicroseconds)
const (
numGen = 20
numWorker = 200
)
var wg sync.WaitGroup
for i := 0; i < numGen; i++ {
go generator(i)
}
wg.Add(numWorker)
for i := 0; i < numWorker; i++ {
go worker(i, &wg)
}
wg.Wait()
close(requestChan)
}
Playground(但请注意,操场上的时间戳不会有用,并且调用runtime.MAXPROCS
可能无法执行任何操作)。进一步请注意,操场缓存结果,因此重新运行完全相同的程序将始终显示相同的输出,您需要进行一些小的更改或只是在您自己的机器上运行它。
使用log
与fmt
分析生成器的大小变化,因为前者提供并发保证,删除数据竞争,使输出看起来更好等等。
答案 1 :(得分:0)
通道提供了一种同时执行函数的机制 通过发送和接收指定元素的值进行通信 类型。未初始化频道的值为零。
可以使用内置的新的初始化通道值 function make,它采用通道类型和可选容量 作为参数:
make(chan int, 100)
容量(以元素数量)设置缓冲区的大小 这个频道。如果容量为零或不存在,则通道为 无缓冲和通信只有当发送者和发送者都成功时才会成功 接收器准备好了。否则,通道被缓冲 如果缓冲区未满,则通信成功而不会阻塞 (发送)或不空(接收)。零通道永远不会准备好 通信。
您使用无缓冲的频道限制频道通信。
例如,
generatorChan = make(chan uint64)
requestChan = make(chan uint64)