Bagaimana menghentikan goroutine

102

Saya memiliki goroutine yang memanggil metode, dan meneruskan nilai yang dikembalikan pada saluran:

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

Bagaimana cara menghentikan goroutine seperti itu?

Łukasz Gruner
sumber
1
Jawaban lain, tergantung pada situasi Anda, adalah dengan menggunakan Konteks Go. Saya tidak punya waktu atau pengetahuan untuk membuat jawaban tentang ini. Saya hanya ingin menyebutkannya di sini sehingga orang yang mencari dan menemukan jawaban ini tidak memuaskan memiliki utas lain untuk ditarik (permainan kata-kata). Dalam kebanyakan kasus, Anda harus melakukan apa yang disarankan oleh jawaban yang diterima. Jawaban ini menyebutkan konteks: stackoverflow.com/a/47302930/167958
Omnifarious

Jawaban:

51

EDIT: Saya menulis jawaban ini dengan tergesa-gesa, sebelum menyadari bahwa pertanyaan Anda adalah tentang mengirim nilai ke chan di dalam goroutine. Pendekatan di bawah ini dapat digunakan baik dengan chan tambahan seperti yang disarankan di atas, atau menggunakan fakta bahwa chan yang sudah Anda miliki adalah dua arah, Anda dapat menggunakan salah satu ...

Jika goroutine Anda ada hanya untuk memproses item yang keluar dari chan, Anda dapat menggunakan builtin "tutup" dan formulir terima khusus untuk saluran.

Artinya, setelah Anda selesai mengirim barang di chan, Anda menutupnya. Kemudian di dalam goroutine Anda, Anda mendapatkan parameter tambahan ke operator penerima yang menunjukkan apakah saluran telah ditutup.

Berikut adalah contoh lengkapnya (grup tunggu digunakan untuk memastikan bahwa proses berlanjut hingga goroutine selesai):

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()
}
laslowh.dll
sumber
16
Isi goroutine bagian dalam lebih idiomatis ditulis menggunakan deferto call wg.Done(), dan range chloop untuk mengulang semua nilai hingga saluran ditutup.
Alan Donovan
115

Biasanya, Anda meneruskan saluran sinyal goroutine (mungkin terpisah). Saluran sinyal itu digunakan untuk mendorong nilai saat Anda ingin goroutine berhenti. Goroutine memilih saluran tersebut secara teratur. Begitu mendeteksi sinyal, itu berhenti.

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

// Do stuff

// Quit goroutine
quit <- true
jimt
sumber
26
Tidak cukup baik. Bagaimana jika goroutine terjebak dalam putaran tak berujung, karena bug?
Elazar Leibovich
232
Kemudian bug harus diperbaiki.
jimt
13
Elazar, Apa yang Anda sarankan adalah cara menghentikan suatu fungsi setelah Anda memanggilnya. Goroutine bukanlah utas. Ini mungkin berjalan di utas yang berbeda atau mungkin berjalan di utas yang sama dengan milik Anda. Saya tahu tidak ada bahasa yang mendukung apa yang menurut Anda harus didukung oleh Go.
Jeremy Wall
5
@jeremy Tidak tidak setuju untuk Go, tetapi Erlang memungkinkan Anda untuk menghentikan proses yang menjalankan fungsi perulangan.
MatthewToday
10
Melakukan multitasking itu kooperatif, bukan preemptive. Goroutine dalam satu loop tidak pernah memasuki penjadwal, sehingga tidak akan pernah bisa dimatikan.
Jeff Allen
34

Anda tidak bisa membunuh goroutine dari luar. Anda bisa memberi tanda pada goroutine untuk berhenti menggunakan saluran, tetapi tidak ada pegangan pada goroutine untuk melakukan manajemen meta apa pun. Goroutine dimaksudkan untuk memecahkan masalah secara kooperatif, jadi membunuh seseorang yang berperilaku tidak baik hampir tidak akan pernah menjadi respons yang memadai. Jika Anda menginginkan isolasi untuk ketahanan, Anda mungkin menginginkan sebuah proses.

SteveMcQwark
sumber
Dan Anda mungkin ingin melihat ke dalam paket encoding / gob, yang memungkinkan dua program Go dengan mudah bertukar struktur data melalui pipa.
Jeff Allen
Dalam kasus saya, saya memiliki goroutine yang akan diblokir pada panggilan sistem, dan saya perlu memberitahukannya untuk membatalkan panggilan sistem dan kemudian keluar. Jika saya diblokir pada pembacaan saluran, itu mungkin untuk melakukan apa yang Anda sarankan.
Omnifarious
Saya melihat masalah itu sebelumnya. Cara kami "menyelesaikannya" adalah dengan meningkatkan jumlah utas di awal aplikasi untuk mencocokkan jumlah goroutine yang mungkin + jumlah CPU
rouzier
20

Umumnya, Anda dapat membuat saluran dan menerima sinyal berhenti di goroutine.

Ada dua cara untuk membuat saluran dalam contoh ini.

  1. saluran

  2. konteks . Dalam contoh ini saya akan democontext.WithCancel

Demo pertama, gunakan 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")
}

Demo kedua, gunakan 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")
}
zouying
sumber
11

Saya tahu jawaban ini telah diterima, tetapi saya pikir saya akan membuang 2 sen saya. Saya suka menggunakan paket makam . Ini pada dasarnya adalah saluran keluar yang ditambahkan, tetapi itu melakukan hal-hal baik seperti mengembalikan kesalahan apa pun juga. Rutinitas yang dikendalikan masih memiliki tanggung jawab untuk memeriksa sinyal mematikan jarak jauh. Afaik tidak mungkin mendapatkan "id" dari sebuah goroutine dan membunuhnya jika ia berperilaku tidak semestinya (yaitu: terjebak dalam loop tak terbatas).

Berikut contoh sederhana yang saya uji:

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)
}

Outputnya akan terlihat seperti:

# Loop the loop
# Loop the loop
# Loop the loop
# Loop the loop
# Death from above
Kevin Cantwell
sumber
Paket ini cukup menarik! Sudahkah Anda menguji untuk melihat apa yang tombdilakukan dengan goroutine seandainya terjadi sesuatu di dalamnya yang menimbulkan kepanikan, misalnya? Secara teknis, goroutine keluar dalam kasus ini, jadi saya berasumsi itu masih akan memanggil deferred proc.Tomb.Done()...
Gwyneth Llewelyn
1
Hai Gwyneth, ya proc.Tomb.Done()akan mengeksekusi sebelum kepanikan merusak program, tetapi untuk tujuan apa? Mungkin saja goroutine utama memiliki jendela kesempatan yang sangat kecil untuk mengeksekusi beberapa pernyataan, tetapi tidak ada cara untuk memulihkan dari kepanikan di goroutine lain, sehingga program tetap macet. Docs mengatakan: "Ketika fungsi F memanggil panic, eksekusi F berhenti, semua fungsi yang ditangguhkan di F dijalankan secara normal, dan kemudian F kembali ke pemanggilnya..Proses terus menumpuk sampai semua fungsi di goroutine saat ini telah kembali, pada titik mana program macet. "
Kevin Cantwell