Khi nào bạn cần hàng trăm ngàn hàng ngàn chủ đề?


31

Erlang, Go và Rust đều tuyên bố bằng cách này hay cách khác rằng họ hỗ trợ lập trình đồng thời với các "luồng" / coroutines giá rẻ. Câu hỏi thường gặp về Go :

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

Các Rust Tutorial nói:

Vì các tác vụ được tạo ra rẻ hơn đáng kể so với các luồng truyền thống, Rust có thể tạo ra hàng trăm nghìn tác vụ đồng thời trên hệ thống 32 bit thông thường.

Tài liệu của Erlang nói:

Kích thước heap ban đầu mặc định là 233 từ khá bảo thủ để hỗ trợ các hệ thống Erlang với hàng trăm nghìn hoặc thậm chí hàng triệu quy trình.

Câu hỏi của tôi: loại ứng dụng nào đòi hỏi nhiều luồng thực thi đồng thời? Chỉ những máy chủ web bận rộn nhất mới nhận được hàng ngàn khách truy cập đồng thời. Các ứng dụng loại sếp-công nhân / công việc mà tôi đã viết đã đạt được lợi nhuận giảm dần khi số lượng luồng / tiến trình lớn hơn nhiều so với số lõi vật lý. Tôi cho rằng nó có thể có ý nghĩa đối với các ứng dụng số, nhưng trong thực tế, hầu hết mọi người đều ủy thác song song cho các thư viện bên thứ ba được viết bằng Fortran / C / C ++, chứ không phải các ngôn ngữ thế hệ mới hơn này.


5
Tôi nghĩ rằng nguồn gốc của sự nhầm lẫn của bạn là đây: Những microthreads / task / etc này không chủ yếu thay thế cho các luồng / tiến trình hệ điều hành mà bạn nói đến, chúng không được sử dụng để phân chia một khối lớn của song song dễ dàng giữa một vài lõi (như bạn đã nhận xét chính xác, không có điểm nào có 100k luồng trên 4 lõi cho mục đích đó).
us2012

1
Vậy thì chúng có ý nghĩa gì? Có thể tôi là một người ngây thơ nhưng tôi chưa bao giờ gặp phải tình huống giới thiệu coroutines / etc sẽ đơn giản hóa một chương trình thực hiện một luồng. Và tôi đã có thể đạt được mức độ đồng thời "thấp" với các quy trình, mà trên Linux tôi có thể khởi chạy hàng trăm hoặc hàng nghìn mà không bị đổ mồ hôi.
dùng39019

Nó sẽ không có ý nghĩa để có nhiều nhiệm vụ thực sự làm việc. Điều đó không có nghĩa là bạn không thể có một số lượng lớn các nhiệm vụ mà hầu hết chỉ bị chặn chờ đợi điều gì đó xảy ra.
Loren Pechtel

5
Ý tưởng về sự không đồng bộ dựa trên nhiệm vụ so với sự không đồng bộ dựa trên luồng là để nói rằng mã người dùng nên tập trung vào các nhiệm vụ cần xảy ra thay vì quản lý các công nhân thực hiện các tác vụ đó. Hãy nghĩ về một chủ đề như một công nhân mà bạn thuê; Thuê một công nhân là tốn kém, và nếu bạn làm, bạn muốn họ làm việc chăm chỉ với càng nhiều nhiệm vụ càng tốt 100% thời gian. Rất nhiều hệ thống có thể được mô tả là có hàng trăm hoặc hàng nghìn nhiệm vụ đang chờ xử lý nhưng bạn không cần hàng trăm hoặc hàng nghìn công nhân.
Eric Lippert

