一旦通道中有新值,就可以终止一个 goroutine。

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

Killing a goroutine once theres a new value in a channel

问题

对于通道中的每个新值,都会生成一个goroutine。当通道中有新值时,我希望启动一个新的goroutine,并终止旧的goroutine。我怀疑我的代码会终止新的goroutine并保持第一个goroutine处于活动状态。我该如何修复这个问题?

func Start() {
	go func() {
		quit := make(chan bool, 1)
		for nbp := range poll() {
			quit <- true
			go create(nbp, quit)
		}
	}()
}

func create(nbp map[string]string, quit chan bool) {
	for {
		select {
		case <-quit:
			fmt.Println("quiting this goroutine!")
			return
		default:
			for k, v := range nbp {
			...
            }
			time.Sleep(3 * time.Second)
		}
	}
}
英文:

For each new value in a channel, a goroutine spawns. When there's a new value in a channel, I want a new goroutine to start, and the old one to be killed. I suspect that my code is killing the new goroutine and keeping the first one alive. how can I fix this?

func Start() {
	go func() {
		quit := make(chan bool, 1)
		for nbp := range poll() {
			quit &lt;- true
			go create(nbp, quit)
		}
	}()
}

func create(nbp map[string]string, , quit chan bool) {
	for {
		select {
		case &lt;-quit:
			fmt.Println(&quot;quiting this goroutine!&quot;)
			return
		default:
			for k, v := range nbp {
			...
            }
			time.Sleep(3 * time.Second)
		}
	}
}

答案1

得分: 1

看着你提供的代码,你想在每次poll提供新数据时启动一个新的例程。这很容易实现:

func Start() {
    for nbp := range poll() {
        go create(nbp)
    }
}

func create(nbp map[string]string) {
    // 在这里执行操作
}

好的,但是使用这种方法,你可能会同时生成大量的例程。你使用的quit通道表明你只想在前一个例程完成后才生成一个新的例程。同样,这很容易实现:

func Start() {
    ch := make(chan struct{}) // 根据规范,struct{}的大小为0字节
    defer close(ch) // 完成后关闭通道
    for nbp := range poll() {
        go create(nbp, ch)
        ch <- struct{}{} // 阻塞,直到例程从通道中读取
    }
}

func create(nbp map[string]string, ch <-chan struct{}) {
    for k, v := range nbp {
        // ... 执行操作
    }
    time.Sleep(3 * time.Second)
    <-ch // 读取以解除Start的阻塞
}

很好,但现在我们只是按顺序执行操作,并使用一个没有太多意义的通道来实现。为什么不简单地这样做:

func Start() {
    ch := make(chan map[string]string) // 通道中的数据
    defer close(ch) // 完成后关闭通道
    go create(ch) // 启动从通道中读取数据的例程
    for nbp := range poll() {
        ch <- nbp // 将数据放入通道,阻塞直到例程从通道中读取
        time.Sleep(3 * time.Second) // 在这里休眠
    }
}

func create(nbp <-chan map[string]string) {
    for nbp := range ch { // 从通道中读取
        // 顺序处理数据
        for k, v := range nbp {
            // ... 执行操作
        }
    }
}

我将休眠移到了写入通道的循环中,因为在第一次迭代中,例程将立即从通道中读取(它还没有做任何事情),并解除poll()循环的阻塞。这将导致两次快速连续调用poll()。将休眠移到例程外确保在第一次和第二次调用之间至少有3秒的间隔。之后,行为几乎完全相同。我说几乎完全相同,是因为每次释放与create例程相关的资源并立即调度新例程时,你不会像之前那样频繁地_“打扰”_运行时和调度器。

英文:

So looking at the code you provided, you're wanting to spin up a new routine every time poll provides new data. That's trivial to do:

func Start() {
    for nbp := range poll() {
        go create(nbp)
    }
}

func create(nbp map[string]string) {
    // do stuff here
}

OK, but with this approach, you might be spawning a ton of routines at the same time. The quit channel you're using suggests you only want to spawn a new routine after the previous one has finished. Again, this is trivial to achieve:

func Start() {
    ch := make(chan struct{}) // struct{} is 0 bytes in size as per spec
    defer close(ch) // close channel when done
    for nbp := range poll() {
        go create(nbp, ch)
        ch &lt;- struct{}{} // blocks until the routine has read from the channel
    }
}

func create(nbp map[string]string, ch &lt;-chan struct{}) {
    for k, v := range nbp {
        // ... do stuff
    }
    time.Sleep(3 * time.Second)
    &lt;-ch // read to unblock Start
}

Great, but now we're just doing things in sequence, and using a pretty pointless channel to do so... Why not simply do this:

func Start() {
    ch := make(chan map[string]string) // data in the channel
    defer close(ch) // close channel when done
    go create(ch) // start the routine reading data from the channel
    for nbp := range poll() {
        ch &lt;- nbp // put data on channel, blocks until routine reads from the channel
        time.Sleep(3 * time.Second) // sleep here
    }
}

func create(nbp &lt;-chan map[string]string) {
    for nbp := range ch { // read from channel
        // and process sequentially
        for k, v := range nbp {
            // ... do stuff
        }
    }
}

The reason why I moved the sleep to the loop that writes to the channel is because on the first iteration, the routine will immediately read from the channel (it's not doing anything yet), and unblock the poll() loop. That'll result in 2 calls to poll() in quick succession. Moving the sleep out of the routine ensures that you'll have at least 3 seconds in between the first and second call. After that, the behaviour is pretty much identical. I say pretty much, because you're not "bothering" the runtime and scheduler as much in order to release resources associated with the create routine every time, and scheduling a new routine immediately after.

答案2

得分: 0

一个简单的解决方法如下:

package main

func Start() {
	go func() {
		var i int
		quit := make(chan bool)
		for nbp := range poll() {
			if i > 0 {
				quit <- true
			}
			i++
			go create(nbp, quit)
		}
	}()
}

func create(nbp map[string]string, quit chan bool) {
	for {
		select {
		case <-quit:
			fmt.Println("退出这个goroutine!")
			return
		default:
			for k, v := range nbp {
			//...
			}
			time.Sleep(3 * time.Second)
		}
	}
}
英文:

A simple workaround looks like this

package main

func Start() {
	go func() {
		var i int
		quit := make(chan bool)
		for nbp := range poll() {
			if i &gt; 0 {
				quit &lt;- true
			}
			i++
			go create(nbp, quit)
		}
	}()
}

func create(nbp map[string]string, quit chan bool) {
	for {
		select {
		case &lt;-quit:
			fmt.Println(&quot;quiting this goroutine!&quot;)
			return
		default:
			for k, v := range nbp {
			//...
			}
			time.Sleep(3 * time.Second)
		}
	}
}

huangapple
  • 本文由 发表于 2021年6月29日 20:49:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/68178997.html
匿名

发表评论

匿名网友

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

确定