고 루틴을 중지하는 방법


102

메서드를 호출하고 채널에서 반환 된 값을 전달하는 고 루틴이 있습니다.

ch := make(chan int, 100)
go func(){
    for {
        ch <- do_stuff()
    }
}()

그런 고 루틴을 어떻게 막을 수 있습니까?


1
상황에 따라 또 다른 대답은 Go Context를 사용하는 것입니다. 나는 이것에 대한 답을 만들 시간이나 지식이 없습니다. 이 답변을 검색하고 만족스럽지 않은 사람들이 다른 스레드를 가져올 수 있도록 여기에서 언급하고 싶었습니다. 대부분의 경우 수락 된 답변이 제안하는대로 수행해야합니다. 이 답변은 컨텍스트를 언급합니다. stackoverflow.com/a/47302930/167958
Omnifarious

답변:


50

편집 : 귀하의 질문이 goroutine 내부의 chan에 값을 보내는 것에 관한 것임을 깨닫기 전에이 답변을 서둘러 작성했습니다. 아래의 접근 방식은 위에서 제안한 추가 채널과 함께 사용하거나 이미 가지고있는 채널이 양방향이라는 사실을 사용하여 사용할 수 있습니다.

고 루틴이 채널에서 나오는 항목을 처리하기 위해서만 존재하는 경우 "close"내장 및 채널에 대한 특수 수신 양식을 사용할 수 있습니다.

즉, 짱에 항목을 보내고 나면 닫습니다. 그런 다음 고 루틴 내부에서 채널이 닫혔는지 여부를 보여주는 수신 연산자에 대한 추가 매개 변수를 얻습니다.

다음은 완전한 예입니다 (대기 그룹은 고 루틴이 완료 될 때까지 프로세스가 계속되는지 확인하는 데 사용됩니다).

package main

import "sync"
func main() {
    var wg sync.WaitGroup
    wg.Add(1)

    ch := make(chan int)
    go func() {
        for {
            foo, ok := <- ch
            if !ok {
                println("done")
                wg.Done()
                return
            }
            println(foo)
        }
    }()
    ch <- 1
    ch <- 2
    ch <- 3
    close(ch)

    wg.Wait()
}

15
내부 고 루틴의 본문은 deferto call을 사용하여보다 관용적으로 작성 되었으며 채널이 닫힐 때까지 모든 값을 반복 wg.Done()하는 range ch루프입니다.
Alan Donovan

115

일반적으로 고 루틴 a (별도의) 신호 채널을 전달합니다. 이 신호 채널은 고 루틴이 중지되기를 원할 때 값을 푸시하는 데 사용됩니다. 고 루틴은 해당 채널을 정기적으로 폴링합니다. 신호를 감지하는 즉시 종료됩니다.

quit := make(chan bool)
go func() {
    for {
        select {
        case <- quit:
            return
        default:
            // Do other stuff
        }
    }
}()

// Do stuff

// Quit goroutine
quit <- true

26
충분하지. 버그로 인해 고 루틴이 무한 루프에 갇 히면 어떻게됩니까?
Elazar Leibovich 2011

232
그런 다음 버그를 수정해야합니다.
jimt

13
Elazar, 당신이 제안하는 것은 당신이 그것을 호출 한 후에 함수를 중지하는 방법입니다. 고 루틴은 스레드가 아닙니다. 다른 스레드에서 실행되거나 귀하의 스레드와 동일한 스레드에서 실행될 수 있습니다. Go가 지원해야한다고 생각하는 것을 지원하는 언어가 없습니다.
Jeremy Wall

5
@jeremy Go에 동의하지 않지만 Erlang을 사용하면 루핑 기능을 실행하는 프로세스를 종료 할 수 있습니다.
MatthewToday

10
Go 멀티 태스킹은 선제 적이 아니라 협력 적입니다. 루프의 고 루틴은 스케줄러에 들어 가지 않으므로 절대로 죽일 수 없습니다.
Jeff Allen

34

외부에서 고 루틴을 죽일 수는 없습니다. 고 루틴에 채널 사용을 중지하도록 신호를 보낼 수 있지만 고 루틴에 대한 핸들은 어떤 종류의 메타 관리도 수행 할 수 없습니다. 고 루틴은 협력 적으로 문제를 해결하기위한 것이므로 오작동하는 문제를 죽이는 것은 거의 적절한 대응이 아닙니다. 견고성을 위해 격리를 원한다면 아마도 프로세스가 필요할 것입니다.


두 개의 Go 프로그램이 파이프를 통해 데이터 구조를 쉽게 교환 할 수 있도록하는 encoding / gob 패키지를 살펴볼 수 있습니다.
Jeff Allen

