致命错误:所有的goroutine都处于休眠状态 – 死锁 | Go Routine

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

fatal error: all goroutines are asleep - deadlock | Go Routine

问题

问题在于goOne和goTwo函数分别向通道ch1和ch2发送值,但是在主函数中没有相应的接收器来接收这些值。这意味着通道被阻塞,程序无法继续执行。因此,主函数中的select语句无法从通道中读取值,所以它总是执行默认情况。

为了解决这个问题,在wg.Wait()之后,我在主函数中添加了<-ch1或<-ch2来接收发送到通道的值并解除阻塞。

英文:

The problem is that both the goOne and goTwo functions are sending values to the channels ch1 and ch2 respectively, but there is no corresponding receiver for these values in the main function. This means that the channels are blocked and the program is unable to proceed. As a result, the select statement in the main function is unable to read from the channels, so it always executes the default case.

package main

import (
    &quot;fmt&quot;
    &quot;sync&quot;
)

func main() {
	var wg sync.WaitGroup
	ch1 := make(chan string)
	ch2 := make(chan string)

	wg.Add(2)
	go goOne(&amp;wg, ch1)
	go goTwo(&amp;wg, ch2)

	select {
	case &lt;-ch1:
		fmt.Println(&lt;-ch1)
		close(ch1)

	case &lt;-ch2:
		fmt.Println(&lt;-ch2)
		close(ch2)

	default:
		fmt.Println(&quot;Default Case&quot;)
	}
	wg.Wait()

}

func goTwo(wg *sync.WaitGroup, ch2 chan string) {
	ch2 &lt;- &quot;Channel 2&quot;
	wg.Done()
}

func goOne(wg *sync.WaitGroup, ch1 chan string) {
	ch1 &lt;- &quot;Channel 1&quot;
	wg.Done()
}

Output:

Default Case
fatal error: all goroutines are asleep - deadlock!

goroutine 1 \[semacquire\]:
sync.runtime_Semacquire(0xc000108270?)
/usr/local/go/src/runtime/sema.go:62 +0x25
sync.(\*WaitGroup).Wait(0x4b9778?)
/usr/local/go/src/sync/waitgroup.go:139 +0x52
main.main()
/home/nidhey/Documents/Go_Learning/goroutines/select.go:29 +0x2af

goroutine 6 \[chan send\]:
main.goOne(0x0?, 0x0?)
/home/nidhey/Documents/Go_Learning/goroutines/select.go:39 +0x28
created by main.main
/home/nidhey/Documents/Go_Learning/goroutines/select.go:14 +0xc5

