2011-12-14 39 views
39

Tôi có thể sử dụng bao nhiêu goroutines? Ví dụ wikipedia nói, trong Erlang 20 triệu quy trình có thể được tạo mà không làm giảm hiệu suất.Số lượng lớn nhất của goroutines

Cập nhật: Tôi đã chỉ investigated in goroutines performance một chút và có như vậy một kết quả:

  • Dường như goroutine đời là nhiều hơn thì tính sqrt() 1000 lần (~ 45μs cho tôi), các chỉ hạn chế là bộ nhớ
  • Goroutine chi phí 4-4,5 KB

Trả lời

34

Nếu một goroutine bị chặn, không có chi phí liên quan đến việc khác hơn là:

  • sử dụng bộ nhớ
  • chậm thu gom rác

Các chi phí (về bộ nhớ và thời gian trung bình để thực sự bắt đầu thực hiện goroutine) là:

Go 1.6.2 (April 2016) 
    32-bit x86 CPU (A10-7850K 4GHz) 
    | Number of goroutines: 100000 
    | Per goroutine: 
    | Memory: 4536.84 bytes 
    | Time: 1.634248 µs 
    64-bit x86 CPU (A10-7850K 4GHz) 
    | Number of goroutines: 100000 
    | Per goroutine: 
    | Memory: 4707.92 bytes 
    | Time: 1.842097 µs 

Go release.r60.3 (December 2011) 
    32-bit x86 CPU (1.6 GHz) 
    | Number of goroutines: 100000 
    | Per goroutine: 
    | Memory: 4243.45 bytes 
    | Time: 5.815950 µs 

Trên máy có 4 G B bộ nhớ được cài đặt, điều này giới hạn số lượng tối đa của goroutines đến ít hơn 1 triệu.


Source code (không cần phải đọc này nếu bạn đã hiểu được các con số in trên):

package main 

import (
    "flag" 
    "fmt" 
    "os" 
    "runtime" 
    "time" 
) 

var n = flag.Int("n", 1e5, "Number of goroutines to create") 

var ch = make(chan byte) 
var counter = 0 

func f() { 
    counter++ 
    <-ch // Block this goroutine 
} 

func main() { 
    flag.Parse() 
    if *n <= 0 { 
      fmt.Fprintf(os.Stderr, "invalid number of goroutines") 
      os.Exit(1) 
    } 

    // Limit the number of spare OS threads to just 1 
    runtime.GOMAXPROCS(1) 

    // Make a copy of MemStats 
    var m0 runtime.MemStats 
    runtime.ReadMemStats(&m0) 

    t0 := time.Now().UnixNano() 
    for i := 0; i < *n; i++ { 
      go f() 
    } 
    runtime.Gosched() 
    t1 := time.Now().UnixNano() 
    runtime.GC() 

    // Make a copy of MemStats 
    var m1 runtime.MemStats 
    runtime.ReadMemStats(&m1) 

    if counter != *n { 
      fmt.Fprintf(os.Stderr, "failed to begin execution of all goroutines") 
      os.Exit(1) 
    } 

    fmt.Printf("Number of goroutines: %d\n", *n) 
    fmt.Printf("Per goroutine:\n") 
    fmt.Printf(" Memory: %.2f bytes\n", float64(m1.Sys-m0.Sys)/float64(*n)) 
    fmt.Printf(" Time: %f µs\n", float64(t1-t0)/float64(*n)/1e3) 
} 
+2

Chuyển đổi của bạn từ ~ 4k/mỗi goroutine (điều này đã thay đổi từ bản phát hành sang bản phát hành; và bạn cũng cần phải tính đến việc sử dụng chồng goroutine) vào một maxium dựa trên bộ nhớ được cài đặt là thiếu sót. Tối đa sẽ dựa trên bộ nhớ ảo nhỏ hơn (thường là 2-3GB cho hệ điều hành 32 bit), hoặc bộ nhớ vật lý * cộng * không gian hoán đổi có sẵn, hoặc giới hạn tài nguyên bộ nhớ của quá trình (thường là không giới hạn). Ví dụ. trên một máy 64bit với thiết lập swap sane bộ nhớ vật lý được cài đặt là không thích hợp với bất kỳ * giới hạn * (nhưng hiệu suất sẽ giảm khi trao đổi bắt đầu xảy ra). –

