2012-04-10 41 views
38

Tôi đang cố hiểu đồng thời trong Go. Đặc biệt, tôi đã viết thread-an toàn này chương trình:Tìm hiểu về goroutines

package main 

import "fmt" 

var x = 1 

func inc_x() { //test 
    for { 
    x += 1 
    } 
} 

func main() { 
    go inc_x() 
    for { 
    fmt.Println(x) 
    } 
} 

tôi nhận ra rằng tôi nên sử dụng các kênh truyền hình để ngăn chặn tình trạng đua với x, nhưng đó không phải là điểm ở đây. Chương trình in 1 và sau đó dường như lặp mãi mãi (không in thêm bất kỳ thứ gì). Tôi mong đợi nó in một danh sách vô hạn các số, có thể bỏ qua một số và lặp lại những người khác do điều kiện chủng tộc (hoặc tệ hơn - in số trong khi nó đang được cập nhật trong inc_x).

Câu hỏi của tôi là: Tại sao chương trình chỉ in một dòng?

Chỉ cần rõ ràng: Tôi không sử dụng kênh nhằm mục đích cho ví dụ về đồ chơi này.

Trả lời

32

Có một vài điều cần lưu ý về goroutines Go.

  1. Chúng không phải là chủ đề theo nghĩa Java hoặc C++.
    1. Chúng giống như greenlets hơn.
  2. Các rạp thời gian chạy đi các goroutines qua đề hệ thống
    1. Số lượng đề hệ thống được điều khiển bởi một môi trường GOMAXPROCS biến và mặc định là 1 hiện tại tôi nghĩ. Nó có thể thay đổi tương lai.
  3. Cách goroutines mang lại luồng hiện tại của chúng được kiểm soát bởi nhiều cấu trúc khác nhau.
    1. Câu lệnh chọn có thể kiểm soát trở lại luồng.
    2. gửi trên kênh có thể kiểm soát trở lại luồng.
    3. Thao tác IO có thể kiểm soát trở lại luồng.
    4. thời gian chạy.Gosched() kiểm soát rõ ràng kiểm soát trở lại luồng.

Các hành vi bạn đang nhìn thấy là do chức năng chính không bao giờ mang lại cho các chủ đề và được thay vì tham gia vào một vòng lặp bận rộn và vì chỉ có một thread vòng lặp chính không có nơi nào để chạy.

+0

Chỉ cần nghĩ rằng tôi muốn đề cập rằng kể từ Go 1.2, lịch trình có thể được gọi vào chức năng nhập định kỳ. Nó sẽ giải quyết trường hợp này tôi không nghĩ, nhưng nó giúp khi bạn có một vòng lặp chặt chẽ gọi một chức năng không được gạch chân. –

2

Không chắc chắn, nhưng tôi nghĩ rằng inc_x đang hogging CPU. Vì không có IO nó không phát hành kiểm soát.

Tôi đã tìm thấy hai thứ đã giải quyết được điều đó. Một là để gọi runtime.GOMAXPROCS(2) ở đầu chương trình và sau đó nó sẽ làm việc kể từ bây giờ có hai chủ đề phục vụ goroutings. Cách khác là chèn time.Sleep(1) sau khi tăng x.

17

Theo thisthis, một số cuộc gọi không thể gọi trong Goroutine bị ràng buộc CPU (nếu Goroutine không bao giờ mang lại lịch biểu). Điều này có thể gây ra Goroutines khác để treo nếu họ cần để ngăn chặn các chủ đề chính (ví dụ là trường hợp với các write() syscall sử dụng bởi fmt.Println())

Các giải pháp tôi tìm thấy liên quan đến việc gọi runtime.Gosched() trong chủ đề cpu-bound của bạn để mang lại cho lên lịch, như sau:

package main 

import (
    "fmt" 
    "runtime" 
) 

var x = 1 

func inc_x() { 
    for { 
    x += 1 
    runtime.Gosched() 
    } 
} 

func main() { 
    go inc_x() 
    for { 
    fmt.Println(x) 
    } 
} 

