Bir gorutin nasıl durdurulur


102

Bir yöntem çağıran ve bir kanalda döndürülen değeri ileten bir gorutinim var:

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

Böyle bir gorutini nasıl durdururum?


1
Durumunuza bağlı olarak başka bir cevap, bir Go Context kullanmaktır. Bunun hakkında bir cevap oluşturacak zamanım veya bilgim yok. Sadece burada bahsetmek istedim, böylece bu cevabı arayan ve tatmin edici bulmayan insanlar çekecek başka bir konuya sahipler (amaçlanan). Çoğu durumda, kabul edilen yanıtın önerdiği şekilde yapmalısınız. Bu cevap bağlamlardan bahsediyor: stackoverflow.com/a/47302930/167958
Omnifarious

Yanıtlar:


51

DÜZENLEME: Sorunuzun bir gorutinin içindeki bir kanala değerler göndermekle ilgili olduğunu anlamadan önce bu yanıtı aceleyle yazdım. Aşağıdaki yaklaşım, yukarıda önerildiği gibi ek bir kanal ile kullanılabilir veya sahip olduğunuz kanalın çift yönlü olduğu gerçeğini kullanarak, sadece birini kullanabilirsiniz ...

Programınız yalnızca kanaldan çıkan öğeleri işlemek için mevcutsa, "kapat" yerleşikini ve kanallar için özel alma formunu kullanabilirsiniz.

Yani, kanala öğe göndermeyi tamamladığınızda kapatırsınız. Daha sonra, programınızın içinde, alma operatörüne kanalın kapatılıp kapatılmadığını gösteren ekstra bir parametre alırsınız.

İşte eksiksiz bir örnek (bekleme grubu, işlemin gorutin tamamlanana kadar devam etmesini sağlamak için kullanılır):

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

16
İç gorutinin gövdesi, daha deyimsel deferolarak çağrı yapmak ve kanal kapanana kadar tüm değerler üzerinde yinelemek için wg.Done()bir range chdöngü kullanılarak yazılır .
Alan Donovan

115

Tipik olarak, gorutine (muhtemelen ayrı) bir sinyal kanalı iletirsiniz. Bu sinyal kanalı, gorutinin durmasını istediğinizde bir değeri içeri itmek için kullanılır. Goroutin, düzenli olarak bu kanalda anketler yapar. Bir sinyal algıladığı anda kapanır.

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

// Do stuff

// Quit goroutine
quit <- true

26
Yeterince iyi değil. Ya gorutin bir hata nedeniyle sonsuz bir döngüde sıkışmışsa?
Elazar Leibovich

232
Daha sonra hata düzeltilmelidir.
jimt

13
Elazar, önerdiğin şey, bir fonksiyonu çağırdıktan sonra durdurmanın bir yolu. Bir gorutin bir iplik değildir. Farklı bir iş parçacığında çalışabilir veya sizinkiyle aynı iş parçacığında çalışabilir. Go'nun desteklemesi gerektiğini düşündüğünüz şeyi destekleyen hiçbir dil bilmiyorum.
Jeremy Wall

5
@jeremy Go için aynı fikirde değil, ancak Erlang döngü işlevi çalıştıran bir işlemi öldürmenize izin veriyor.
MatthewToday

10
Çoklu göreve gitme, önleyici değil işbirlikçidir. Bir döngüdeki bir gorutin asla programlayıcıya girmez, bu yüzden asla öldürülmez.
Jeff Allen

34

Bir gorutini dışarıdan öldüremezsin. Bir gorutinin bir kanalı kullanmayı bırakması için sinyal verebilirsiniz, ancak gorutinlerin herhangi bir meta yönetimi yapmaları için bir yolu yoktur. Goroutinlerin problemleri işbirliği içinde çözmesi amaçlanmıştır, bu nedenle yanlış davranan birini öldürmek neredeyse hiçbir zaman yeterli bir cevap olmayacaktır. Sağlamlık için izolasyon istiyorsanız, muhtemelen bir süreç istersiniz.


Ve iki Go programının bir boru üzerinden veri yapılarını kolayca değiştirmesine izin veren kodlama / gob paketine bakmak isteyebilirsiniz.
Jeff Allen

Benim durumumda, bir sistem çağrısında bloke edilecek bir gorutin var ve ona sistem çağrısını iptal edip çıkmasını söylemem gerekiyor. Okumada bir kanalda bloke olsaydım, önerdiğin gibi yapmak mümkün olurdu.
Omnifarious

Bu sorunu daha önce görmüştüm. Sorunu "çözme" şeklimiz, uygulamanın başlangıcındaki iş parçacığı sayısını, muhtemelen + CPU sayısı ile
eşleşebilecek goroutinlerin

20

Genel olarak, bir kanal oluşturabilir ve gorutinde bir durdurma sinyali alabilirsiniz.

Bu örnekte kanal oluşturmanın iki yolu var.

  1. kanal

  2. bağlam . Örnekte demo yapacağımcontext.WithCancel

İlk demo, şunu kullanın 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")
}

İkinci demo, şunu kullanın 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

Bu cevabın zaten kabul edildiğini biliyorum ama 2 sentimi de atayım dedim. Mezar paketini kullanmayı seviyorum . Temelde yenilenmiş bir çıkış kanalı, ancak herhangi bir hatayı geri almak gibi güzel şeyler de yapıyor. Kontrol altındaki rutin hala uzaktan öldürme sinyallerini kontrol etme sorumluluğuna sahiptir. Afaik, bir gorutinin "id" sini almak ve yanlış davranıyorsa onu öldürmek mümkün değildir (yani: sonsuz bir döngüde sıkışmışsa).

İşte test ettiğim basit bir örnek:

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

Çıktı şöyle görünmelidir:

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

Bu paket oldukça ilginç! tombÖrneğin, içinde panik yaratan bir şey olması durumunda gorutinin ne yapacağını test ettiniz mi? Teknik olarak konuşursak, gorutin bu durumda çıkıyor, bu yüzden hala ertelenmiş olarak adlandıracağını varsayıyorum proc.Tomb.Done()...
Gwyneth Llewelyn

1
Merhaba Gwyneth, evet proc.Tomb.Done(), panik programı çökertmeden çalıştırırdı, ama ne amaçla? Ana gorutinin bazı ifadeleri yürütmek için çok küçük bir fırsat penceresine sahip olması mümkündür , ancak başka bir gorutindeki panikten kurtulmanın bir yolu yoktur, bu nedenle program hala çökmektedir. Dokümanlar şöyle der: "F fonksiyonu panik çağırdığında, F'nin yürütülmesi durur, F'deki ertelenmiş tüm fonksiyonlar normal olarak yürütülür ve ardından F, çağırana geri döner ... İşlem, mevcut programdaki tüm fonksiyonlar geri dönene kadar yığını sürdürür, bu noktada program çöküyor. "
Kevin Cantwell

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.