วิธีหยุดโกรูทีน


107

ฉันมี goroutine ที่เรียกใช้เมธอดและส่งค่าที่ส่งคืนในช่อง:

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

ฉันจะหยุด goroutine ได้อย่างไร?


1
คำตอบอื่นขึ้นอยู่กับสถานการณ์ของคุณคือการใช้ Go Context ฉันไม่มีเวลาหรือความรู้ที่จะสร้างคำตอบเกี่ยวกับเรื่องนี้ ฉันแค่อยากจะพูดถึงที่นี่เพื่อให้ผู้ที่ค้นหาและพบคำตอบนี้ไม่พอใจมีเธรดอื่นที่จะดึง (เล่นสำนวนเจตนา) ในกรณีส่วนใหญ่คุณควรทำตามคำตอบที่ยอมรับแนะนำ คำตอบนี้กล่าวถึงบริบท: stackoverflow.com/a/47302930/167958
Omnifarious

คำตอบ:


53

แก้ไข: ฉันเขียนคำตอบนี้ด้วยความเร่งรีบก่อนที่จะตระหนักว่าคำถามของคุณเกี่ยวกับการส่งค่าไปยังชานภายใน goroutine วิธีการด้านล่างนี้สามารถใช้ได้กับ chan เพิ่มเติมตามที่แนะนำไว้ข้างต้นหรือใช้ข้อเท็จจริงที่ว่า chan ที่คุณมีอยู่แล้วเป็นแบบสองทิศทางคุณสามารถใช้เพียงอันเดียว ...

หากโกรูทีนของคุณมีอยู่เพื่อประมวลผลรายการที่ออกมาจากชานเท่านั้นคุณสามารถใช้ประโยชน์จาก "ปิด" ในตัวและแบบฟอร์มการรับพิเศษสำหรับช่อง

นั่นคือเมื่อคุณส่งรายการบนชานเสร็จแล้วคุณก็จะปิดมัน จากนั้นภายใน goroutine ของคุณคุณจะได้รับพารามิเตอร์พิเศษสำหรับตัวดำเนินการรับที่แสดงว่าช่องถูกปิดหรือไม่

นี่คือตัวอย่างที่สมบูรณ์ (waitgroup ใช้เพื่อให้แน่ใจว่ากระบวนการดำเนินต่อไปจนกว่า goroutine จะเสร็จสมบูรณ์):

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

17
เนื้อความของ goroutine ด้านในมีการเขียนโดยใช้สำนวนมากขึ้นโดยใช้deferเพื่อเรียกwg.Done()และrange chวนซ้ำเพื่อวนซ้ำค่าทั้งหมดจนกว่าช่องจะปิด
Alan Donovan

120

โดยปกติแล้วคุณจะส่งผ่านช่องสัญญาณ goroutine (อาจแยกจากกัน) ช่องสัญญาณนั้นใช้เพื่อดันค่าเมื่อคุณต้องการให้ goroutine หยุด goroutine โพลช่องนั้นเป็นประจำ ทันทีที่ตรวจพบสัญญาณจะหยุดทำงาน

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

// Do stuff

// Quit goroutine
quit <- true

26
ไม่ดีพอ. จะเกิดอะไรขึ้นถ้า goroutine ติดอยู่ในวงที่ไม่มีที่สิ้นสุดเนื่องจากข้อผิดพลาด?
Elazar Leibovich

241
จากนั้นควรแก้ไขข้อบกพร่อง
jimt

13
Elazar สิ่งที่คุณแนะนำคือวิธีหยุดฟังก์ชันหลังจากที่คุณเรียกใช้แล้ว goroutine ไม่ใช่ด้าย มันอาจทำงานในเธรดอื่นหรืออาจทำงานในเธรดเดียวกับของคุณ ฉันรู้ว่าไม่มีภาษาใดรองรับสิ่งที่คุณคิดว่า Go ควรสนับสนุน
Jeremy Wall

5
@jeremy ไม่เห็นด้วยกับ Go แต่ Erlang อนุญาตให้คุณฆ่ากระบวนการที่กำลังเรียกใช้ฟังก์ชันวนซ้ำ
MatthewToday

10
การทำงานหลายอย่างพร้อมกันเป็นความร่วมมือไม่ใช่การป้องกันล่วงหน้า goroutine ในลูปจะไม่เข้าสู่ตัวกำหนดตารางเวลาดังนั้นจึงไม่สามารถถูกฆ่าได้
Jeff Allen

35