Tiếp tục bình luận của @ EricLippert, có một số tình huống tồn tại hàng trăm ngàn nhiệm vụ. Ví dụ # 1: phân tách tác vụ song song dữ liệu, chẳng hạn như xử lý ảnh. Ví dụ # 2: một máy chủ hỗ trợ hàng trăm ngàn khách hàng, mỗi khách hàng có khả năng ra lệnh bất cứ lúc nào. Mỗi tác vụ sẽ yêu cầu "bối cảnh thực thi nhẹ" của riêng nó - khả năng ghi nhớ trạng thái của nó trong (giao thức truyền thông) và lệnh mà nó hiện đang thực thi, và một số thứ khác. Nhẹ là có thể miễn là mỗi có một ngăn xếp cuộc gọi nông.
rwong

Câu trả lời:


19

một trường hợp sử dụng - websockets:
vì websockets tồn tại lâu so với các yêu cầu đơn giản, trên một máy chủ bận rộn, rất nhiều websockets sẽ tích lũy theo thời gian. microthreads cung cấp cho bạn một mô hình khái niệm tốt và cũng là một triển khai tương đối dễ dàng.

nói chung, các trường hợp trong đó nhiều hoặc nhiều đơn vị tự trị đang chờ đợi một số sự kiện nhất định xảy ra sẽ là trường hợp sử dụng tốt.


15

Có thể giúp nghĩ về những gì Erlang ban đầu được thiết kế để làm, đó là quản lý viễn thông. Các hoạt động như định tuyến, chuyển mạch, thu thập / tổng hợp cảm biến, v.v.

Đưa điều này vào thế giới web - hãy xem xét một hệ thống như Twitter . Hệ thống có thể sẽ không sử dụng microthread trong việc tạo các trang web, nhưng nó có thể sử dụng chúng trong bộ sưu tập / lưu trữ / phân phối các tweet của nó.

Bài viết này có thể giúp đỡ thêm.


11

Trong một ngôn ngữ mà bạn không được phép sửa đổi các biến, hành động đơn giản duy trì trạng thái yêu cầu một bối cảnh thực thi riêng (mà hầu hết mọi người sẽ gọi một luồng và Erlang gọi một tiến trình). Về cơ bản, tất cả mọi thứ là một công nhân.

Hãy xem xét hàm Erlang này, duy trì bộ đếm:

counter(Value) ->
    receive                               % Sit idle until a message is received
        increment -> counter(Value + 1);  % Restart with incremented value
        decrement -> counter(Value - 1);  % Restart with decremented value
        speak     ->
            io:fwrite("~B~n", [Value]),
            counter(Value);               % Restart with unaltered value
        _         -> counter(Value)       % Anything else?  Do nothing.
    end.

Trong một ngôn ngữ OO thông thường như C ++ hoặc Java, bạn sẽ thực hiện điều này bằng cách có một lớp với một thành viên lớp riêng, các phương thức công khai để lấy hoặc thay đổi trạng thái của nó và một đối tượng được khởi tạo cho mỗi bộ đếm. Erlang thay thế khái niệm đối tượng được khởi tạo bằng một quy trình, khái niệm phương thức với thông điệp và duy trì trạng thái bằng các lệnh gọi đuôi khởi động lại hàm với bất kỳ giá trị nào tạo nên trạng thái mới. Lợi ích ẩn trong mô hình này - và hầu hết Erlang của raison d'être - là ngôn ngữ tự động serializes truy cập vào các giá trị truy cập thông qua việc sử dụng một hàng đợi thông điệp, làm cho mã đồng thời rất dễ thực hiện với một mức độ cao về an toàn .

Có lẽ bạn đã quen với ý tưởng rằng các chuyển đổi ngữ cảnh là đắt tiền, điều này vẫn đúng theo quan điểm của hệ điều hành máy chủ. Thời gian chạy Erlang tự nó là một hệ điều hành nhỏ được điều chỉnh để chuyển đổi giữa các quy trình của chính nó là nhanh chóng và hiệu quả, trong khi vẫn giữ cho số lượng bối cảnh chuyển đổi hệ điều hành xuống mức tối thiểu. Vì lý do này, việc có hàng ngàn quy trình không phải là vấn đề và được khuyến khích.