goroutine 7 \[chan send\]:
main.goTwo(0x0?, 0x0?)
/home/nidhey/Documents/Go_Learning/goroutines/select.go:33 +0x28
created by main.main
/home/nidhey/Documents/Go_Learning/goroutines/select.go:15 +0x119\```

I'm looking for a different pattern such as select to handle the case when the channels are blocked.

To fix the issue, I've added a &lt;-ch1 or &lt;-ch2 in the main function after wg.Wait() to receive the values sent to the channels and unblock them

答案1

得分: 1

这是你要翻译的内容:

不太清楚你想要做什么。如果你想等待两个goroutine完成它们的工作,并将工作结果放入通道中,你不需要使用WaitGroup(因为它不会被触发)。

你可以这样做。

package main

import (
	"fmt"
	"time"
)

func main() {
	ch1 := make(chan string)
	ch2 := make(chan string)

	go goOne(ch1)
	go goTwo(ch2)

	for {
		select {
		case v := <-ch1:
			fmt.Println("Done ch1:", v)
			ch1 = nil
		case v := <-ch2:
			fmt.Println("Done ch2:", v)
			ch2 = nil
		case <-time.After(time.Second):
			fmt.Println("I didn't get result so lets skip it!")
			ch1, ch2 = nil, nil
		}
		if ch1 == nil && ch2 == nil {
			break
		}
	}
}

func goTwo(ch chan string) {
	ch <- "Channel 2"
}

func goOne(_ chan string) {
	//ch1 <- "Channel 1"
}

更新:

假设我们有两个API端点,API1和API2,它们返回相同的数据,但托管在不同的地区。所以我想做的是,在两个不同的函数(goroutines)中对两个API进行调用,一旦任何一个API发送响应,我就想处理接收到的数据。所以我使用select块来检查哪个API首先获取到数据。

package main

import (
	"context"
	"fmt"
	"math/rand"
	"time"
)

func main() {

	regions := []string{
		"Europe",
		"America",
		"Asia",
	}

	// Just for different results for each run
	rand.Seed(time.Now().UnixNano())
	rand.Shuffle(len(regions), func(i, j int) { regions[i], regions[j] = regions[j], regions[i] })

	output := make(chan string)

	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	for i, region := range regions {
		go func(ctx context.Context, region string, output chan<- string, i int) {

			// Do call with context
			// If context will be cancelled just ignore it here

			timeout := time.Duration(i) * time.Second
			fmt.Printf("Call %s (with timeout %s)\n", region, timeout)
			time.Sleep(timeout) // Simulate request timeout

			select {
			case <-ctx.Done():
				fmt.Println("Cancel by context:", region)
			case output <- fmt.Sprintf("Answer from `%s`", region):
				fmt.Println("Done:", region)
			}
		}(ctx, region, output, i)
	}

	select {
	case v := <-output:
		cancel() // Cancel all requests in progress (if possible)
		// Do not close output chan to avoid panics: When the channel is no longer used, it will be garbage collected.
		fmt.Println("Result:", v)
	case <-ctx.Done():
		fmt.Println("Timeout by context done")
	}

	fmt.Println("There is we already have result or timeout, but wait a little bit to check all is okay")
	time.Sleep(5 * time.Second)
}
英文:

It's not entirely clear what you want to do. If you want to wait for both goroutines to complete their work and get the result of their work into the channel, you don't need a weight group (because it won't be reached).

You can do something like this.

package main
import (
&quot;fmt&quot;
&quot;time&quot;
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go goOne(ch1)
go goTwo(ch2)
for {
select {
case v := &lt;-ch1:
fmt.Println(&quot;Done ch1:&quot;, v)
ch1 = nil
case v := &lt;-ch2:
fmt.Println(&quot;Done ch2:&quot;, v)
ch2 = nil
case &lt;-time.After(time.Second):
fmt.Println(&quot;I didn&#39;t get result so lets skip it!&quot;)
ch1, ch2 = nil, nil
}
if ch1 == nil &amp;&amp; ch2 == nil {
break
}
}
}
func goTwo(ch chan string) {
ch &lt;- &quot;Channel 2&quot;
}
func goOne(_ chan string) {
//ch1 &lt;- &quot;Channel 1&quot;
}

UPD:

> Imagine if we are having two api end points, API1 & API2 which are returning same data but are hosted on different regions. So what I want to do, I need to make API calls for both apis in two different function ie goroutines and as soon as any one api sends us response back, I want to process the data received. So for that Im check whcih api is fetching data first using select block.

package main
import (
&quot;context&quot;
&quot;fmt&quot;
&quot;math/rand&quot;
&quot;time&quot;
)
func main() {
regions := []string{
&quot;Europe&quot;,
&quot;America&quot;,
&quot;Asia&quot;,
}
// Just for different results for each run
rand.Seed(time.Now().UnixNano())
rand.Shuffle(len(regions), func(i, j int) { regions[i], regions[j] = regions[j], regions[i] })
output := make(chan string)
ctx, cancel := context.WithTimeout(context.Background(), 2 * time.Second)
for i, region := range regions {
go func(ctx context.Context, region string, output chan &lt;- string, i int) {
// Do call with context
// If context will be cancelled just ignore it here
timeout := time.Duration(i)*time.Second
fmt.Printf(&quot;Call %s (with timeout %s)\n&quot;, region, timeout)
time.Sleep(timeout) // Simulate request timeout
select {
case &lt;-ctx.Done():
fmt.Println(&quot;Cancel by context:&quot;, region)
case output &lt;- fmt.Sprintf(&quot;Answer from `%s`&quot;, region):
fmt.Println(&quot;Done:&quot;, region)
}
}(ctx, region, output, i)
}
select {
case v := &lt;-output:
cancel() // Cancel all requests in progress (if possible)
// Do not close output chan to avoid panics: When the channel is no longer used, it will be garbage collected.
fmt.Println(&quot;Result:&quot;, v)
case &lt;-ctx.Done():
fmt.Println(&quot;Timeout by context done&quot;)
}
fmt.Println(&quot;There is we already have result or timeout, but wait a little bit to check all is okay&quot;)
time.Sleep(5*time.Second)
}

答案2

得分: 0

首先,你的代码存在竞态条件,因为在进入select语句时,你的通道发布goroutine可能还没有启动,所以它会立即执行到default分支。

但是假设你解决了这个问题(例如使用另一种形式的信号量),你将面临下一个问题 - 你的select语句将获取chan1或chan2的消息,然后在方法的底部等待,但由于它不再在select语句中,你的某个消息将无法到达,你将永远等待下去。

你需要使用循环(在这种情况下是两次)或者在它们自己的goroutine中运行的接收器来接收两个通道的消息并完成等待组。

但无论如何 - 正如其他人所问 - 你想要实现什么目标?

英文:

Firstly you have a race condition in that your channel publishing goroutines will probably not have been started by the time you enter the select statement, and it will immediately fall through to the default.

But assuming you resolve this (e.g. with another form of semaphore) you're on to the next issue - your select statement will either get chan1 or chan2 message, then wait at the bottom of the method, but since it is no longer in the select statement, one of your messages won't have arrived and you'll be waiting forever.

You'll need either a loop (twice in this case) or a receiver for both channels running in their own goroutines to pick up both messages and complete the waitgroup.

But in any case - as others have queried - what are you trying to achieve?

答案3

得分: 0

你可以尝试像这样迭代一个单通道(假设两个通道返回相同类型的数据),然后在主方法中计算完成的任务数量。一旦所有任务完成,就关闭通道。示例代码如下:

package main

import (
	"fmt"
)

func main() {
	ch := make(chan string)

	go goOne(ch)
	go goTwo(ch)
	doneCount := 0

	for v := range ch {
		fmt.Println(v)
		doneCount++
		if doneCount == 2 {
			close(ch)
		}
	}
}

func goTwo(ch chan string) {
	ch <- "Channel 2"
}

func goOne(ch chan string) {
	ch <- "Channel 1"
}
英文:

You can try something like iterating over a single channel (assuming both channels return the same type of data) and then count in the main method how many tasks are completed. Then close the channel once all the tasks are done. Example:

package main

import (
	&quot;fmt&quot;
)

func main() {
	ch := make(chan string)

	go goOne(ch)
	go goTwo(ch)
	doneCount := 0

	for v := range ch {
		fmt.Println(v)
		doneCount++
		if doneCount == 2 {
			close(ch)
		}
	}
}

func goTwo(ch chan string) {
	ch &lt;- &quot;Channel 2&quot;
}

func goOne(ch chan string) {
	ch &lt;- &quot;Channel 1&quot;
}

huangapple
  • 本文由 发表于 2023年1月25日 15:37:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/75230886.html
匿名

发表评论

匿名网友

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

确定