尽管通道是打开的,但TryRecv返回通道关闭

huangapple go评论89阅读模式
英文:

TryRecv returning channel closed despite beeing open

问题

我正在尝试在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(); {
            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位上运行go 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() {
            l1.Unlock()
            time.Sleep(time.Nanosecond)
            l1.Lock()
            val, ok = in.TryRecv()
        }
        if !ok {
            return
        }
        l1.Unlock()
        l2.Lock()
        out.Send(val)
        LogValue(val)
        l2.Unlock()
    }
}
英文:

I am trying to write a function in Go which monitors a channel and logs what is sent through it.

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(&quot;a&quot;, &quot;b&quot;, inChannel.Interface(), toLog.Interface(), fid)
	cond.L.Unlock()
}

}

This function is supposed to receive from inChannel, log the message sent and send it through outChannel. Since I want to be able to log bi-directional channels, I call this function twice for each channel I want to log, swapping inChannel and outChannel. The lock is to keep the two goroutines from passing messages between each other. "fid" is just the id of the log file.

But when I run the following test code, I get a deadlock :

errsIn := make(chan int64)
errsOut := make(chan int64)
cond := sync.NewCond(&amp;sync.Mutex{})
go monitorChannel(reflect.ValueOf(errsIn), reflect.ValueOf(errsOut), fid, cond)
go monitorChannel(reflect.ValueOf(errsOut), reflect.ValueOf(errsIn), fid,  cond)
errsIn &lt;- 1
if &lt;-errsOut != 1 {
	t.Fatal(&quot;lost value through channel send&quot;)
}
errsOut &lt;- 1
if &lt;-errsIn != 1 {
	t.Fatal(&quot;lost value through channel send&quot;)
}

It seems as if TryRecv is returning false on its second return value even though I haven't closed the channel. Why is this? What should I do about it?

I am running go 1.0.3 on Windows 8 64 bit.

EDIT

I later discovered that TryRecv has a somewhat confusing behaviour and managed to make a generalized version of the function using the reflect package and two sync.Locker's. I still think that jnml's solution is more elegant, but if anyone has experienced similar problems with TryRecv, take a look at the comment in the middle of the function.

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&#39;t want the other thread to receive while I am sending
		out.Send(val)
		LogValue(val) // logging

		l2.Unlock()
	}
}

答案1

得分: 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以避免泄漏goroutine。

警告:上述代码未经过任何测试。

英文:

The reflection based solution is too convoluted for me to figure out, being lazy, if it is correct and or feasible at all. (I suspect it is not, but only by intuition.)

I would approach the task in a simpler, although non-generic way. Let's have a channel which will be used by some producer(s) to write to it and will be used by some consumer(s) to read from it.

c := make(chan T, N)

It's possible to monitor this channel using a small helper function, like for example:

func monitored(c chan T) chan T {
        m := make(chan T, M)
        go func() {
                for v := range c {
                        m &lt;- v
                        logMessage(v)
                }
                close(m)
        }()
        return m
}

Now it is enough to:

mc := monitored(c)

and

  • Pass c to producers(s), but mc to consumers(s).
  • Close c when done to not leak goroutines.

Warning: Above code was not tested at all.

huangapple
  • 本文由 发表于 2013年3月10日 15:59:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/15320167.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定