1
Ứng dụng cuối cùng của bạn counter/1nên sử dụng chữ thường c;) Tôi đã cố gắng sửa nó, nhưng StackExchange không giống như chỉnh sửa 1 ký tự.
d11wtq

4

Câu hỏi của tôi: loại ứng dụng nào đòi hỏi nhiều luồng thực thi đồng thời?

1) Thực tế là một ngôn ngữ "quy mô" có nghĩa là sẽ có ít cơ hội hơn để bạn bỏ ngôn ngữ đó khi mọi thứ trở nên phức tạp hơn. (Đây được gọi là khái niệm "Toàn bộ sản phẩm".) Nhiều người đang bỏ rơi Apache cho Nginx vì lý do này. Nếu bạn ở bất kỳ nơi nào gần với "giới hạn cứng" được áp đặt bởi chi phí trên luồng, bạn sẽ cảm thấy sợ hãi và bắt đầu suy nghĩ về cách vượt qua nó. Các trang web không bao giờ có thể dự đoán lưu lượng truy cập mà họ sẽ nhận được, vì vậy dành một chút thời gian để làm cho mọi thứ có thể mở rộng là hợp lý.

2) Một con goroutine cho mỗi yêu cầu chỉ là bắt đầu. Có rất nhiều lý do để sử dụng goroutines trong nội bộ.

  • Hãy xem xét một ứng dụng web với 100 yêu cầu đồng thời, nhưng mỗi yêu cầu tạo ra 100 yêu cầu phụ trợ. Ví dụ rõ ràng là một công cụ tổng hợp công cụ tìm kiếm. Nhưng bất kỳ ứng dụng nào cũng có thể tạo ra những con khỉ đột cho từng "khu vực" trên màn hình, sau đó tạo chúng một cách độc lập thay vì tuần tự. Ví dụ: mỗi trang trên Amazon.com được tạo thành từ hơn 150 yêu cầu back-end, được lắp ráp chỉ dành cho bạn. Bạn không để ý vì chúng song song, không tuần tự và mỗi "khu vực" là dịch vụ web riêng.
  • Xem xét bất kỳ ứng dụng nào mà độ tin cậy và độ trễ là tối quan trọng. Bạn có thể muốn mỗi yêu cầu đến sẽ loại bỏ một vài yêu cầu back-end và trả lại bất kỳ dữ liệu nào trở lại trước .
  • Xem xét bất kỳ "khách hàng tham gia" được thực hiện trong ứng dụng của bạn. Thay vì nói "cho mỗi yếu tố, lấy dữ liệu", bạn có thể loại bỏ một loạt các con khỉ đột. Nếu bạn có một loạt các DB nô lệ để truy vấn, bạn sẽ kỳ diệu đi N thời gian nhanh hơn. Nếu bạn không, nó sẽ không chậm hơn.

đạt lợi nhuận giảm dần khi số lượng luồng / tiến trình lớn hơn nhiều so với số lõi vật lý

Hiệu suất không phải là lý do duy nhất để chia nhỏ chương trình thành CSP . Nó thực sự có thể làm cho chương trình dễ hiểu hơn và một số vấn đề có thể được giải quyết với ít mã hơn.

Như trong các slide được liên kết ở trên, có đồng thời trong mã của bạn là một cách để tổ chức vấn đề. Không có goroutines giống như không có cấu trúc dữ liệu Map / Dictonary / Hash trong ngôn ngữ của bạn. Bạn có thể nhận được mà không có nó. Nhưng một khi bạn có nó, bạn bắt đầu sử dụng nó ở mọi nơi và nó thực sự đơn giản hóa chương trình của bạn.

Trong quá khứ, điều này có nghĩa là "cuộn" chương trình đa luồng của riêng bạn. Nhưng điều này rất phức tạp và nguy hiểm - vẫn không có nhiều công cụ để đảm bảo bạn không tạo ra các cuộc đua. Và làm thế nào để bạn ngăn chặn một người duy trì trong tương lai phạm sai lầm? Nếu bạn xem các chương trình lớn / phức tạp, bạn sẽ thấy họ tiêu tốn rất nhiều tài nguyên theo hướng đó.

