我正在尝试在Go中编写一个函数来监视一个频道并记录通过它发送的内容。
func monitorChannel(inChannel, outChannel reflect.Value, fid int64, cond *sync.Cond) {
for {
cond.L.Lock()
var toLog reflect.Value
var ok bool
for toLog, ok = inChannel.TryRecv() ; !toLog.IsValid(); { // while no value received
if !ok {
cond.L.Unlock()
return
}
cond.Wait()
}
outChannel.Send(toLog)
logMessage("a", "b", inChannel.Interface(), toLog.Interface(), fid)
cond.L.Unlock()
}
}
此函数应该从inChannel接收,记录发送的消息并通过outChannel发送。由于我希望能够记录双向通道,因此我要为每个要记录的通道调用此函数两次,交换inChannel和outChannel。锁是为了防止两个goroutine在彼此之间传递消息。 “fid”只是日志文件的id。
但是当我运行以下测试代码时,我遇到了僵局:
errsIn := make(chan int64)
errsOut := make(chan int64)
cond := sync.NewCond(&sync.Mutex{})
go monitorChannel(reflect.ValueOf(errsIn), reflect.ValueOf(errsOut), fid, cond)
go monitorChannel(reflect.ValueOf(errsOut), reflect.ValueOf(errsIn), fid, cond)
errsIn <- 1
if <-errsOut != 1 {
t.Fatal("lost value through channel send")
}
errsOut <- 1
if <-errsIn != 1 {
t.Fatal("lost value through channel send")
}
即使我没有关闭频道,似乎TryRecv在其第二个返回值上返回false。为什么是这样?我该怎么办呢?
我在Windows 8 64位上运行1.0.3。
修改
我后来发现TryRecv有一些令人困惑的行为,并设法使用reflect包和两个sync.Locker来创建该函数的通用版本。我仍然认为jnml的解决方案更优雅,但如果有人遇到类似TryRecv的问题,请查看函数中间的注释。
func passOnAndLog(in, out reflect.Value, l1, l2 sync.Locker) {
for {
l1.Lock()
val, ok := in.TryRecv()
for !val.IsValid() { // while nothing received
l1.Unlock()
time.Sleep(time.Nanosecond) // pausing current thread
l1.Lock()
val, ok = in.TryRecv()
}
// if val.IsValid() == true and ok == false ,the channel is closed
// if val.IsValid() == false and ok == false ,the channel is open but we received nothing
// if val.IsValid() == true and ok == true ,we received an actual value from the open channel
// if val.IsValid() == false and ok == true ,we have no idea what happened
if !ok {
return
}
l1.Unlock()
l2.Lock() // don't want the other thread to receive while I am sending
out.Send(val)
LogValue(val) // logging
l2.Unlock()
}
}
答案 0 :(得分:2)
基于反射的解决方案太让人费解,如果它是正确的或可行的话,我就会弄清楚,懒惰。 (我怀疑它不是,但只是凭直觉。)
我会以更简单但非通用的方式处理任务。让我们有一个通道,一些生产者将使用它来写入它,并将被一些消费者用来从中读取。
c := make(chan T, N)
可以使用小辅助函数监视此通道,例如:
func monitored(c chan T) chan T {
m := make(chan T, M)
go func() {
for v := range c {
m <- v
logMessage(v)
}
close(m)
}()
return m
}
现在足够了:
mc := monitored(c)
和
c
传递给制作人,但mc
传递给消费者。c
以免泄漏goroutines。警告:上面的代码根本没有经过测试。