Đa luồng: Điểm của nhiều luồng hơn lõi là gì?


142

Tôi nghĩ rằng điểm của một máy tính đa lõi là nó có thể chạy nhiều luồng cùng lúc. Trong trường hợp đó, nếu bạn có một máy lõi tứ, thì điểm nào có nhiều hơn 4 luồng chạy cùng một lúc? Họ sẽ không đánh cắp thời gian (Tài nguyên CPU) với nhau chứ?


52
chúng tôi thích những câu hỏi kiểu này, họ đặt câu hỏi rất cơ bản của một cái gì đó, được coi là điều hiển nhiên .. hãy đến ..
Srinivas Reddy Thatiparthy

6
Lần cuối bạn có Firefox, MS Word, Winamp, Eclipse và trình quản lý tải xuống (hơn bốn chương trình / quy trình) chạy cùng lúc trên máy lõi tứ của bạn là khi nào? Ngoài ra, một ứng dụng đôi khi có thể sinh ra nhiều hơn bốn luồng - làm thế nào về điều đó?
Amarghosh

1
Ăn cắp không hẳn là xấu. Bạn có thể có một chủ đề với mức độ ưu tiên cao hơn cho các nhiệm vụ quan trọng cần đánh cắp thời gian.
kichik

1
@Amarghosh Tôi đoán đó là câu hỏi, tại sao một ứng dụng có thể muốn sinh ra nhiều luồng hơn lõi nếu nó dường như không mang lại bất kỳ lợi ích hiệu suất nào. Và ví dụ của bạn với hơn bốn chương trình không hoàn toàn phù hợp ở đây. Như bạn lưu ý chính xác, đó là những quá trình. Tính năng đa nhiệm của hệ điều hành (ghép kênh quy trình) có rất ít liên quan đến các luồng trong một tiến trình.
Alexanderr Ivannikov 20/03/18

Câu trả lời:


81

Câu trả lời xoay quanh mục đích của các luồng, đó là sự song song: để chạy một vài dòng thực thi riêng biệt cùng một lúc. Trong một hệ thống 'lý tưởng', bạn sẽ có một luồng thực thi trên mỗi lõi: không bị gián đoạn. Trong thực tế, đây không phải là trường hợp. Ngay cả khi bạn có bốn lõi và bốn luồng làm việc, tiến trình của bạn và các luồng của nó sẽ liên tục bị tắt cho các tiến trình và luồng khác. Nếu bạn đang chạy bất kỳ HĐH hiện đại nào, mọi tiến trình đều có ít nhất một luồng và nhiều luồng có nhiều hơn. Tất cả các quá trình này đang chạy cùng một lúc. Bạn có thể có hàng trăm luồng tất cả đang chạy trên máy của bạn ngay bây giờ. Bạn sẽ không bao giờ gặp phải tình huống một luồng chạy mà không có thời gian 'bị đánh cắp' từ nó. (Chà, bạn có thể nếu nó chạy thời gian thực, nếu bạn đang sử dụng HĐH thời gian thực hoặc thậm chí trên Windows, hãy sử dụng mức độ ưu tiên của luồng thời gian thực. Nhưng nó rất hiếm.)

Với nền tảng đó, câu trả lời: Có, hơn bốn luồng trên máy bốn lõi thực sự có thể mang đến cho bạn tình huống chúng 'đánh cắp thời gian của nhau', nhưng chỉ khi mỗi luồng riêng lẻ cần CPU 100% . Nếu một luồng không hoạt động 100% (vì một luồng UI có thể không hoặc một luồng thực hiện một lượng công việc nhỏ hoặc chờ đợi một thứ khác) thì một luồng khác được lên lịch thực sự là một tình huống tốt.

Nó thực sự phức tạp hơn thế:

  • Điều gì nếu bạn có năm bit công việc mà tất cả cần phải được thực hiện cùng một lúc? Nó có ý nghĩa hơn để chạy tất cả chúng cùng một lúc, hơn là chạy bốn trong số chúng và sau đó chạy thứ năm sau đó.

  • Thật hiếm khi một luồng thực sự cần CPU 100%. Ví dụ, thời điểm nó sử dụng I / O của đĩa hoặc mạng, nó có thể có khả năng dành thời gian chờ đợi mà không làm gì hữu ích. Đây là một tình huống rất phổ biến.

  • Nếu bạn có công việc cần phải chạy, một cơ chế phổ biến là sử dụng một luồng. Có vẻ như có ý nghĩa khi có cùng số lượng luồng như lõi, tuy nhiên, luồng xử lý .Net có tới 250 luồng có sẵn trên mỗi bộ xử lý . Tôi không chắc tại sao họ làm điều này, nhưng tôi đoán là sẽ làm với kích thước của các nhiệm vụ được đưa ra để chạy trên các luồng.