제 경우에는 시스템 호출에서 차단 될 고 루틴이 있으며 시스템 호출을 중단 한 다음 종료하도록 지시해야합니다. 내가 채널 읽기에서 차단 되었다면 당신이 제안한대로 할 수 있습니다.
Omnifarious

나는 전에 그 문제를 보았다. 우리가 "해결"한 방법은 가능한 고 루틴 수 + CPU 수와 일치하도록 애플리케이션 시작시 스레드 수를 늘리는 것이 었습니다
rouzier

19

일반적으로 채널을 생성하고 고 루틴에서 정지 신호를 수신 할 수 있습니다.

이 예에서 채널을 만드는 방법에는 두 가지가 있습니다.

  1. 채널

  2. 컨텍스트 . 이 예에서는context.WithCancel

첫 번째 데모는 다음을 사용합니다 channel.

package main

import "fmt"
import "time"

func do_stuff() int {
    return 1
}

func main() {

    ch := make(chan int, 100)
    done := make(chan struct{})
    go func() {
        for {
            select {
            case ch <- do_stuff():
            case <-done:
                close(ch)
                return
            }
            time.Sleep(100 * time.Millisecond)
        }
    }()

    go func() {
        time.Sleep(3 * time.Second)
        done <- struct{}{}
    }()

    for i := range ch {
        fmt.Println("receive value: ", i)
    }

    fmt.Println("finish")
}

두 번째 데모는 다음을 사용합니다 context.

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    forever := make(chan struct{})
    ctx, cancel := context.WithCancel(context.Background())

    go func(ctx context.Context) {
        for {
            select {
            case <-ctx.Done():  // if cancel() execute
                forever <- struct{}{}
                return
            default:
                fmt.Println("for loop")
            }

            time.Sleep(500 * time.Millisecond)
        }
    }(ctx)

    go func() {
        time.Sleep(3 * time.Second)
        cancel()
    }()

    <-forever
    fmt.Println("finish")
}

11

이 대답은 이미 받아 들여 졌다는 것을 알고 있지만 2 센트를 넣을 것이라고 생각했습니다. 저는 무덤 패키지 를 사용하는 것을 좋아 합니다. 기본적으로 지원 종료 채널이지만 오류를 다시 전달하는 것과 같은 좋은 기능을 수행합니다. 제어되는 루틴은 여전히 ​​원격 킬 신호를 확인하는 책임이 있습니다. Afaik 고 루틴의 "id"를 가져 와서 오작동하는 경우 죽일 수 없습니다 (예 : 무한 루프에 갇혀 있음).

다음은 내가 테스트 한 간단한 예입니다.

package main

import (
  "launchpad.net/tomb"
  "time"
  "fmt"
)

type Proc struct {
  Tomb tomb.Tomb
}

func (proc *Proc) Exec() {
  defer proc.Tomb.Done() // Must call only once
  for {
    select {
    case <-proc.Tomb.Dying():
      return
    default:
      time.Sleep(300 * time.Millisecond)
      fmt.Println("Loop the loop")
    }
  }
}

func main() {
  proc := &Proc{}
  go proc.Exec()
  time.Sleep(1 * time.Second)
  proc.Tomb.Kill(fmt.Errorf("Death from above"))
  err := proc.Tomb.Wait() // Will return the error that killed the proc
  fmt.Println(err)
}

출력은 다음과 같아야합니다.

# Loop the loop
# Loop the loop
# Loop the loop
# Loop the loop
# Death from above

이 패키지는 꽤 흥미 롭습니다! tomb예를 들어, 고 루틴 내부에서 어떤 일이 발생하여 패닉을 일으키는 경우를 대비하여 고 루틴이 무엇을하는지 테스트 해 보셨습니까 ? 내가 있으리라 믿고있어 그래서 기술적으로는 연기를 호출 여전히,이 경우, goroutine 종료를합니다 말하기 proc.Tomb.Done()...
기네스 르 웰린

1
안녕 Gwyneth, 예 proc.Tomb.Done(), 패닉이 프로그램을 충돌시키기 전에 실행될 것입니다. 메인 고 루틴이 일부 명령문을 실행할 수 있는 매우 작은 기회를 가질 수 있지만 다른 고 루틴의 패닉에서 복구 할 방법이 없으므로 프로그램이 여전히 충돌합니다. 문서에 따르면 "F가 패닉을 호출하면 F의 실행이 중지되고 F의 지연된 함수가 정상적으로 실행 된 다음 F가 호출자에게 반환됩니다. 프로세스는 현재 고 루틴의 모든 함수가 반환 될 때까지 스택을 계속합니다. 이 시점에서 프로그램이 충돌합니다. "
Kevin Cantwell

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.