当数据通道关闭时,Goroutines不会退出。

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

Goroutines not exiting when data channel is closed

问题

我正在尝试跟随在http://blog.golang.org/pipelines/bounded.go上发布的有界goroutine示例。我遇到的问题是,如果有更多的工作线程被启动,而工作量不足,额外的工作线程永远不会被取消。其他的部分似乎都正常工作,值被计算和记录,但是当我关闭groups通道时,工作线程就会在range语句处挂起。

我猜我不明白的是(无论是我的代码还是示例代码),工作线程如何知道没有更多的工作要做,他们应该退出?

更新

一个可工作(即不工作)的示例已发布在http://play.golang.org/p/T7zBCYLECp。它显示了工作线程的死锁,因为它们都处于休眠状态,没有工作要做。我困惑的是,我认为示例代码会有同样的问题。

这是我目前正在使用的代码:

// 创建一个工作池来执行一系列计算
func computeAll() error {
	done := make(chan struct{})
	defer close(done)

	groups, errc := findGroups(done)

	// 启动固定数量的goroutine来进行调度
	const numComputers = 20
	c := make(chan result)
	var wg sync.WaitGroup
	wg.Add(numComputers)
	for i := 0; i < numComputers; i++ {
		go func() {
			compute(done, groups, c)
			wg.Done()
		}()
	}

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

	// 记录计算结果
	for r := range c {
		// 记录结果
	}

	if err := <-errc; err != nil {
		return err
	}

	return nil
}

这是填充通道数据的代码:

// 获取需要计算的数据组
func findGroups(done <-chan struct{}) (<-chan model, <-chan error) {
	groups := make(chan model)
	errc := make(chan error, 1)
	go func() {
		// 在find返回后关闭groups通道
		defer close(groups)

		group, err := //... 获取组的代码 ...
		if err == nil {
			// 将组添加到通道中
			select {
			case groups <- group:
			}
		}
	}()

	return groups, errc
}

这是读取通道并进行计算的代码:

// 计算数据组的结果
func compute(done <-chan struct{}, groups <-chan model, c chan<- result) {
	for group := range groups {
		value := compute(group)

		select {
		case c <- result{value}:
		case <-done:
			return
		}
	}
}
英文:

I'm trying to follow along the bounded goroutine example that is posted at http://blog.golang.org/pipelines/bounded.go. The problem that I'm having is that if there are more workers spun up then the amount of work to do, the extra workers never get cancelled. Everything else seems to work, the values get computed and logged, but when I close the groups channel, the workers just hang at the range statement.

I guess what I don't understand (in both my code and the example code) is how do the workers know when there is no more work to do and that they should exit?

Update

A working (i.e. non-working) example is posted at http://play.golang.org/p/T7zBCYLECp. It shows the deadlock on the workers since they are all asleep and there is no work to do. What I'm confused about is that I think the example code would have the same problem.

Here is the code that I'm currently using:

// Creates a pool of workers to do a bunch of computations
func computeAll() error {
	done := make(chan struct{})
	defer close(done)

	groups, errc := findGroups(done)

	// start a fixed number of goroutines to schedule with
	const numComputers = 20    	
    c := make(chan result)
	var wg sync.WaitGroup
	wg.Add(numComputers)
	for i := 0; i &lt; numComputers; i++ {
		go func() {
			compute(done, groups, c)
			wg.Done()
		}()
	}

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

	// log the results of the computation
	for r := range c { // log the results }
    
    if err := &lt;-errc; err != nil {
	    return err
    }

    return nil
}

Here is the code that fills up the channel with data:

// Retrieves the groups of data the must be computed
func findGroups(done &lt;-chan struct{}) (&lt;-chan model, &lt;-chan error) {
	groups := make(chan model)
	errc := make(chan error, 1)
	go func() {
		// close the groups channel after find returns
		defer close(groups)

        group, err := //... code to get the group ...
        if err == nil {
	        // add the group to the channel
			select {
			    case groups &lt;- group:
			}
		}
	}()

	return groups, errc
}

And here is the code that reads the channel to do the computations.

// Computes the results for the groups of data
func compute(done &lt;-chan struct{}, groups &lt;-chan model, c chan&lt;- result) {
	for group := range groups {
        value := compute(group)

		select {
		case c &lt;- result{value}:
		case &lt;-done:
			return
		}
	}
}

答案1

得分: 2

因为你试图从errc中读取数据,除非有错误,否则它是空的。

//编辑

如果没有错误,computeAll()将始终在<- errc上阻塞,另一种方法是使用类似以下的方式:

func computeAll() (err error) {
    .........
    select {
    case err = <-errc:
    default: //不阻塞
    }
    return
}
英文:

Because you're trying to read from errc and it's empty unless there's an error.

//edit

computeAll() will always block on &lt;- errc if there are no errors, another approach is to use something like:

func computeAll() (err error) {
	.........
	select {
	case err = &lt;-errc:
    default: //don&#39;t block
	}
	return
}

答案2

得分: 1

尝试按照OneOfOne的建议关闭errc。

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

// 记录计算结果
for r := range c {
    // 记录结果
}

for err := range errc {
    if err != nil {
        return err
    }
}
英文:

Try to close the errc as OneOfOne says

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

// log the results of the computation
for r := range c { // log the results }

if err := range errc {
   if err != nil {
    return err
   }
}

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

发表评论

匿名网友

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

确定