vì bạn chỉ thực hiện một hoạt động trong Goroutine, runtime.Gosched() đang được gọi là rất thường. Gọi runtime.GOMAXPROCS(2) trên init nhanh hơn theo thứ tự độ lớn, nhưng sẽ rất không an toàn nếu bạn làm bất cứ điều gì phức tạp hơn tăng số (ví dụ, xử lý mảng, cấu trúc, bản đồ, v.v.).

Trong trường hợp đó, thực tiễn tốt nhất có khả năng sẽ sử dụng kênh để quản lý quyền truy cập chia sẻ vào tài nguyên.

Cập nhật: Tính đến Go 1.2, any non-inlined function call can invoke the scheduler.

+3

Có đúng không khi nói rằng vấn đề này không bao giờ xảy ra với việc sử dụng kênh thích hợp? Nếu không nó có vẻ như một vấn đề lớn với Go - nếu một sợi có thể hog CPU và không có chủ đề khác có được để thực hiện. –

+0

Nếu bạn có nhiều CPU, bạn cũng có thể đặt biến môi trường thành GOMAXPROCS = 2 và sau đó goroutine có thể chạy trong một chuỗi riêng biệt hơn hàm chính. Hàm Gosched() cho biết thời gian chạy để sinh ra trong vòng lặp đó. –

+0

@ user793587 Phụ thuộc vào ý bạn là "thích hợp". Bỏ phiếu trong một vòng lặp chặt chẽ có thể hog một sợi, nhưng anyways xấu của mã. Trong thực tế, nó không phải là một vấn đề. Trong trường hợp hiếm hoi bạn * cần * để thăm dò ý kiến ​​trong một vòng lặp chặt chẽ, bạn có thể thu được một cách rõ ràng lên lịch trình, nhưng nó chỉ thường xuất hiện trong các ví dụ đồ chơi. Tôi đã nghe nói về các kế hoạch để chuyển sang một bộ lập lịch preemptive, nhưng lịch trình hợp tác hiện tại hoạt động tốt trong hầu hết các trường hợp. – SteveMcQwark

7

Đó là sự tương tác của hai điều. Một, theo mặc định, Go chỉ sử dụng một lõi đơn và hai, Go phải lên lịch hợp tác với goroutines. Hàm inc_x của bạn không có năng suất và do đó nó độc quyền hóa lõi đơn được sử dụng. Việc giảm một trong hai điều kiện này sẽ dẫn đến kết quả bạn mong đợi.

Nói "lõi" là một chút bóng. Go thực sự có thể sử dụng nhiều lõi đằng sau hậu trường, nhưng nó sử dụng một biến gọi là GOMAXPROCS để xác định số lượng các luồng để lên lịch các goroutines của bạn đang thực hiện các nhiệm vụ không thuộc hệ thống. Như được giải thích trong các FAQEffective Go mặc định là 1, nhưng nó có thể được đặt cao hơn với một biến môi trường hoặc một hàm thời gian chạy. Điều này có thể sẽ cung cấp cho đầu ra bạn mong đợi, nhưng chỉ khi bộ vi xử lý của bạn có nhiều lõi.

Độc lập của lõi và GOMAXPROCS, bạn có thể cung cấp trình lên lịch goroutine trong thời gian chạy một cơ hội để thực hiện công việc của mình. Trình lên lịch không thể khước từ một goroutine đang chạy nhưng phải chờ nó quay trở lại thời gian chạy và yêu cầu một số dịch vụ, chẳng hạn như IO, time.Sleep() hoặc runtime.Gosched(). Việc thêm bất kỳ thứ gì như thế này trong inc_x sẽ tạo ra kết quả mong đợi. Các goroutine chạy main() đã yêu cầu một dịch vụ với fmt.Println, do đó, với hai goroutines bây giờ định kỳ năng suất cho thời gian chạy, nó có thể làm một số loại lịch trình công bằng.

+0

Bạn có thể chạy nhiều luồng/quy trình đồng thời trên một bộ xử lý, do đó "chỉ khi bộ xử lý của bạn có nhiều lõi" là gây hiểu nhầm. – lunixbochs

Các vấn đề liên quan