Vì đồng thời không phải là một phần hạng nhất của hầu hết các ngôn ngữ, các lập trình viên ngày nay có một điểm mù về lý do tại sao nó sẽ hữu ích cho họ. Điều này sẽ chỉ trở nên rõ ràng hơn khi mọi điện thoại và đồng hồ đeo tay hướng tới 1000 lõi. Đi tàu với một công cụ phát hiện cuộc đua tích hợp.


2

Đối với Erlang, thông thường có một quy trình cho mỗi kết nối hoặc tác vụ khác. Vì vậy, ví dụ một máy chủ âm thanh phát trực tuyến có thể có 1 quy trình cho mỗi người dùng được kết nối.

Erlang VM được tối ưu hóa để xử lý hàng ngàn hoặc thậm chí hàng trăm nghìn quy trình bằng cách chuyển đổi ngữ cảnh rất rẻ.


1

Tiện. Quay lại khi tôi bắt đầu làm lập trình đa luồng, tôi đã thực hiện rất nhiều mô phỏng và phát triển trò chơi ở bên cạnh cho vui. Tôi thấy nó rất tiện lợi khi chỉ cần quay ra một luồng cho mỗi đối tượng và để nó tự làm việc đó thay vì xử lý từng đối tượng thông qua một vòng lặp. Nếu mã của bạn không bị xáo trộn bởi hành vi không xác định và bạn không có xung đột, nó có thể giúp mã hóa dễ dàng hơn. Với sức mạnh sẵn có cho chúng ta bây giờ, nếu tôi quay trở lại, tôi có thể dễ dàng tưởng tượng ra một vài nghìn luồng do có đủ sức mạnh xử lý và bộ nhớ để xử lý nhiều đối tượng rời rạc đó!


1

Một ví dụ đơn giản cho Erlang, được thiết kế để liên lạc: chuyển các gói mạng. Khi bạn thực hiện một yêu cầu http, bạn có thể có hàng ngàn gói TCP / IP. Thêm vào đó là mọi người kết nối cùng một lúc và bạn có trường hợp sử dụng của mình.

Xem xét nhiều ứng dụng được sử dụng nội bộ bởi bất kỳ công ty lớn nào để xử lý các đơn đặt hàng của họ hoặc bất cứ điều gì họ có thể cần. Máy chủ web không phải là thứ duy nhất cần chủ đề.


-2

Một số nhiệm vụ kết xuất mùa xuân đến tâm trí ở đây. Nếu bạn đang thực hiện một chuỗi ops dài trên mỗi pixel của hình ảnh và nếu các op đó có thể song song, thì ngay cả một hình ảnh 1024x768 tương đối nhỏ cũng nằm ngay trong khung "hàng trăm ngàn".


2
Vài năm trước, tôi đã dành vài năm để xử lý hình ảnh FLIR thời gian thực, xử lý hình ảnh 256x256 với tốc độ 30 khung hình mỗi giây. Trừ khi bạn có RẤT NHIỀU bộ xử lý PHẦN MỀM và cách SEAMLESS phân vùng dữ liệu của bạn trong số chúng, điều LAST bạn muốn làm là thêm chuyển đổi ngữ cảnh, tranh chấp bộ nhớ và xóa bộ nhớ cache vào chi phí tính toán thực tế.
John R. Strohm

Nó phụ thuộc vào công việc đang được thực hiện. Nếu tất cả những gì bạn đang làm là bàn giao công việc cho một đơn vị thực thi / lõi phần cứng, sau đó bạn có thể quên nó một cách hiệu quả (và lưu ý rằng đây là cách GPU hoạt động nên đây không phải là một tình huống giả định), thì cách tiếp cận là có hiệu lực.
Maximus Minimus
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.