Chính xác thì runtime.Gosched làm gì?


86

Trong phiên bản trước khi phát hành go 1.5 của trang web Tour of Go , có một đoạn mã trông như thế này.

package main

import (
    "fmt"
    "runtime"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        runtime.Gosched()
        fmt.Println(s)
    }
}

func main() {
    go say("world")
    say("hello")
}

Đầu ra trông như thế này:

hello
world
hello
world
hello
world
hello
world
hello

Điều làm tôi bận tâm là khi runtime.Gosched()bị gỡ bỏ, chương trình không còn in "world" nữa.

hello
hello
hello
hello
hello

Tại sao lại như vậy? Làm thế nào runtime.Gosched()ảnh hưởng đến việc thực hiện?

Câu trả lời:


143

Ghi chú:

Kể từ Go 1.5, GOMAXPROCS được đặt thành số lõi của phần cứng: golang.org/doc/go1.5#runtime , thấp hơn câu trả lời ban đầu trước 1.5.


Khi bạn chạy chương trình Go mà không chỉ định biến môi trường GOMAXPROCS, các goroutines Go được lên lịch thực thi trong một chuỗi hệ điều hành duy nhất. Tuy nhiên, để làm cho chương trình có vẻ đa luồng (đó là mục đích của các goroutines, phải không?), Bộ lập lịch Go đôi khi phải chuyển đổi bối cảnh thực thi, vì vậy mỗi goroutine có thể thực hiện phần việc của nó.

Như tôi đã nói, khi biến GOMAXPROCS không được chỉ định, Go runtime chỉ được phép sử dụng một luồng, vì vậy không thể chuyển đổi ngữ cảnh thực thi trong khi goroutine đang thực hiện một số công việc thông thường, như tính toán hoặc thậm chí IO (được ánh xạ tới các hàm C đơn giản ). Ngữ cảnh chỉ có thể được chuyển đổi khi sử dụng nguyên mẫu đồng thời của Go, ví dụ: khi bạn bật một số lệnh hoặc (đây là trường hợp của bạn) khi bạn yêu cầu người lập lịch chuyển đổi ngữ cảnh một cách rõ ràng - đây runtime.Goschedlà mục đích.

Vì vậy, trong ngắn hạn, khi ngữ cảnh thực thi trong một goroutine gặp Goschedlệnh gọi, bộ lập lịch được hướng dẫn chuyển việc thực thi sang một goroutine khác. Trong trường hợp của bạn, có hai goroutines, main (đại diện cho luồng 'chính' của chương trình) và bổ sung, một trong những thứ bạn đã tạo go say. Nếu bạn xóa Goschedcuộc gọi, ngữ cảnh thực thi sẽ không bao giờ được chuyển từ quy trình đầu tiên sang quy trình thứ hai, do đó không có 'thế giới' cho bạn. Khi Goschedcó mặt, bộ lập lịch chuyển việc thực thi trên mỗi lần lặp vòng lặp từ goroutine đầu tiên sang chương trình thứ hai và ngược lại, vì vậy bạn có 'hello' và 'world' xen kẽ.

FYI, đây được gọi là 'đa nhiệm hợp tác': các goroutines phải nhường quyền kiểm soát rõ ràng cho các goroutines khác. Cách tiếp cận được sử dụng trong hầu hết các hệ điều hành hiện đại được gọi là 'đa nhiệm phủ đầu': các luồng thực thi không liên quan đến việc chuyển điều khiển; thay vào đó, bộ lập lịch chuyển đổi các ngữ cảnh thực thi sang chúng một cách minh bạch. Cách tiếp cận hợp tác thường được sử dụng để triển khai 'chuỗi màu xanh lá cây', tức là các chuỗi điều chỉnh đồng thời hợp lý không ánh xạ 1: 1 tới các chuỗi hệ điều hành - đây là cách thực hiện thời gian chạy Go và các tuyến tính của nó.

Cập nhật

Tôi đã đề cập đến biến môi trường GOMAXPROCS nhưng không giải thích nó là gì. Đã đến lúc khắc phục điều này.

Khi biến này được đặt thành một số dương N, Go runtime sẽ có thể tạo tối đa Ncác luồng gốc, trên đó tất cả các luồng màu xanh lục sẽ được lên lịch. Chủ đề riêng là một loại luồng được tạo bởi hệ điều hành (luồng Windows, pthreads, v.v.). Điều này có nghĩa là nếu Nlớn hơn 1, có thể các goroutines sẽ được lên lịch để thực thi trong các luồng gốc khác nhau và do đó, chạy song song (ít nhất là tùy theo khả năng máy tính của bạn: nếu hệ thống của bạn dựa trên bộ xử lý đa lõi, nó có khả năng là các luồng này sẽ thực sự song song; nếu bộ xử lý của bạn có lõi đơn, thì đa nhiệm ưu tiên được thực hiện trong các luồng hệ điều hành sẽ tạo ra khả năng thực thi song song).