+0

Tôi nghĩ rằng điều này có chứa một điều kiện chủng tộc, vì không có đồng bộ hóa rõ ràng để đảm bảo tất cả các goroutines đã bắt đầu trước khi bộ đếm được so sánh với 'n'. Bạn may mắn mỗi lần? :) –

+2

sân chơi go báo cáo '2758,41 byte' trên mỗi goroutine, chạy đi 1.5.1. –

4

Điều đó phụ thuộc hoàn toàn vào hệ thống bạn đang chạy trên. Nhưng goroutines rất nhẹ. Một quá trình trung bình sẽ không có vấn đề với 100.000 thói quen đồng thời. Dù điều này có xảy ra cho nền tảng mục tiêu của bạn hay không, tất nhiên, điều mà chúng tôi không thể trả lời mà không biết nền tảng đó là gì.

+0

Bạn có gặp sự cố nào trên máy tính bảng dựa trên ARM không? – peterSO

+1

Vì tôi không có máy tính bảng ARM, tôi không thể nói. Điểm vẫn đứng vững. Không thể nói mà không biết hệ thống đích có thể làm gì. – jimt

+0

Nói cách khác, khiếu nại của bạn "không có vấn đề với 100.000 thói quen đồng thời" là vô nghĩa nếu không có ngữ cảnh thích hợp. – peterSO

3

Để diễn giải, có những lời nói dối, dối trá, và điểm chuẩn. Là tác giả của các benchmark Erlang thú nhận,

Nó đi mà không nói rằng không có đủ bộ nhớ còn lại trong máy để thực sự làm bất cứ điều gì hữu ích. stress-testing erlang

Phần cứng của bạn là gì, hệ điều hành của bạn, mã nguồn chuẩn của bạn là gì? Tiêu chuẩn cố gắng đo lường và chứng minh/bác bỏ là gì?

2

Dưới đây là một bài viết tuyệt vời bởi Dave Cheney về chủ đề này: http://dave.cheney.net/2013/06/02/why-is-a-goroutines-stack-infinite

+0

