Làm thế nào để dừng một quy trình


102

Tôi có một quy trình gọi một phương thức và chuyển giá trị trả về trên một kênh:

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

Làm cách nào để dừng một thói quen như vậy?


1
Một câu trả lời khác, tùy thuộc vào tình huống của bạn, là sử dụng Ngữ cảnh cờ vây. Tôi không có thời gian hoặc kiến ​​thức để tạo ra câu trả lời về điều này. Tôi chỉ muốn đề cập đến nó ở đây để những người tìm kiếm và thấy câu trả lời này không hài lòng có một chủ đề khác để kéo (ý định chơi chữ). Trong hầu hết các trường hợp, bạn nên làm như câu trả lời được chấp nhận gợi ý. Câu trả lời này đề cập đến các ngữ cảnh: stackoverflow.com/a/47302930/167958
Omnifarious

Câu trả lời:


50

CHỈNH SỬA: Tôi đã viết câu trả lời này một cách vội vàng, trước khi nhận ra rằng câu hỏi của bạn là về việc gửi các giá trị đến một chan bên trong một quy trình. Cách tiếp cận dưới đây có thể được sử dụng với một chan bổ sung như đã đề xuất ở trên, hoặc sử dụng thực tế là chan bạn đã có hai hướng, bạn có thể chỉ sử dụng một ...

Nếu quy trình goroutine của bạn chỉ tồn tại để xử lý các mặt hàng ra khỏi chan, bạn có thể sử dụng nội trang "đóng" và biểu mẫu nhận đặc biệt cho các kênh.

Tức là sau khi gửi xong các mục trên chan, bạn đóng nó lại. Sau đó, bên trong quy trình của bạn, bạn nhận được một tham số bổ sung cho toán tử nhận cho biết liệu kênh đã bị đóng hay chưa.

Đây là một ví dụ đầy đủ (nhóm chờ được sử dụng để đảm bảo rằng quá trình tiếp tục cho đến khi quy trình hoàn tất):

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
Phần thân của goroutine bên trong được viết một cách thành ngữ hơn bằng cách sử dụng deferđể gọi wg.Done()và một range chvòng lặp để lặp lại tất cả các giá trị cho đến khi kênh bị đóng.
Alan Donovan,

115

Thông thường, bạn chuyển goroutine một kênh tín hiệu (có thể riêng biệt). Kênh tín hiệu đó được sử dụng để đẩy một giá trị vào khi bạn muốn quy trình dừng lại. Goroutine thăm dò kênh đó thường xuyên. Ngay sau khi phát hiện một tín hiệu, nó sẽ thoát ra.

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

// Do stuff

// Quit goroutine
quit <- true

26
Không đủ tốt. Điều gì sẽ xảy ra nếu quy trình goroutine bị mắc kẹt trong một vòng lặp vô tận do lỗi?
Elazar Leibovich,

232
Sau đó, lỗi sẽ được sửa.
jimt

13
Elazar, Những gì bạn đề xuất là một cách để dừng một chức năng sau khi bạn đã gọi nó. Một quy trình không phải là một chuỗi. Nó có thể chạy trong một chuỗi khác hoặc nó có thể chạy trong cùng một chuỗi với của bạn. Tôi biết không có ngôn ngữ nào hỗ trợ những gì bạn nghĩ rằng Go nên hỗ trợ.
Jeremy Wall,

5
@jeremy Không đồng ý với Go, nhưng Erlang cho phép bạn giết một tiến trình đang chạy chức năng lặp.
MatthewHôm nay

10
Đi đa nhiệm là hợp tác, không phải là phủ đầu. Một quy trình trong một vòng lặp không bao giờ đi vào bộ lập lịch, vì vậy nó không bao giờ có thể bị giết.
Jeff Allen

34

Bạn không thể giết một quy trình từ bên ngoài. Bạn có thể báo hiệu một quy trình ngừng sử dụng một kênh, nhưng không có quy trình nào về quy trình thực hiện bất kỳ loại quản lý meta nào. Goroutines nhằm mục đích hợp tác giải quyết các vấn đề, do đó, việc giết một con đang hoạt động sai hầu như không bao giờ là một phản ứng thích hợp. Nếu bạn muốn sự cô lập để có sự mạnh mẽ, bạn có thể muốn có một quá trình.


Và bạn có thể muốn xem xét gói mã hóa / gob, gói này sẽ cho phép hai chương trình Go dễ dàng trao đổi cấu trúc dữ liệu qua đường ống.
Jeff Allen

Trong trường hợp của tôi, tôi có một quy trình sẽ bị chặn trong cuộc gọi hệ thống và tôi cần yêu cầu nó hủy cuộc gọi hệ thống rồi thoát. Nếu tôi bị chặn khi đọc kênh, tôi có thể làm như bạn đề xuất.
Omnifarious

Tôi đã thấy vấn đề đó trước đây. Cách chúng tôi "giải quyết" nó là tăng số luồng khi khởi động ứng dụng để phù hợp với số lượng goroutines có thể có + số lượng CPU
rouzier

18

Nói chung, bạn có thể tạo kênh và nhận tín hiệu dừng trong quy trình.

Có hai cách để tạo kênh trong ví dụ này.

  1. kênh

  2. bối cảnh . Trong ví dụ, tôi sẽ democontext.WithCancel

Bản demo đầu tiên, sử dụng 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")
}

Bản demo thứ hai, sử dụng 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

Tôi biết câu trả lời này đã được chấp nhận, nhưng tôi nghĩ tôi sẽ ném 2 xu của mình vào. Tôi thích sử dụng gói mộ . Về cơ bản, nó là một kênh thoát đã hoàn thành, nhưng nó cũng thực hiện những điều tốt đẹp như trả lại bất kỳ lỗi nào. Quy trình được kiểm soát vẫn có trách nhiệm kiểm tra các tín hiệu tiêu diệt từ xa. Afaik không thể lấy "id" của một quy trình và giết nó nếu nó hoạt động sai (tức là: bị mắc kẹt trong một vòng lặp vô hạn).

Đây là một ví dụ đơn giản mà tôi đã thử nghiệm:

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

Đầu ra sẽ giống như sau:

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

Gói này khá thú vị! Bạn đã thử nghiệm để xem điều tombgì xảy ra với goroutine trong trường hợp có điều gì đó xảy ra bên trong nó khiến bạn hoảng sợ chưa? Về mặt kỹ thuật, các lối ra goroutine trong trường hợp này, vì vậy tôi giả định nó sẽ vẫn gọi trì hoãn proc.Tomb.Done()...
Gwyneth Llewelyn

1
Xin chào Gwyneth, vâng proc.Tomb.Done()sẽ thực thi trước khi chương trình hoảng sợ, nhưng kết thúc là gì? Có thể quy trình chính có thể có một cơ hội rất nhỏ để thực thi một số câu lệnh, nhưng nó không có cách nào khôi phục sau cơn hoảng loạn trong một quy trình khác, vì vậy chương trình vẫn bị treo. Docs cho biết: "Khi hàm F gọi hoảng loạn, việc thực thi F dừng lại, mọi hàm bị hoãn lại trong F được thực thi bình thường và sau đó F quay trở lại trình gọi của nó. Quá trình tiếp tục lên ngăn xếp cho đến khi tất cả các hàm trong goroutine hiện tại đã trả về. tại thời điểm đó chương trình bị treo. "
Kevin Cantwell,

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.