คุณไม่สามารถฆ่าโกรูทีนจากภายนอกได้ คุณสามารถส่งสัญญาณให้ goroutine หยุดใช้ช่องสัญญาณได้ แต่ไม่มีการจัดการกับ goroutines ในการจัดการเมตาใด ๆ Goroutines มีจุดมุ่งหมายเพื่อแก้ปัญหาร่วมกันดังนั้นการฆ่าคนที่ประพฤติไม่ดีแทบจะไม่เป็นการตอบสนองที่เพียงพอ หากคุณต้องการแยกเพื่อความแข็งแรงคุณอาจต้องการกระบวนการ


และคุณอาจต้องการดูแพ็คเกจการเข้ารหัส / gob ซึ่งจะช่วยให้โปรแกรม Go สองโปรแกรมแลกเปลี่ยนโครงสร้างข้อมูลผ่านท่อได้อย่างง่ายดาย
Jeff Allen

ในกรณีของฉันฉันมี goroutine ที่จะถูกบล็อกในการเรียกระบบและฉันต้องบอกให้ยกเลิกการเรียกระบบแล้วออก หากฉันถูกบล็อกไม่ให้อ่านช่องก็ทำได้ตามที่คุณแนะนำ
Omnifarious

ฉันเห็นปัญหานั้นมาก่อน วิธีที่เรา "แก้ไข" คือการเพิ่มจำนวนเธรดในการเริ่มต้นแอปพลิเคชันเพื่อให้ตรงกับจำนวน goroutines ที่อาจ + จำนวนซีพียู
rouzier

23

โดยทั่วไปคุณสามารถสร้างช่องสัญญาณและรับสัญญาณหยุดใน goroutine

มีสองวิธีในการสร้างช่องในตัวอย่างนี้

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

12

ฉันรู้ว่าคำตอบนี้ได้รับการตอบรับแล้ว แต่ฉันคิดว่าฉันจะโยน 2 เซ็นต์เข้าไปฉันชอบใช้แพ็คเกจหลุมฝังศพ โดยพื้นฐานแล้วมันเป็นช่องทางการออกจากระบบ แต่มันก็ทำได้ดีเช่นส่งคืนข้อผิดพลาดใด ๆ เช่นกัน กิจวัตรภายใต้การควบคุมยังคงมีหน้าที่ในการตรวจสอบสัญญาณการฆ่าระยะไกล Afaik เป็นไปไม่ได้ที่จะรับ "id" ของ goroutine และฆ่ามันถ้ามันทำงานผิดปกติ (เช่น: ติดอยู่ในวงวนที่ไม่มีที่สิ้นสุด)

นี่คือตัวอย่างง่ายๆที่ฉันทดสอบ:

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

แพ็คเกจนี้น่าสนใจทีเดียว! คุณได้ทดสอบเพื่อดูว่าtombgoroutine ทำอะไรได้บ้างในกรณีที่มีบางสิ่งเกิดขึ้นภายในที่ทำให้ตกใจเช่น? ในทางเทคนิคแล้ว goroutine ออกในกรณีนี้ดังนั้นฉันคิดว่ามันจะยังคงเรียกว่ารอการตัดบัญชีproc.Tomb.Done()...
Gwyneth Llewelyn

1
สวัสดีกวินเน็ ธ ใช่proc.Tomb.Done()จะดำเนินการก่อนที่โปรแกรมจะล่ม แต่จะจบลงอย่างไร เป็นไปได้ว่า goroutine หลักอาจมีโอกาสน้อยมากในการดำเนินการบางคำสั่ง แต่ก็ไม่มีทางฟื้นตัวจากความตื่นตระหนกใน goroutine อื่นได้ดังนั้นโปรแกรมจึงยังคงหยุดทำงาน เอกสารกล่าวว่า: "เมื่อฟังก์ชัน F เรียกความตื่นตระหนกการดำเนินการของ F จะหยุดลงฟังก์ชันใด ๆ ที่รอการตัดบัญชีใน F จะดำเนินการตามปกติจากนั้น F จะส่งกลับไปยังผู้เรียก .. กระบวนการจะดำเนินต่อไปในกองซ้อนจนกว่าฟังก์ชันทั้งหมดใน goroutine ปัจจุบันจะกลับมา เมื่อถึงจุดที่โปรแกรมขัดข้อง "
Kevin Cantwell

นี่คือคำตอบที่ดี - ดีกว่าการใช้บริบทในบางกรณี API ที่เป็นที่แตกต่างกันเล็กน้อย แต่การใช้งานที่คล้ายกันที่นี่กับtomb.v2
Anirudh Ramanathan

7

โดยส่วนตัวแล้วฉันต้องการใช้ range ในช่องใน goroutine:

https://play.golang.org/p/qt48vvDu8cd

เดฟได้เขียนโพสต์มากเกี่ยวกับเรื่องนี้: http://dave.cheney.net/2013/04/30/curious-channels


1
ที่สวยงามอย่างแท้จริง
Profpatsch
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.