Có thể đặt biến GOMAXPROCS bằng cách sử dụng runtime.GOMAXPROCS()hàm thay vì đặt trước biến môi trường. Sử dụng một cái gì đó như thế này trong chương trình của bạn thay vì hiện tại main:

func main() {
    runtime.GOMAXPROCS(2)
    go say("world")
    say("hello")
}

Trong trường hợp này, bạn có thể quan sát kết quả thú vị. Có thể bạn sẽ nhận được các dòng 'xin chào' và 'thế giới' được in xen kẽ không đồng đều, ví dụ:

hello
hello
world
hello
world
world
...

Điều này có thể xảy ra nếu các goroutines được lên lịch để phân tách các chuỗi hệ điều hành. Trên thực tế, đây là cách hoạt động của đa nhiệm phủ đầu (hoặc xử lý song song trong trường hợp hệ thống đa lõi): các luồng song song và đầu ra kết hợp của chúng là không xác định. BTW, bạn có thể rời khỏi hoặc xóa Goschedcuộc gọi, nó dường như không có tác dụng khi GOMAXPROCS lớn hơn 1.

Sau đây là những gì tôi nhận được trong một số lần chạy chương trình với runtime.GOMAXPROCScuộc gọi.

hyperplex /tmp % go run test.go
hello
hello
hello
world
hello
world
hello
world
hyperplex /tmp % go run test.go
hello
world
hello
world
hello
world
hello
world
hello
world
hyperplex /tmp % go run test.go
hello
hello
hello
hello
hello
hyperplex /tmp % go run test.go
hello
world
hello
world
hello
world
hello
world
hello
world

Hãy xem, đôi khi đầu ra là tốt, đôi khi không. Chủ nghĩa không xác định trong hành động :)

Cập nhật khác

Có vẻ như trong các phiên bản mới hơn của trình biên dịch Go Thời gian chạy Go buộc các goroutines không chỉ mang lại hiệu quả khi sử dụng nguyên thủy đồng thời mà còn trên các lệnh gọi hệ điều hành. Điều này có nghĩa là ngữ cảnh thực thi có thể được chuyển đổi giữa các goroutines trên các lệnh gọi hàm IO. Do đó, trong các trình biên dịch Go gần đây, có thể quan sát hành vi không xác định ngay cả khi GOMAXPROCS không được đặt hoặc được đặt thành 1.


Bạn đã làm rất tốt ! Nhưng tôi đã không gặp vấn đề này trong phiên bản 1.0.3, wierd.
WoooHaaaa

1
Đây là sự thật. Tôi vừa kiểm tra điều này với go 1.0.3 và vâng, hành vi này đã không xuất hiện: ngay cả với GOMAXPROCS == 1 chương trình đã hoạt động như thể GOMAXPROCS> = 2. Có vẻ như trong 1.0.3 trình lập lịch đã được tinh chỉnh.
Vladimir Matveev

Tôi nghĩ rằng mọi thứ đã thay đổi trình biên dịch wrt go 1.4. Ví dụ trong câu hỏi OPs dường như đang tạo chuỗi hệ điều hành trong khi điều này (-> gobyexample.com/atomic-counters ) dường như tạo lập lịch hợp tác. Vui lòng cập nhật câu trả lời nếu điều này là đúng
tez

8
Kể từ Go 1.5, GOMAXPROCS được đặt thành số lõi của phần cứng: golang.org/doc/go1.5#runtime
thepanuto

1
@paulkon, Gosched()cần hay không tùy vào chương trình của bạn, không phụ thuộc vào GOMAXPROCSgiá trị. Hiệu quả của đa nhiệm phủ đầu so với hợp tác cũng phụ thuộc vào chương trình của bạn. Nếu chương trình của bạn bị ràng buộc I / O, thì đa nhiệm hợp tác với I / O không đồng bộ có thể sẽ hiệu quả hơn (tức là có nhiều thông lượng hơn) so với I / O dựa trên luồng đồng bộ; nếu chương trình của bạn bị ràng buộc bởi CPU (ví dụ: tính toán dài), thì đa nhiệm hợp tác sẽ ít hữu ích hơn nhiều.
Vladimir Matveev

8

Lập kế hoạch hợp tác là thủ phạm. Nếu không có kết quả, quy trình goroutine khác (giả sử "thế giới") có thể không có cơ hội thực thi hợp pháp trước / khi main kết thúc, theo thông số kỹ thuật chấm dứt tất cả các gorutines - tức là. toàn bộ quá trình.


1
được, vì vậy runtime.Gosched()sản lượng. Điều đó nghĩa là gì? Nó mang lại điều khiển trở lại chức năng chính?
Jason Yeo

5
Trong trường hợp cụ thể này có. Nói chung, nó yêu cầu người lập lịch khởi động và chạy bất kỳ một trong các goroutines "sẵn sàng" theo thứ tự lựa chọn không xác định có chủ ý.
zzzz
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.