Lưu ý rằng bài viết được liên kết hơi lỗi thời. Vì Go1.2 đã có ['debug.SetMaxStack'] (https://golang.org/pkg/runtime/debug/#SetMaxStack) để ghi đè kích thước ngăn xếp tối đa mặc định cho mỗi goroutine" mới "là 1 GB và 250 MB (trên hệ thống 64 bit và 32 bit tương ứng). I E. kích thước chồng goroutine có ** không ** là vô hạn kể từ Go1.2. –

0

Nếu số goroutine bao giờ trở thành một vấn đề, bạn dễ dàng có thể giới hạn nó cho chương trình của bạn:
Xem mr51m0n/gorcthis example.

Set ngưỡng trên số chạy goroutines

có thể tăng và giảm một bộ đếm khi khởi động hoặc dừng một goroutine.
Nó có thể chờ đợi ở mức tối thiểu hoặc số lượng tối đa goroutines chạy, do đó cho phép thiết lập các ngưỡng cho số gorc goroutines chỉnh chạy cùng một lúc.

12

Hàng trăm ngàn người, mỗi Go FAQ: Why goroutines instead of threads?:

Đó là thực tế để tạo ra hàng trăm ngàn goroutines trong không gian địa chỉ tương tự.

Bài kiểm tra test/chan/goroutines.go tạo 10.000 và có thể dễ dàng làm nhiều hơn, nhưng được thiết kế để chạy nhanh; bạn có thể thay đổi số trên hệ thống của mình để thử nghiệm. Bạn có thể dễ dàng chạy hàng triệu, cho đủ bộ nhớ, chẳng hạn như trên máy chủ.

Để hiểu số lượng tối đa của goroutines, lưu ý rằng chi phí cho mỗi goroutine chủ yếu là ngăn xếp. Mỗi câu hỏi thường gặp một lần nữa:

… goroutines, có thể rất rẻ: chúng có ít chi phí vượt quá bộ nhớ cho ngăn xếp, chỉ là vài kilobyte.

Một back-of-the-phong bì tính toán là giả định rằng mỗi goroutine có một 4 KiB page phân bổ cho chồng (4 KiB là khá kích thước thống nhất), cộng với một số chi phí nhỏ cho một khối điều khiển (như a Thread Control Block) cho thời gian chạy; điều này đồng ý với những gì bạn đã quan sát (trong năm 2011, trước khi đi 1.0). Vì vậy, 100 Ki thói quen sẽ mất khoảng 400 MiB bộ nhớ, và 1 Mi thói quen sẽ mất khoảng 4 GiB bộ nhớ, mà vẫn có thể quản lý trên máy tính để bàn, một chút cho một chiếc điện thoại, và rất dễ quản lý trên một máy chủ. Trong thực tế, ngăn xếp bắt đầu đã thay đổi kích thước từ một nửa trang (2 KiB) đến hai trang (8 KiB), do đó, điều này là chính xác.

Kích thước ngăn xếp bắt đầu đã thay đổi theo thời gian; nó bắt đầu ở 4 KiB (một trang), sau đó trong 1,2 đã được tăng lên 8 KiB (2 trang), sau đó trong 1,4 đã giảm xuống còn 2 KiB (nửa trang). Những thay đổi này là do các ngăn xếp phân đoạn gây ra các vấn đề về hiệu suất khi chuyển đổi nhanh chóng giữa các phân đoạn ("chia ngăn nóng"), do đó tăng lên để giảm thiểu (1,2), sau đó giảm khi ngăn xếp phân đoạn được thay thế bằng ngăn xếp liền kề (1.4):

Go 1.2 Ghi chú Phát hành: Stack size:

trong Go 1.2, kích thước tối thiểu của ngăn xếp khi một goroutine được tạo ra đã được nâng lên từ 4KB để 8KB

Go 1,4 Ghi chú Phát hành: Changes to the runtime:

kích thước khởi động mặc định cho ngăn xếp của goroutine trong 1,4 đã được giảm từ 8192 byte xuống 2048 byte.

Bộ nhớ Per-goroutine chủ yếu là ngăn xếp, và nó bắt đầu thấp và phát triển để bạn có thể có nhiều goroutines rẻ. Bạn có thể sử dụng một ngăn xếp bắt đầu nhỏ hơn, nhưng sau đó nó sẽ phải phát triển sớm hơn (đạt được không gian với chi phí thời gian), và lợi ích giảm do khối điều khiển không co lại. Có thể loại bỏ ngăn xếp, ít nhất là khi được hoán đổi (ví dụ, thực hiện tất cả phân bổ trên heap, hoặc lưu ngăn xếp để đống trên chuyển đổi ngữ cảnh), mặc dù điều này làm tổn thương hiệu suất và làm tăng thêm độ phức tạp. Điều này là có thể (như trong Erlang), và có nghĩa là bạn chỉ cần khối điều khiển và ngữ cảnh đã lưu, cho phép một hệ số 5 × –10 × khác trong số các goroutine, bị giới hạn bởi kích thước khối điều khiển và kích thước trên goroutine biến địa phương. Tuy nhiên, điều này không hữu ích, trừ khi bạn cần hàng triệu con goroutines ngủ nhỏ.

Vì việc sử dụng chính có nhiều goroutin cho các nhiệm vụ liên quan đến IO (cụ thể là xử lý chặn các hệ thống, đặc biệt là mạng hoặc hệ thống tệp IO), bạn có nhiều khả năng chạy vào giới hạn OS trên các tài nguyên khác. ổ cắm hoặc tay cầm tập tin: golang-nuts › The max number of goroutines and file descriptors?. Cách thông thường để giải quyết vấn đề này là với pool của tài nguyên khan hiếm, hoặc đơn giản hơn là chỉ giới hạn số lượng thông qua số semaphore; xem Conserving File Descriptors in GoLimiting Concurrency in Go.

+1

[Hạn chế đồng thời đi] (http://jmoiron.net/blog/limiting-concurrency-in-go/) là một ví dụ rất hay và đơn giản – gabuzo

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