如何停止 goroutine

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

How to stop goroutine

问题

我有一个goroutine调用一个函数,并且使用一个特殊的参数来启动或停止这个goroutine。我的问题是,这段代码从来没有停止我的goroutine,它每次都创建一个新的任务。

quit := make(chan bool)
run := make(chan bool)

go func() {
    for {
        select {
        case <-quit:
            close(run)
        case <-run:
            myFunc(c)
        default:
        }
    }
}()

if x == true {
    quit <- true
} else {
    run <- true
}

我该如何停止我的routine?

英文:

I have a goroutine that calls a function and with a special parameter i want to start or stop this goroutine. My problem is that this code never stops my goroutine, it creates everytime a new job.

quit := make(chan bool)
run := make(chan bool)

	go func() {
		for {
			select {
			case &lt;-quit:
				close(run)
			case &lt;-run:
				myFunc(c)
			default:
			}
		}
	}()

	if x == true {
		quit &lt;- true
	} else {
		run &lt;- true
	}

How do I stop my routine?

答案1

得分: 1

当你关闭run通道时,case <-run将始终触发:监听关闭的通道会立即返回零值。

如果你想停止goroutine,在接收到<-quit信号后应该返回。

另外,你的default:子句使for循环主动工作,你应该将其删除(你仍然会监听两个通道)。

英文:

When you close the run channel, case &lt;-run will always trigger: listening on a closed channel returns a zero value immediately.

if you want to stop the goroutine, you should return after you get the &lt;-quit signal.

As a side note, your default: clause makes the for loop actively work, you should get rid of it (you will still be listening on both channels)

答案2

得分: 1

以下是一个如何实现这样一个信号系统的独立注释可运行版本的示例代码:

package main

import (
	"time"
	"log"
)

func main() {
	statusChannel := make(chan bool)
	go applicationLoop(statusChannel)

	// 用于测试的合理随机结果
	if time.Now().Unix() % 2 == 0 {
		statusChannel <- true
	} else {
		statusChannel <- false
	}

	for {
		// 用于测试的忙碌循环
		time.Sleep(1000)
	}
}

func applicationLoop(statusChannel chan bool) {
	defer close(statusChannel)
	for {
		log.Printf("等待信号...\n")
		shouldContinue := <-statusChannel
		if !shouldContinue {
			log.Print("收到 false,退出...\n")
			break
		}
		// 在这里运行你的代码
		// 由于通道没有缓冲区,你应该使用第二个通道返回结果
		log.Print("正在工作...\n")
	}
}

请注意,当 statusChannel 不在监听值时向其发送值会导致示例代码出错。你可以使用带缓冲区的通道或者一个在 goroutine 回到监听信号状态时向 main 发送信号的通道来解决这个问题。

英文:

Here's an isolated commented runable version of how such a signaling system might be implemented.

package main

import (
	&quot;time&quot;
	&quot;log&quot;
)

func main() {
	statusChannel := make(chan bool)
	go applicationLoop(statusChannel)

	// reasonably random outcome for testing
	if time.Now().Unix() % 2 == 0 {
		statusChannel&lt;-true
	} else {
		statusChannel&lt;-false
	}

	for {
		// busy loop for testing
		time.Sleep(1000)
	}
}

func applicationLoop(statusChannel chan bool) {
	defer close(statusChannel)
	for {
		log.Printf(&quot;waiting for signal...\n&quot;)
		shouldContinue := &lt;-statusChannel
		if !shouldContinue {
			log.Print(&quot;received false, breaking...\n&quot;)
			break
		}
		// run your code here
		// you should use a second channel to return results, as the channel is not buffered
		log.Print(&quot;working...\n&quot;)
	}
}

Do note that sending a value to statusChannel while is is not listening for a value will make the example blow up in your face. Either use a buffered channel or a channel that signals back to main when the goroutine is back to listening for a signal.

答案3

得分: 1

这个问题有两个部分。

首先,我们需要以某种方式停止子goroutine,即使父goroutine停止,所有子goroutine也应该收到通知并停止——这是一个向下传递但不向上传递的停止信号层次结构。

