需要运行几个 `func() bool` 函数,并获取第一个返回 `false` 的结果。

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

Need to run several func() bool and get the first false

问题

我正在尝试优化一个验证过程,该过程在一个IF语句中运行多个函数,所以我尝试使用Go的并发来解决这个问题,但是它不起作用。

我运行了以下代码,并尝试了很多不同的方法来实现通道关闭和等待组,但是我一直收到以下错误:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main.func1(0xc000074f60, 0x3, 0x3, 0xc000024180, 0xc0000241e0)
        /home/raphael/projects/go/go-herupaa/main.go:24 +0x9f
main.main()
        /home/raphael/projects/go/go-herupaa/main.go:25 +0xcb

goroutine 6 [chan send]:
main.a1(0xc0000241e0)
        /home/raphael/projects/go/go-herupaa/main.go:49 +0x72
created by main.main.func1
        /home/raphael/projects/go/go-herupaa/main.go:22 +0x71

goroutine 7 [chan send]:
main.a2(0xc0000241e0)
        /home/raphael/projects/go/go-herupaa/main.go:56 +0x72
created by main.main.func1
        /home/raphael/projects/go/go-herupaa/main.go:22 +0x71

goroutine 8 [chan send]:
main.a3(0xc0000241e0)
        /home/raphael/projects/go/go-herupaa/main.go:63 +0x72
created by main.main.func1
        /home/raphael/projects/go/go-herupaa/main.go:22 +0x71
exit status 2
package main

import (
	"fmt"
	"sync"
	"time"
)

var wg sync.WaitGroup

func main() {
	v := []func(chan<- bool){
		a1,
		a2,
		a3,
	}
	q := make(chan bool)
	c := make(chan bool)
	func(c chan bool) {
		for _, f := range v {
			wg.Add(1)
			go f(c)
		}
		q <- true
	}(c)
	p := receive(c, q)
	fmt.Println(p)
	wg.Wait()
}

func receive(c, q chan bool) bool {
	for {
		select {
		case v := <-c:
			if !v {
				fmt.Println("Received ", v)
				return false
			}
		case <-q:
			fmt.Println("Received Exit")
			return true
		}
	}
}

func a1(c chan<- bool) {
	defer wg.Done()
	time.Sleep(time.Millisecond * 2000)
	c <- true
	fmt.Println("Finish 1")
}

func a2(c chan<- bool) {
	defer wg.Done()
	time.Sleep(time.Millisecond * 2000)
	c <- true
	fmt.Println("Finish 2")
}

func a3(c chan<- bool) {
	defer wg.Done()
	time.Sleep(time.Millisecond * 2000)
	c <- true
	fmt.Println("Finish 3")
}

请注意,我只返回翻译好的部分,不包括代码部分。

英文:

I'm trying to optimize a validation process which runs several functions in a IF statement, so I'm trying to use go's concurrency to solve this, but its not working

I run the following code and tried a lot of different ways to implement channel close and wait groups, but I keep receiving the following error:

fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main.func1(0xc000074f60, 0x3, 0x3, 0xc000024180, 0xc0000241e0)
/home/raphael/projects/go/go-herupaa/main.go:24 +0x9f
main.main()
/home/raphael/projects/go/go-herupaa/main.go:25 +0xcb
goroutine 6 [chan send]:
main.a1(0xc0000241e0)
/home/raphael/projects/go/go-herupaa/main.go:49 +0x72
created by main.main.func1
/home/raphael/projects/go/go-herupaa/main.go:22 +0x71
goroutine 7 [chan send]:
main.a2(0xc0000241e0)
/home/raphael/projects/go/go-herupaa/main.go:56 +0x72
created by main.main.func1
/home/raphael/projects/go/go-herupaa/main.go:22 +0x71
goroutine 8 [chan send]:
main.a3(0xc0000241e0)
/home/raphael/projects/go/go-herupaa/main.go:63 +0x72
created by main.main.func1
/home/raphael/projects/go/go-herupaa/main.go:22 +0x71
exit status 2
package main
import (
&quot;fmt&quot;
&quot;sync&quot;
&quot;time&quot;
)
var wg sync.WaitGroup
func main() {
v := []func(chan&lt;- bool){
a1,
a2,
a3,
}
q := make(chan bool)
c := make(chan bool)
func(c chan bool) {
for _, f := range v {
wg.Add(1)
go f(c)
}
q &lt;- true
}(c)
p := receive(c, q)
fmt.Println(p)
wg.Wait()
}
func receive(c, q chan bool) bool {
for {
select {
case v := &lt;-c:
if !v {
fmt.Println(&quot;Received &quot;, v)
return false
}
case &lt;-q:
fmt.Println(&quot;Received Exit&quot;)
return true
}
}
}
func a1(c chan&lt;- bool) {
defer wg.Done()
time.Sleep(time.Millisecond * 2000)
c &lt;- true
fmt.Println(&quot;Finish 1&quot;)
}
func a2(c chan&lt;- bool) {
defer wg.Done()
time.Sleep(time.Millisecond * 2000)
c &lt;- true
fmt.Println(&quot;Finish 2&quot;)
}
func a3(c chan&lt;- bool) {
defer wg.Done()
time.Sleep(time.Millisecond * 2000)
c &lt;- true
fmt.Println(&quot;Finish 3&quot;)
}