Vì vậy: đánh cắp thời gian không phải là một điều xấu (và cũng không thực sự là trộm cắp: đó là cách hệ thống được cho là hoạt động.) Viết các chương trình đa luồng của bạn dựa trên loại công việc mà các luồng sẽ làm, có thể không phải là CPU -Đến. Chỉ ra số lượng chủ đề bạn cần dựa trên hồ sơ và đo lường. Bạn có thể thấy hữu ích hơn khi suy nghĩ về các nhiệm vụ hoặc công việc, thay vì các chủ đề: viết các đối tượng của công việc và đưa chúng vào một nhóm để được chạy. Cuối cùng, trừ khi chương trình của bạn thực sự quan trọng về hiệu suất, đừng quá lo lắng :)


16
+1 cho "nhưng chỉ khi mỗi luồng riêng lẻ cần 100% CPU". Đó là giả định tôi không nhận ra mình đang thực hiện.
Nick Heiner

Một câu trả lời tuyệt vời cho một câu hỏi tuyệt vời. Cảm ơn bạn!
Edgecase

53

Chỉ vì một luồng tồn tại không có nghĩa là nó luôn hoạt động. Nhiều ứng dụng của các luồng liên quan đến một số luồng sẽ đi ngủ cho đến khi chúng phải làm gì đó - ví dụ, các luồng kích hoạt đầu vào của người dùng để đánh thức, xử lý và quay trở lại giấc ngủ.

Về cơ bản, các luồng là các tác vụ riêng lẻ có thể hoạt động độc lập với nhau, không cần phải biết về tiến trình của một tác vụ khác. Hoàn toàn có thể có nhiều thứ trong số này hơn là bạn có khả năng chạy đồng thời; chúng vẫn hữu ích cho sự thuận tiện ngay cả khi đôi khi chúng phải xếp hàng chờ nhau.


11
Nói hay lắm. Đối số 'một luồng trên mỗi CPU' chỉ áp dụng cho mã giới hạn CPU. Lập trình không đồng bộ là một lý do khác để sử dụng các chủ đề.
Joshua Davis

26

Vấn đề là, mặc dù không nhận được bất kỳ sự tăng tốc thực sự nào khi số lượng luồng vượt quá số lượng lõi, bạn có thể sử dụng các luồng để loại bỏ các đoạn logic không cần phải phụ thuộc lẫn nhau.

Ngay cả trong một ứng dụng phức tạp vừa phải, sử dụng một luồng duy nhất sẽ cố gắng thực hiện mọi thứ một cách nhanh chóng để tạo ra "dòng chảy" mã của bạn. Chủ đề duy nhất dành phần lớn thời gian để thăm dò ý kiến ​​này, kiểm tra điều đó, gọi các thói quen một cách có điều kiện khi cần thiết, và thật khó để nhìn thấy bất cứ điều gì ngoại trừ một chút vụn vặt.

Đối chiếu điều này với trường hợp bạn có thể dành các luồng cho các tác vụ để khi nhìn vào bất kỳ luồng nào, bạn có thể thấy luồng đó đang làm gì. Chẳng hạn, một luồng có thể chặn chờ đầu vào từ ổ cắm, phân tích luồng thành tin nhắn, lọc tin nhắn và khi có thông báo hợp lệ xuất hiện, hãy chuyển nó sang một số luồng công nhân khác. Chuỗi công nhân có thể làm việc trên các đầu vào từ một số nguồn khác. Mã cho mỗi trong số này sẽ thể hiện một luồng rõ ràng, có mục đích, mà không cần phải kiểm tra rõ ràng rằng không có gì khác để làm.

Phân vùng công việc theo cách này cho phép ứng dụng của bạn dựa vào hệ điều hành để lên lịch cho những việc cần làm tiếp theo với cpu, do đó bạn không phải thực hiện kiểm tra có điều kiện rõ ràng ở mọi nơi trong ứng dụng của mình về những gì có thể chặn và những gì đã sẵn sàng để xử lý.


1
Đây là một suy nghĩ thú vị ... Tôi luôn nghe nói rằng đa ứng dụng là một sự bổ sung phức tạp, nhưng những gì bạn đang nói có ý nghĩa.
Nick Heiner

Đa luồng một ứng dụng làm tăng thêm độ phức tạp nếu mối quan tâm của nó không được phân tách đầy đủ. Nếu nó được thiết kế với sự chồng chéo tối thiểu của các mối quan tâm (và do đó là trạng thái chia sẻ) thì đó là một sự tiết kiệm ròng trong các vấn đề phức tạp.
CHỈ CẦN HOẠT ĐỘNG CỦA TÔI NGÀY