另一方面,父goroutine需要等待其子goroutine完成。否则,在某些goroutine正确完成之前,我们可能会从goroutine返回或甚至从应用程序退出。

为了简单起见,我们忽略了实现错误处理、超时等方面的内容。

为了解决第一个问题,我们使用context.Context,它为我们提供了一个很好的执行上下文处理工具的层次结构;为了解决第二个问题,我们使用sync.WaitGroup,它允许我们等待一组goroutine完成其任务。一个简单的示例代码如下:

func main() {
    all := &sync.WaitGroup{}
    rootCtx, rootCancel := context.WithCancel(context.Background())

    all.Add(1)
    go level1(rootCtx, all)

    // just to simulate stop, we could use an os signal instead
    // app ends after 3 seconds
    go func() {
        time.Sleep(time.Second * 3)
        rootCancel()
    }()

    all.Wait()
}

func level1(parent context.Context, all *sync.WaitGroup) {
    defer all.Done()
    l1Ctx, l1Cancel := context.WithCancel(parent)
    defer l1Cancel()

    for i := 0; i < 3; i++ {
        all.Add(1)
        go level2(l1Ctx, all)
    }

    for {
        select {
        case <-parent.Done():
            return
        // other cases if any,
        // this is a sample
        case <-time.After(time.Second):
            log.Println(`level1`)
        }
    }
}

func level2(parent context.Context, all *sync.WaitGroup) {
    defer all.Done()
    for {
        select {
        case <-parent.Done():
            return
        case <-time.After(time.Second):
            log.Println(`level2`)
        }
    }
}

这段代码的输出类似于:

[info] level2
[info] level2
[info] level2
[info] level1
[info] level2
[info] level1
[info] level2
[info] level2

目前还没有官方的包提供将context.Contextsync.WaitGroup功能结合起来的功能。最接近的是errgroup,它可以通过一些技巧实现类似的功能。

英文:

This problem has two parts.

First we need to stop child goroutines somehow in a way that even if a parent goroutines stops, all it's children should get notified and stop - a hierarchy of stop signals that goes down but not up.

On the other hand the parent needs to wait for it's children until they are done. Otherwise we would return from a goroutine or even exit from the app before some goroutines are finished properly.

For simplicity we ignore implementing error handling, timeouts and the like.

For handling the first problem we use context.Context which gives us a nice hierarchy of execution context handling tools and for solving the second problem we use sync.WaitGroup which allows us to wait for a group of goroutines to complete their tasks. A simple demonstration would be:

func main() {
	all := &amp;sync.WaitGroup{}
	rootCtx, rootCancel := context.WithCancel(context.Background())

	all.Add(1)
	go level1(rootCtx, all)

	// just to simulate stop, we could use an os signal instead
	// app ends after 3 seconds
	go func() {
		time.Sleep(time.Second * 3)
		rootCancel()
	}()

	all.Wait()
}

func level1(parent context.Context, all *sync.WaitGroup) {
	defer all.Done()
	l1Ctx, l1Cancel := context.WithCancel(parent)
	defer l1Cancel()

	for i := 0; i &lt; 3; i++ {
		all.Add(1)
		go level2(l1Ctx, all)
	}

	for {
		select {
		case &lt;-parent.Done():
			return
		// other cases if any,
		// this is a sample
		case &lt;-time.After(time.Second):
			log.Println(`level1`)
		}
	}
}

func level2(parent context.Context, all *sync.WaitGroup) {
	defer all.Done()
	for {
		select {
		case &lt;-parent.Done():
			return
		case &lt;-time.After(time.Second):
			log.Println(`level2`)
		}
	}
}

Which gives us some output like:

[  info ] level2
[  info ] level2
[  info ] level2
[  info ] level1
[  info ] level2
[  info ] level1
[  info ] level2
[  info ] level2

Currently there is no official package that provide a functionality which combines context.Context and sync.WaitGroup. The nearest thing is an errgroup which can resemble this functionality with some hacks.

huangapple
  • 本文由 发表于 2017年3月1日 03:00:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/42516717.html
匿名

发表评论

匿名网友

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

确定