答案1

得分: 4

你的第一个问题出在这段代码中的 q &lt;- true

func(c chan bool) {
        for _, f := range v {
            wg.Add(1)
            go f(c)
        }
        q &lt;- true
    }(c)

将其封装在 func() 块中没有任何作用(我猜你之前可能在某个goroutine中运行过这段代码)。按照当前的写法,它会启动三个goroutine,然后执行 q &lt;- true。由于没有任何地方在接收 q 的值,这会导致阻塞。

修复这个问题后,你会遇到下一个问题:

p := receive(c, q)
fmt.Println(p)
wg.Wait()

receivevq 中获取值;你可能期望它从 v 中获取三个值,然后从 q 中获取一个值,但实际上它很可能(更有可能)在从 v 中获取值之前就从 q 中获取到了值(这会导致 wg.Wait 处发生死锁)。

有几种解决方法;正如评论中Peter提到的,你可以使用带缓冲的通道(例如 c := make(chan bool, len(v)));向一个已满的通道发送数据不会阻塞。另外,我猜你的意图可能是这样的(playground):

func main() {
	v := []func(chan<- bool){
		a1,
		a2,
		a3,
	}
	q := make(chan bool)
	c := make(chan bool)

	for _, f := range v {
		wg.Add(1)
		go f(c)
	}
	// 当上述goroutine完成后,向q发送数据
	go func() {
		wg.Wait()
		q <- true
	}()

	p := receive(c, q)
	fmt.Println(p)
}

正如Peter指出的,你需要处理 receivev 上接收到 false 并提前退出的情况。可以通过使用带缓冲的通道来解决这个问题,或者你可以启动一个仅用于丢弃在通道上接收到的数据的goroutine,例如:

case v := <-c:
   if !v {
      fmt.Println("Received ", v)
      // 丢弃在v上接收到的其他数据,以允许其他goroutine完成
      go func() { for _ = range v {}}()
      return false
   }

如果采用这种方法,你需要确保最终关闭 v;实际上,你可以将其作为退出的信号,这样可以大大简化代码:

go func() {
		wg.Wait()
		close(c)
	}()