Có nhiều cách để cấu trúc các ứng dụng đơn luồng để luồng điều khiển rõ ràng hơn ở cấp độ bạn viết chương trình. OTOH, nếu bạn có thể cấu trúc các chủ đề của mình để chúng chỉ truyền tin nhắn cho nhau (thay vì có tài nguyên được chia sẻ) thì thật đơn giản để tìm ra những gì đang diễn ra và khiến mọi thứ hoạt động.
Donal Fellows

1
Tuy nhiên, nên chỉ ra rằng việc sử dụng các chủ đề chỉ có thể đơn giản hóa mọi thứ đến một điểm nhất định. Tất cả quá thường xuyên, nỗ lực được thực hiện để làm cho hai luồng thực hiện công việc nên được thực hiện đúng bởi một, trong đó sự phức tạp trở lại trong các nhịp. Các triệu chứng của điều này là nhu cầu quá mức cho giao tiếp và đồng bộ hóa để phối hợp một số kết quả mong muốn.
JustJeff

15

Nếu một luồng đang chờ một tài nguyên (chẳng hạn như tải một giá trị từ RAM vào một thanh ghi, I / O đĩa, truy cập mạng, khởi chạy một quy trình mới, truy vấn cơ sở dữ liệu hoặc đợi đầu vào của người dùng), bộ xử lý có thể hoạt động trên một luồng khác nhau và trở về luồng đầu tiên sau khi tài nguyên có sẵn. Điều này giúp giảm thời gian CPU không hoạt động, vì CPU có thể thực hiện hàng triệu hoạt động thay vì ngồi không.

Xem xét một chủ đề cần đọc dữ liệu từ một ổ đĩa cứng. Trong năm 2014, lõi xử lý điển hình hoạt động ở tốc độ 2,5 GHz và có thể thực hiện 4 lệnh mỗi chu kỳ. Với thời gian chu kỳ là 0,4 ns, bộ xử lý có thể thực hiện 10 lệnh mỗi nano giây. Với thời gian tìm kiếm ổ cứng cơ học thông thường là khoảng 10 mili giây, bộ xử lý có khả năng thực hiện 100 triệu hướng dẫn trong thời gian cần thiết để đọc giá trị từ ổ cứng. Có thể có những cải tiến hiệu suất đáng kể với các ổ đĩa cứng có bộ đệm nhỏ (bộ đệm 4 MB) và ổ đĩa lai có dung lượng lưu trữ vài GB, vì độ trễ dữ liệu để đọc hoặc đọc tuần tự từ phần lai có thể nhanh hơn vài bậc.

