Bắt các giá trị trả về từ goroutines


82

Đoạn mã dưới đây đưa ra lỗi biên dịch nói rằng 'không mong muốn':

x := go doSomething(arg)

func doSomething(arg int) int{
    ...
    return my_int_value
}

Tôi biết, tôi có thể tìm nạp giá trị trả về nếu gọi hàm bình thường mà không cần sử dụng goroutine. Hoặc tôi có thể sử dụng các kênh v.v.

Câu hỏi của tôi là tại sao không thể tìm nạp một giá trị trả về như thế này từ một quy trình.


7
bạn có thể sử dụng một kênh để trả lại
rogerdpack

tại sao nó cho phép có giá trị để đổi lấy một goroutine
Srinath samala

1
@rogerdpack yêu cầu thay đổi api của bất kỳ chức năng nào bạn đang sử dụng. vì vậy bạn có thể cần một hàm wrapper nếu nó không phải là của riêng bạn
David Callanan

Câu trả lời:


66

Câu trả lời chặt chẽ là bạn có thể làm điều đó. Nó chỉ có thể không phải là một ý kiến ​​hay. Đây là mã sẽ làm điều đó:

var x int
go func() {
    x = doSomething()
}()

Điều này sẽ sinh ra một quy trình mới sẽ tính toán doSomething()và sau đó gán kết quả cho x. Vấn đề là: bạn sẽ sử dụng xtừ goroutine ban đầu như thế nào? Bạn có thể muốn đảm bảo rằng quá trình sinh sản đã được hoàn thành với nó để bạn không có tình trạng chạy đua. Nhưng nếu bạn muốn làm điều đó, bạn sẽ cần một cách giao tiếp với quy trình, và nếu bạn có cách để làm điều đó, tại sao không chỉ sử dụng nó để gửi giá trị trở lại?


6
Bạn có thể thêm WaitGroup để đảm bảo rằng bạn đã hoàn thành và đợi nó. Nhưng như bạn đã nói, đó không phải là cách để làm điều đó, một kênh là như vậy.
Not_a_Golfer

1
Đây không phải là return, đây là assignment
Nidhin David

94

Tại sao không thể tìm nạp giá trị trả về từ một chương trình goroutine gán nó cho một biến?

Chạy goroutine (không đồng bộ) và tìm nạp giá trị trả về từ hàm về cơ bản là các hành động trái ngược nhau. Khi bạn nói goý bạn là "làm điều đó không đồng bộ" hoặc đơn giản hơn: "Tiếp tục! Đừng đợi quá trình thực thi hàm kết thúc". Nhưng khi bạn gán giá trị trả về của hàm cho một biến, bạn đang mong đợi giá trị này trong biến đó. Vì vậy, khi bạn làm điều đó, x := go doSomething(arg)bạn đang nói: "Tiếp tục, đừng đợi hàm! Chờ-đợi-đợi! Tôi cần một giá trị trả về có thể truy cập được trong xvar ngay ở dòng tiếp theo bên dưới!"

Kênh truyền hình

Cách tự nhiên nhất để tìm nạp một giá trị từ một quy trình là các kênh. Kênh là các đường ống kết nối các tuyến sinh dục đồng thời. Bạn có thể gửi các giá trị vào các kênh từ một quy trình và nhận các giá trị đó vào một quy trình khác hoặc trong một hàm đồng bộ. Bạn có thể dễ dàng lấy một giá trị từ một quy trình không phá vỡ tính tương tranh bằng cách sử dụng select:

func main() {

    c1 := make(chan string)
    c2 := make(chan string)

    go func() {
        time.Sleep(time.Second * 1)
        c1 <- "one"
    }()
    go func() {
        time.Sleep(time.Second * 2)
        c2 <- "two"
    }()

    for i := 0; i < 2; i++ {
        // Await both of these values
        // simultaneously, printing each one as it arrives.
        select {
        case msg1 := <-c1:
            fmt.Println("received", msg1)
        case msg2 := <-c2:
            fmt.Println("received", msg2)
        } 
    }
}

Ví dụ được lấy từ Go By Example

CSP & chuyển tin nhắn

Go lớn hơn là dựa trên lý thuyết CSP . Mô tả ngây thơ ở trên có thể được phác thảo chính xác về CSP (mặc dù tôi tin rằng nó nằm ngoài phạm vi của câu hỏi). Tôi thực sự khuyên bạn nên làm quen với lý thuyết CSP ít nhất vì nó là RAD. Những câu danh ngôn ngắn này đưa ra một hướng suy nghĩ:

Như tên gọi của nó, CSP cho phép mô tả hệ thống dưới dạng các quy trình thành phần hoạt động độc lập và tương tác với nhau duy nhất thông qua giao tiếp truyền thông điệp .

Trong khoa học máy tính, truyền thông điệp sẽ gửi thông điệp đến một quy trình và dựa vào quy trình và cơ sở hạ tầng hỗ trợ để chọn và gọi mã thực tế để chạy. Truyền thông điệp khác với lập trình thông thường trong đó một tiến trình, chương trình con hoặc hàm được gọi trực tiếp bằng tên.


9

Ý tưởng của gotừ khóa là bạn chạy hàm doSomething một cách không đồng bộ và tiếp tục goroutine hiện tại mà không cần đợi kết quả, giống như thực hiện một lệnh trong Bash shell với dấu '&' sau nó. Nếu bạn muốn làm

x := doSomething(arg)
// Now do something with x

thì bạn cần goroutine hiện tại chặn cho đến khi doSomething kết thúc. Vì vậy, tại sao không chỉ gọi doSomething trong goroutine hiện tại? Có các tùy chọn khác (như doSomething có thể đăng kết quả lên một kênh mà goroutine hiện tại nhận các giá trị từ đó) nhưng chỉ cần gọi doSomething và gán kết quả cho một biến rõ ràng là đơn giản hơn.


0

Đó là lựa chọn thiết kế của những người sáng tạo Go. Có rất nhiều toàn bộ trừu tượng / API để đại diện cho giá trị của async I / O hoạt động - promise, future, async/await, callback, observable, vv Những trừu tượng / API được vốn đã gắn liền với đơn vị lập kế hoạch - coroutines - và những khái niệm trừu tượng / API dictate cách coroutines ( hay chính xác hơn giá trị trả về của async I / O được thể hiện bởi họ) có thể được bao gồm .

Go đã chọn truyền thông báo (hay còn gọi là các kênh ) dưới dạng trừu tượng / API để biểu thị giá trị trả về của các hoạt động I / O không đồng bộ. Và tất nhiên, goroutines và kênh cung cấp cho bạn một công cụ có thể kết hợp để thực hiện các hoạt động I / O không đồng bộ.

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.