这样就完全不需要 q 了。你的 receive 可以使用 range 迭代通道,例如 for v := range c {...

英文:

Your first issue is with the q &lt;- true in:

func(c chan bool) {
for _, f := range v {
wg.Add(1)
go f(c)
}
q &lt;- true
}(c)

Enclosing this in a func() block does nothing (I suspect you were running this in a go routine at some point). As written it will start three go routines and then hit q &lt;- true. As nothing is receiving on q this will block.

Once you fix that you will hit the next issue:

p := receive(c, q)
fmt.Println(p)
wg.Wait()

receive grabs something from either v or q; you probably expect this to get three values from v and then one from q but its just as likely (probably more likely) to receive from q before v (and that leads to another deadlock at wg.Wait)..

There are a few ways of addressing this; as mentioned by Peter in the comments you could use buffered channels (e.g. c := make(chan bool, len(v))); sending to a blocked channel will not block unless the channel is full. Alternatively I suspect that what you were intending is something like this (playground):

func main() {
	v := []func(chan&lt;- bool){
		a1,
		a2,
		a3,
	}
	q := make(chan bool)
	c := make(chan bool)

	for _, f := range v {
		wg.Add(1)
		go f(c)
	}
	// When the above go routines have completed send to q
	go func() {
		wg.Wait()
		q &lt;- true
	}()

	p := receive(c, q)
	fmt.Println(p)
}

As Peter points out you do need to cater for the situation where receive receives false on v and exits early. This can be solved by using a buffered channel or you can spin up a go routine that just dumps data received on the channel i.e.:

case v := &lt;-c:
if !v {
fmt.Println(&quot;Received &quot;, v)
// Throw away anything else received on v to allow other go routines to complete
go func() { for _ = range v {}}()
return false
}

If you take this approach you need to ensure v is closed eventually; in fact you can use that as the signal to exit which can simplify things considerably:

go func() {
wg.Wait()
close(c)
}()

This removes the need for q completely. Your receive can range over the channel e.g. for v := range c {...).

答案2

得分: 1

这是对Brits的回答的补充和详细说明。

这个示例使用上下文取消来跳过写入操作,它总是排空c

你原始代码中的问题是你试图通过从其退出点停止流来退出,你实际上是在阻碍它。

正如Brits的回答所示,这不是停止流处理的正确方法,因为它会在管道中留下未管理的数据。

总体思路是在取消信号到达时停止向管道发送数据(关闭数据的输入源),并让管道完成对正在进行的数据的工作,完全排空它。

最后回答问题标题“需要运行多个func() bool并获取第一个false”

你可能想要实现流的缩减。捕获所有值,实现一个小逻辑尽快关闭你的缩减目标,但始终完全排空它。

package main

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"sync"
	"time"
)

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		s := make(chan os.Signal, 10)
		signal.Notify(s)
		<-s
		cancel()
	}()
	// 让我们模拟取消
	go func() {
		<-time.After(time.Millisecond * 200)
		cancel()
	}()
	v := []func(context.Context, chan<- bool){
		a1,
		a2,
		a3,
	}
	c := make(chan bool)

	var wg sync.WaitGroup
	for _, f := range v {
		wg.Add(1)
		f := f
		go func() {
			f(ctx, c)
			wg.Done()
		}()
	}
	// 当上述go例程完成时发送到q
	go func() {
		wg.Wait()
		close(c)
	}()

	firstRes := make(chan bool)
	go func() {
		var closed bool
		for v := range c {
			if !v && !closed {
				firstRes <- v
				closed = true
				close(firstRes)
			}
			fmt.Println("Received ", v)
		}
		if !closed {
			close(firstRes)
		}
	}()
	var wasFalsy bool
	for v := range firstRes {
		wasFalsy = !v
	}
	fmt.Println("was falsy ", wasFalsy)
}

func a1(ctx context.Context, c chan<- bool) {
	<-time.After(time.Millisecond * 2000)
	select {
	case c <- !true:
		fmt.Println("Finish 1")
	case <-ctx.Done():
		fmt.Println("Cancelled 1")
	}
}

func a2(ctx context.Context, c chan<- bool) {
	<-time.After(time.Millisecond * 2000)
	select {
	case c <- true:
		fmt.Println("Finish 2")
	case <-ctx.Done():
		fmt.Println("Cancelled 2")
	}
}

func a3(ctx context.Context, c chan<- bool) {
	<-time.After(time.Millisecond * 2000)
	select {
	case c <- true:
		fmt.Println("Finish 3")
	case <-ctx.Done():
		fmt.Println("Cancelled 3")
	}
}
英文:

this adds up on great and detailed answer of Brits.

This example uses context cancellation to skip writes, It always drains c.

The problem in your original code is that you try to exit by stopping the stream from its exit point, you are litterally obstructing it.

As demonstrated in Brits answer it is not the right way to stop processing of the stream because it leaves unmanaged data in the pipe.

The general idea is to stop sending data into the pipe (close the input source of data) when the cancellation signal arrives and let the pipeline finish its job over the on going data by fully draining it.

package main