Lõi bộ xử lý có thể chuyển đổi giữa các luồng (chi phí để tạm dừng và tiếp tục một luồng là khoảng 100 chu kỳ xung nhịp) trong khi luồng đầu tiên chờ đầu vào có độ trễ cao (bất cứ thứ gì đắt hơn đăng ký (1 xung nhịp) và RAM (5 nano giây) I / O đĩa, truy cập mạng (độ trễ 250ms), đọc dữ liệu từ đĩa CD hoặc bus chậm hoặc cuộc gọi cơ sở dữ liệu. Có nhiều luồng hơn lõi có nghĩa là công việc hữu ích có thể được thực hiện trong khi các nhiệm vụ có độ trễ cao được giải quyết.

CPU có một bộ lập lịch luồng chỉ định mức độ ưu tiên cho mỗi luồng và cho phép một luồng ngủ, sau đó tiếp tục lại sau một thời gian định trước. Công việc của bộ lập lịch xử lý là giảm bớt việc đập, điều này sẽ xảy ra nếu mỗi luồng thực hiện chỉ 100 lệnh trước khi được đưa vào chế độ ngủ lại. Chi phí hoạt động của các luồng chuyển đổi sẽ làm giảm tổng thông lượng hữu ích của lõi bộ xử lý.

Vì lý do này, bạn có thể muốn chia nhỏ vấn đề của mình thành số lượng chủ đề hợp lý. Nếu bạn đang viết mã để thực hiện phép nhân ma trận, việc tạo một luồng trên mỗi ô trong ma trận đầu ra có thể là quá mức, trong khi một luồng trên mỗi hàng hoặc mỗi n hàng trong ma trận đầu ra có thể giảm chi phí tạo ra, tạm dừng và tiếp tục các luồng.

Đây cũng là lý do tại sao dự đoán chi nhánh là quan trọng. Nếu bạn có một câu lệnh if yêu cầu tải một giá trị từ RAM nhưng phần thân của câu lệnh if và khác sử dụng các giá trị đã được tải vào các thanh ghi, bộ xử lý có thể thực thi một hoặc cả hai nhánh trước khi điều kiện được đánh giá. Khi điều kiện trả về, bộ xử lý sẽ áp dụng kết quả của nhánh tương ứng và loại bỏ nhánh kia. Thực hiện công việc có khả năng vô dụng ở đây có lẽ tốt hơn là chuyển sang một chủ đề khác, điều này có thể dẫn đến đập.

Khi chúng tôi chuyển từ bộ xử lý lõi đơn tốc độ cao sang bộ xử lý đa lõi, thiết kế chip đã tập trung vào việc nhồi nhét nhiều lõi hơn mỗi lần chết, cải thiện việc chia sẻ tài nguyên trên chip giữa các lõi, thuật toán dự đoán nhánh tốt hơn, chuyển đổi luồng tốt hơn, và lập lịch trình tốt hơn.


điều tương tự cũng có thể được thực hiện với một luồng và một hàng đợi: \ thực sự có lợi ích gì khi có 80 luồng trên 2-4 lõi, chỉ cần có 2-4 lõi để ăn hết các nhiệm vụ ngay khi chúng đến và họ không có gì để làm?
Dmitry

8

Hầu hết các câu trả lời ở trên nói về hiệu suất và hoạt động đồng thời. Tôi sẽ tiếp cận điều này từ một góc độ khác.

Chúng ta hãy xem trường hợp của một chương trình mô phỏng thiết bị đầu cuối đơn giản. Bạn phải làm những điều sau đây:

  • xem các ký tự đến từ hệ thống từ xa và hiển thị chúng
  • xem những thứ đến từ bàn phím và gửi chúng đến hệ thống từ xa

(Trình giả lập thiết bị đầu cuối thực sự làm được nhiều hơn, bao gồm cả khả năng lặp lại nội dung bạn nhập vào màn hình, nhưng chúng tôi sẽ chuyển qua điều đó ngay bây giờ.)

Bây giờ vòng lặp để đọc từ xa rất đơn giản, theo mã giả sau đây:

while get-character-from-remote:
    print-to-screen character

Vòng lặp để giám sát bàn phím và gửi cũng đơn giản:

while get-character-from-keyboard:
    send-to-remote character

Tuy nhiên, vấn đề là bạn phải làm điều này đồng thời. Mã bây giờ phải trông giống như thế này nếu bạn không có luồng:

loop:
    check-for-remote-character
    if remote-character-is-ready:
        print-to-screen character
    check-for-keyboard-entry
    if keyboard-is-ready:
        send-to-remote character

Logic, ngay cả trong ví dụ đơn giản hóa có chủ ý này không tính đến sự phức tạp của truyền thông trong thế giới thực, cũng khá khó hiểu. Tuy nhiên, với luồng, ngay cả trên một lõi đơn, hai vòng mã giả có thể tồn tại độc lập mà không xen kẽ logic của chúng. Vì cả hai luồng sẽ chủ yếu là ràng buộc I / O, nên chúng không gây tải nặng cho CPU, mặc dù, nói đúng ra, sẽ lãng phí tài nguyên CPU hơn so với vòng lặp tích hợp.

Bây giờ tất nhiên việc sử dụng trong thế giới thực phức tạp hơn so với ở trên. Nhưng sự phức tạp của vòng lặp tích hợp tăng theo cấp số nhân khi bạn thêm nhiều mối quan tâm hơn vào ứng dụng. Logic trở nên phân mảnh hơn bao giờ hết và bạn phải bắt đầu sử dụng các kỹ thuật như máy trạng thái, coroutines, et al để có thể quản lý mọi thứ. Quản lý, nhưng không thể đọc được. Threading giữ cho mã dễ đọc hơn.

Vậy tại sao bạn không sử dụng luồng?

Chà, nếu các tác vụ của bạn bị ràng buộc CPU thay vì ràng buộc I / O, thì luồng thực sự làm chậm hệ thống của bạn. Hiệu suất sẽ bị. Rất nhiều, trong nhiều trường hợp. ("Đập" là một vấn đề phổ biến nếu bạn thả quá nhiều luồng bị ràng buộc CPU. Bạn sẽ mất nhiều thời gian hơn để thay đổi các luồng hoạt động hơn là bạn tự chạy nội dung của các luồng.) đơn giản đến mức tôi đã cố tình chọn một ví dụ đơn giản (và không thực tế). Nếu bạn muốn lặp lại những gì đã gõ vào màn hình thì bạn đã có một thế giới đau thương mới khi bạn giới thiệu khóa tài nguyên được chia sẻ. Chỉ với một tài nguyên được chia sẻ, đây không phải là vấn đề quá lớn, nhưng nó bắt đầu trở thành một vấn đề lớn hơn và lớn hơn khi bạn có nhiều tài nguyên hơn để chia sẻ.

Vì vậy, cuối cùng, luồng là về nhiều thứ. Ví dụ, đó là về việc làm cho các quy trình ràng buộc I / O phản ứng nhanh hơn (ngay cả khi tổng thể kém hiệu quả hơn) như một số người đã nói. Đó cũng là về việc làm cho logic dễ theo dõi hơn (nhưng chỉ khi bạn giảm thiểu trạng thái chia sẻ). Đó là về rất nhiều thứ, và bạn phải quyết định xem những ưu điểm của nó có vượt trội hơn những nhược điểm của nó trong từng trường hợp hay không.


6

Mặc dù bạn chắc chắn có thể sử dụng các luồng để tăng tốc tính toán tùy thuộc vào phần cứng của bạn, một trong những công dụng chính của chúng là làm nhiều việc một lúc vì lý do thân thiện với người dùng.

Ví dụ: nếu bạn phải thực hiện một số xử lý trong nền và vẫn đáp ứng với đầu vào UI, bạn có thể sử dụng các luồng. Không có chủ đề, giao diện người dùng sẽ bị treo mỗi khi bạn cố gắng thực hiện bất kỳ xử lý nặng nào.

Cũng xem câu hỏi liên quan này: Sử dụng thực tế cho chủ đề


Xử lý giao diện người dùng là một ví dụ cổ điển về nhiệm vụ ràng buộc IO. Thật không tốt khi có một lõi CPU duy nhất thực hiện cả hai tác vụ xử lý và IO.
Donal Fellows

6

Tôi hoàn toàn không đồng ý với khẳng định của @ kyoryu rằng con số lý tưởng là một luồng trên mỗi CPU.

Hãy nghĩ về nó theo cách này: tại sao chúng ta có các hệ điều hành đa xử lý? Trong hầu hết lịch sử máy tính, gần như tất cả các máy tính đều có một CPU. Tuy nhiên, từ những năm 1960 trở đi, tất cả các máy tính "thực" đều có hệ điều hành đa xử lý (hay còn gọi là đa tác vụ).

Bạn chạy nhiều chương trình để một chương trình có thể chạy trong khi các chương trình khác bị chặn cho những thứ như IO.

hãy đặt các đối số sang một bên về việc các phiên bản Windows trước NT có đa tác vụ hay không. Kể từ đó, mọi hệ điều hành thực sự đều có đa tác vụ. Một số người không tiếp xúc với người dùng, nhưng dù sao thì nó cũng làm những việc như nghe radio trên điện thoại di động, nói chuyện với chip GPS, chấp nhận nhập chuột, v.v.

Chủ đề chỉ là nhiệm vụ hiệu quả hơn một chút. Không có sự khác biệt cơ bản giữa một nhiệm vụ, quy trình và luồng.

CPU là một thứ khủng khiếp để lãng phí, vì vậy hãy chuẩn bị sẵn rất nhiều thứ khi bạn có thể.

Tôi sẽ đồng ý rằng với hầu hết các ngôn ngữ thủ tục, C, C ++, Java, v.v., viết mã an toàn chủ đề phù hợp là rất nhiều công việc. Với CPU 6 lõi trên thị trường hiện nay và CPU 16 lõi không còn xa nữa, tôi hy vọng rằng mọi người sẽ tránh xa các ngôn ngữ cũ này, vì đa luồng ngày càng trở thành một yêu cầu quan trọng.

Không đồng ý với @kyoryu chỉ là IMHO, phần còn lại là sự thật.


5
Nếu bạn có nhiều luồng xử lý giới hạn bộ xử lý , thì con số lý tưởng là một luồng trên mỗi CPU (hoặc có thể là một ít hơn, để lại một luồng để quản lý tất cả I / O và HĐH và tất cả những thứ đó). Nếu bạn có các luồng liên kết IO , bạn có thể xếp chồng khá nhiều trên một CPU. Các ứng dụng khác nhau có các hỗn hợp khác nhau của các tác vụ giới hạn bộ xử lý và ràng buộc IO; điều đó hoàn toàn tự nhiên, nhưng tại sao bạn phải cẩn thận với những tuyên bố phổ quát.
Donal Fellows

1
Tất nhiên, sự khác biệt quan trọng nhất giữa các luồng và tiến trình là trên Windows không có fork (), vì vậy việc tạo quy trình thực sự tốn kém, dẫn đến việc sử dụng quá mức các luồng.
ninjalj

Ngoại trừ việc gấp protein, SETI, v.v., không có tác vụ người dùng thực tế nào được tính toán ràng buộc rất lâu. Luôn có nhu cầu lấy thông tin từ người dùng, nói chuyện với đĩa, nói chuyện với DBMS, v.v ... Vâng, chi phí của fork () là một trong nhiều điều mà Cutler đã nguyền rủa NT với những người khác tại DEC biết.
fishtoprecords

5

Hãy tưởng tượng một máy chủ Web phải phục vụ một số lượng yêu cầu tùy ý. Bạn phải phục vụ các yêu cầu song song vì nếu không, mỗi yêu cầu mới phải đợi cho đến khi tất cả các yêu cầu khác được hoàn thành (bao gồm gửi phản hồi qua Internet). Trong trường hợp này, hầu hết các máy chủ web có số lõi ít ​​hơn số lượng yêu cầu họ thường phục vụ.

Nó cũng giúp nhà phát triển máy chủ dễ dàng hơn: Bạn chỉ phải viết một chương trình luồng phục vụ yêu cầu, bạn không phải suy nghĩ về việc lưu trữ nhiều yêu cầu, thứ tự bạn phục vụ, v.v.


2
Bạn đang viết phần mềm cho một hệ điều hành hỗ trợ luồng nhưng không có khả năng ghép kênh io? Tôi nghĩ rằng máy chủ web có lẽ là một ví dụ tồi vì trong trường hợp này, việc ghép kênh io hầu như sẽ luôn hiệu quả hơn so với việc tạo ra nhiều luồng hơn lõi.
Jason Coco

3

Nhiều luồng sẽ ngủ, chờ người dùng nhập, I / O và các sự kiện khác.


Chắc chắn. chỉ cần sử dụng Trình quản lý tác vụ trên Windows hoặc TOP trên HĐH thực và xem có bao nhiêu tác vụ / quy trình được thực hiện. Nó luôn luôn 90% trở lên.
fishtoprecords

2

Chủ đề có thể giúp đáp ứng trong các ứng dụng UI. Ngoài ra, bạn có thể sử dụng các chủ đề để có được nhiều công việc hơn từ lõi của bạn. Ví dụ, trên một lõi đơn, bạn có thể có một luồng thực hiện IO và một luồng khác thực hiện một số tính toán. Nếu nó là một luồng đơn, thì về cơ bản, lõi có thể không hoạt động khi chờ IO hoàn thành. Đó là một ví dụ cấp độ khá cao, nhưng các chủ đề chắc chắn có thể được sử dụng để đập cpu của bạn khó hơn một chút.


Cụ thể hơn, một luồng có thể chờ trên I / O trong khi một luồng khác tính toán. Nếu I / O mất các chu kỳ CPU (đáng kể), sẽ không có lợi ích gì khi chạy nó trong một luồng riêng biệt. Lợi ích là luồng tính toán của bạn có thể chạy trong khi luồng I / O của bạn đang vặn ngón tay cái chờ đợi một xi lanh nhôm lớn quay vào vị trí, hoặc cho các gói đến từ dây từ Iceland, hoặc bất cứ điều gì.
Ken

2

Bộ xử lý, hoặc CPU, là chip vật lý được cắm vào hệ thống. Một bộ xử lý có thể có nhiều lõi (lõi là một phần của chip có khả năng thực hiện các hướng dẫn). Một lõi có thể xuất hiện với hệ điều hành dưới dạng nhiều bộ xử lý ảo nếu nó có khả năng thực thi đồng thời nhiều luồng (một luồng là một chuỗi các lệnh).

Một quy trình là một tên khác cho một ứng dụng. Nói chung, các quá trình là độc lập với nhau. Nếu một quá trình chết, nó không gây ra quá trình khác cũng chết. Có thể cho các quá trình giao tiếp hoặc chia sẻ tài nguyên như bộ nhớ hoặc I / O.

Mỗi quá trình có một không gian địa chỉ và ngăn xếp riêng biệt. Một tiến trình có thể chứa nhiều luồng, mỗi luồng có thể thực hiện các lệnh cùng một lúc. Tất cả các luồng trong một tiến trình chia sẻ cùng một không gian địa chỉ, nhưng mỗi luồng sẽ có ngăn xếp riêng.

Hy vọng với những định nghĩa và nghiên cứu sâu hơn bằng cách sử dụng những nguyên tắc cơ bản này sẽ giúp bạn hiểu hơn.


2
Tôi không thấy làm thế nào điều này giải quyết câu hỏi của anh ấy cả. Giải thích của tôi về câu hỏi của anh ấy là về việc sử dụng các lõi và sử dụng tối ưu các tài nguyên có sẵn, hoặc về hành vi của các luồng khi bạn tăng số lượng của chúng, hoặc một cái gì đó dọc theo các dòng đó.
David

@David có lẽ đó không phải là câu trả lời trực tiếp cho câu hỏi của tôi, nhưng tôi vẫn cảm thấy mình đã học được bằng cách đọc nó.
Nick Heiner

1

Việc sử dụng lý tưởng của các chủ đề là, thực sự, một cho mỗi lõi.

Tuy nhiên, trừ khi bạn độc quyền sử dụng IO không đồng bộ / không chặn, rất có thể bạn sẽ có các luồng bị chặn trên IO tại một số điểm, điều này sẽ không sử dụng CPU của bạn.

Ngoài ra, các ngôn ngữ lập trình điển hình làm cho việc sử dụng 1 luồng trên mỗi CPU trở nên khó khăn hơn. Các ngôn ngữ được thiết kế xung quanh đồng thời (như Erlang) có thể giúp dễ dàng hơn khi không sử dụng các luồng bổ sung.


Sử dụng các luồng cho các tác vụ định kỳ là một quy trình công việc rất phổ biến và được hoan nghênh, và sẽ ít hơn nhiều so với lý tưởng nếu chúng đánh cắp một lõi.
Nick Bastin

@Nick Bastin: Có, nhưng sẽ hiệu quả hơn khi gắn các nhiệm vụ đó vào hàng đợi nhiệm vụ và thực hiện chúng từ hàng đợi đó (hoặc một chiến lược tương tự). Để có hiệu quả tối ưu, 1 luồng trên mỗi lõi sẽ đánh bại tất cả, vì nó ngăn chặn chi phí chuyển đổi ngữ cảnh không cần thiết và các ngăn xếp bổ sung được phân bổ. Dù thế nào, nhiệm vụ định kỳ phải đánh cắp một lõi trong khi 'hoạt động', vì cpu chỉ thực sự có thể thực hiện một nhiệm vụ trên mỗi lõi (cộng với những thứ như siêu phân luồng nếu có).
kyoryu

@Nick Bastin: Thật không may, như tôi đã nói trong câu trả lời chính, hầu hết các ngôn ngữ hiện đại không cho vay tốt để dễ dàng thực hiện một hệ thống mà điều này thực sự không tầm thường - cuối cùng bạn đã thực hiện một số cách sử dụng ngôn ngữ thông thường.
kyoryu

Quan điểm của tôi không phải là một luồng trên mỗi lõi không tối ưu, mà một luồng trên mỗi lõi là một giấc mơ ống (trừ khi bạn được nhúng) và thiết kế để cố gắng đánh nó là một sự lãng phí thời gian, vì vậy bạn cũng có thể hãy làm những gì giúp bạn dễ dàng (và dù sao cũng không hiệu quả hơn trên một trình lập lịch hiện đại), thay vì cố gắng tối ưu hóa số lượng chủ đề bạn đang sử dụng. Chúng ta có nên quay lên chủ đề không có lý do tốt? Tất nhiên là không, nhưng liệu bạn có lãng phí tài nguyên máy tính một cách không cần thiết hay không là vấn đề bất kể luồng.
Nick Bastin

@Nick Bastin: Vì vậy, để tóm tắt, một luồng trên mỗi lõi là lý tưởng, nhưng thực sự đạt được điều đó không có khả năng. Tôi có lẽ nên mạnh mẽ hơn 'hơi khó khăn' khi nói về khả năng thực sự đạt được điều đó.
kyoryu

1

Cách một số API được thiết kế, bạn không có lựa chọn nào khác ngoài việc chạy chúng trong một luồng riêng biệt (bất cứ điều gì có hoạt động chặn). Một ví dụ sẽ là các thư viện HTTP của Python (AFAIK).

Thông thường, đây không phải là vấn đề lớn (nếu đó là sự cố, HĐH hoặc API sẽ xuất xưởng với chế độ hoạt động không đồng bộ thay thế, ví dụ select(2):), bởi vì điều đó có thể có nghĩa là luồng sẽ ngủ trong khi chờ I / O hoàn thành. Mặt khác, nếu một cái gì đó đang thực hiện một tính toán nặng nề, bạn phải đặt nó trong một luồng riêng biệt hơn là luồng GUI (trừ khi bạn thích ghép kênh thủ công).


1

Tôi biết đây là một câu hỏi siêu cũ với nhiều câu trả lời hay, nhưng tôi ở đây để chỉ ra một điều quan trọng trong môi trường hiện tại:

Nếu bạn muốn thiết kế một ứng dụng cho đa luồng, bạn không nên thiết kế cho một cài đặt phần cứng cụ thể. Công nghệ CPU đã tiến bộ khá nhanh trong nhiều năm và số lượng lõi đang tăng đều đặn. Nếu bạn cố tình thiết kế ứng dụng của mình sao cho nó chỉ sử dụng 4 luồng, thì bạn có khả năng tự giới hạn mình trong một hệ thống octa-core (ví dụ). Bây giờ, ngay cả các hệ thống 20 lõi đều có sẵn trên thị trường, vì vậy một thiết kế như vậy chắc chắn gây hại nhiều hơn là tốt.


0

Đáp lại phỏng đoán đầu tiên của bạn: các máy đa lõi có thể chạy đồng thời nhiều tiến trình, không chỉ nhiều luồng của một tiến trình đơn lẻ.

Để trả lời câu hỏi đầu tiên của bạn: điểm của nhiều luồng thường là thực hiện đồng thời nhiều tác vụ trong một ứng dụng. Các ví dụ kinh điển trên mạng là chương trình gửi và nhận thư, và máy chủ web nhận và gửi yêu cầu trang. (Lưu ý rằng về cơ bản không thể giảm một hệ thống như Windows để chỉ chạy một luồng hoặc thậm chí chỉ một tiến trình. Chạy Trình quản lý tác vụ Windows và thông thường bạn sẽ thấy một danh sách dài các quy trình đang hoạt động, nhiều quy trình sẽ chạy nhiều luồng. )

Để trả lời cho câu hỏi thứ hai của bạn: hầu hết các quy trình / luồng không bị ràng buộc CPU (nghĩa là không chạy liên tục và không bị gián đoạn), nhưng thay vào đó hãy dừng lại và chờ đợi thường xuyên để I / O kết thúc. Trong thời gian chờ đợi đó, các quy trình / luồng khác có thể chạy mà không "đánh cắp" từ mã chờ (ngay cả trên một máy lõi đơn).


-5

Một luồng là một sự trừu tượng cho phép Bạn viết mã đơn giản như một chuỗi hoạt động, hoàn toàn không biết rằng mã được thực thi xen kẽ với mã khác, hoặc chờ đợi IO, hoặc (có thể nhận thức rõ hơn một chút) chờ đợi các luồng khác sự kiện hoặc tin nhắn.


Tôi có thể đã chỉnh sửa điều này bằng cách thêm nhiều ví dụ kể từ các downvote - nhưng một luồng (hoặc quá trình, trong bối cảnh này hầu như không có sự khác biệt) không được phát minh để tăng quy mô hiệu suất, mà là để đơn giản hóa mã không đồng bộ và tránh viết các máy trạng thái phức tạp đã phải xử lý tất cả các siêu trạng thái có thể có trong chương trình. Trong thực tế, thường có một CPU ngay cả trong các máy chủ lớn. Tôi, chỉ tò mò tại sao câu trả lời của tôi được coi là chống hữu ích?
KarlP

-8

Vấn đề là phần lớn các lập trình viên không hiểu cách thiết kế một máy trạng thái. Việc có thể đặt mọi thứ trong luồng riêng của nó giúp người lập trình không phải suy nghĩ về cách biểu diễn hiệu quả trạng thái của các tính toán đang thực hiện khác nhau để chúng có thể bị gián đoạn và sau đó được tiếp tục.

Ví dụ, xem xét nén video, một nhiệm vụ rất cpu. Nếu bạn đang sử dụng công cụ gui, có lẽ bạn muốn giao diện vẫn phản hồi (hiển thị tiến trình, trả lời các yêu cầu hủy, thay đổi kích thước cửa sổ, v.v.). Vì vậy, bạn thiết kế phần mềm mã hóa của mình để xử lý một đơn vị lớn (một hoặc nhiều khung hình) tại một thời điểm và chạy nó trong luồng riêng, tách biệt với UI.

Tất nhiên một khi bạn nhận ra rằng thật tuyệt khi có thể lưu trạng thái mã hóa đang tiến hành để bạn có thể đóng chương trình để khởi động lại hoặc chơi một trò chơi ngốn tài nguyên, bạn nhận ra rằng bạn nên học cách thiết kế các máy trạng thái từ bắt đầu Dù vậy, hoặc bạn quyết định thiết kế một vấn đề hoàn toàn mới về quá trình ngủ đông hệ điều hành của bạn để bạn có thể tạm dừng và tiếp tục các ứng dụng riêng lẻ vào đĩa ...


7
Không (khá!) Đáng giá -1, nhưng nghiêm túc mà nói, đó là về điều ngớ ngẩn ngu ngốc nhất mà tôi từng nghe bất cứ ai nói về chủ đề này. Tôi, ví dụ, không có vấn đề trong việc thực hiện một máy trạng thái. Không có gì cả. Tôi chỉ không thích sử dụng chúng khi các công cụ khác ở đó để lại mã rõ ràng hơndễ dàng hơn để duy trì mã. Máy nhà nước có vị trí của chúng, và ở những nơi đó chúng không thể phù hợp. Việc xen kẽ các hoạt động chuyên sâu của CPU với các bản cập nhật GUI không phải là một trong những nơi đó. Ít nhất các coroutines là một lựa chọn tốt hơn ở đó, với luồng thậm chí còn tốt hơn.
CHỈ CẦN HOẠT ĐỘNG CỦA TÔI NGÀY

Đối với mọi người đang sửa đổi câu trả lời của tôi, đây KHÔNG phải là một đối số chống lại việc sử dụng các chủ đề! Nếu bạn có thể mã hóa một máy trạng thái thật tuyệt vời và chắc chắn rằng việc chạy các máy trạng thái theo các luồng riêng biệt ngay cả khi bạn không phải làm vậy. Nhận xét của tôi là thường lựa chọn sử dụng các luồng được thực hiện chủ yếu vì mong muốn tránh thiết kế các máy trạng thái, điều mà nhiều lập trình viên cho là "quá khó", thay vì bất kỳ lợi ích nào khác.
R .. GitHub DỪNG GIÚP ICE
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.