import (
	&quot;context&quot;
	&quot;fmt&quot;
	&quot;os&quot;
	&quot;os/signal&quot;
	&quot;sync&quot;
	&quot;time&quot;
)

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		s := make(chan os.Signal, 10)
		signal.Notify(s)
		&lt;-s
		cancel()
	}()
	// let us simulate cancellation
	go func() {
		&lt;-time.After(time.Millisecond * 200)
		cancel()
	}()
	v := []func(context.Context, chan&lt;- bool){
		a1,
		a2,
		a3,
	}
	c := make(chan bool)

	var wg sync.WaitGroup
	for _, f := range v {
		wg.Add(1)
		f := f
		go func() {
			f(ctx, c)
			wg.Done()
		}()
	}
	go func() {
		wg.Wait()
		close(c)
	}()

	for v := range c {
		fmt.Println(&quot;Received &quot;, v)
	}
}

func a1(ctx context.Context, c chan&lt;- bool) {
	&lt;-time.After(time.Millisecond * 1000)
	select {
	case c &lt;- true:
		fmt.Println(&quot;Finish 1&quot;)
	case &lt;-ctx.Done():
		fmt.Println(&quot;Cancelled 1&quot;)
	}
}

func a2(ctx context.Context, c chan&lt;- bool) {
	&lt;-time.After(time.Millisecond * 1000)
	select {
	case c &lt;- true:
		fmt.Println(&quot;Finish 2&quot;)
	case &lt;-ctx.Done():
		fmt.Println(&quot;Cancelled 2&quot;)
	}
}

func a3(ctx context.Context, c chan&lt;- bool) {
	&lt;-time.After(time.Millisecond * 1000)
	select {
	case c &lt;- true:
		fmt.Println(&quot;Finish 3&quot;)
	case &lt;-ctx.Done():
		fmt.Println(&quot;Cancelled 3&quot;)
	}
}

Finally to answer to the question title Need to run several func() bool and get the first false

You might want to implement stream reduction. capture all values, implement a small logic to close your reduction target asap. but always keep draining it completely.

package main

import (
	&quot;context&quot;
	&quot;fmt&quot;
	&quot;os&quot;
	&quot;os/signal&quot;
	&quot;sync&quot;
	&quot;time&quot;
)

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		s := make(chan os.Signal, 10)
		signal.Notify(s)
		&lt;-s
		cancel()
	}()
	// let us simulate cancellation
	go func() {
		&lt;-time.After(time.Millisecond * 200)
		cancel()
	}()
	v := []func(context.Context, chan&lt;- bool){
		a1,
		a2,
		a3,
	}
	c := make(chan bool)

	var wg sync.WaitGroup
	for _, f := range v {
		wg.Add(1)
		f := f
		go func() {
			f(ctx, c)
			wg.Done()
		}()
	}
	// When the above go routines have completed send to q
	go func() {
		wg.Wait()
		close(c)
	}()

	firstRes := make(chan bool)
	go func() {
		var closed bool
		for v := range c {
			if !v &amp;&amp; !closed {
				firstRes &lt;- v
				closed = true
				close(firstRes)
			}
			fmt.Println(&quot;Received &quot;, v)
		}
		if !closed {
			close(firstRes)
		}
	}()
	var wasFalsy bool
	for v := range firstRes {
		wasFalsy = !v
	}
	fmt.Println(&quot;was falsy &quot;, wasFalsy)
}

func a1(ctx context.Context, c chan&lt;- bool) {
	&lt;-time.After(time.Millisecond * 2000)
	select {
	case c &lt;- !true:
		fmt.Println(&quot;Finish 1&quot;)
	case &lt;-ctx.Done():
		fmt.Println(&quot;Cancelled 1&quot;)
	}
}

func a2(ctx context.Context, c chan&lt;- bool) {
	&lt;-time.After(time.Millisecond * 2000)
	select {
	case c &lt;- true:
		fmt.Println(&quot;Finish 2&quot;)
	case &lt;-ctx.Done():
		fmt.Println(&quot;Cancelled 2&quot;)
	}
}

func a3(ctx context.Context, c chan&lt;- bool) {
	&lt;-time.After(time.Millisecond * 2000)
	select {
	case c &lt;- true:
		fmt.Println(&quot;Finish 3&quot;)
	case &lt;-ctx.Done():
		fmt.Println(&quot;Cancelled 3&quot;)
	}
}

huangapple
  • 本文由 发表于 2021年8月3日 11:27:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/68630018.html
匿名

发表评论

匿名网友

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

确定