Làm thế nào để giải thích tại sao đa luồng là khó khăn


84

Tôi là một lập trình viên khá giỏi, sếp của tôi cũng là một lập trình viên khá giỏi. Mặc dù anh ta dường như đánh giá thấp một số nhiệm vụ như đa luồng và mức độ khó của nó (tôi thấy rất khó cho mọi thứ hơn là chạy một vài luồng, đợi tất cả kết thúc, sau đó trả về kết quả).

Khoảnh khắc bạn bắt đầu phải lo lắng về những bế tắc và điều kiện chủng tộc, tôi thấy rất khó khăn, nhưng ông chủ dường như không đánh giá cao điều này - tôi không nghĩ rằng anh ấy đã từng gặp phải điều này. Chỉ cần tát một khóa vào nó là khá nhiều thái độ.

Vì vậy, làm thế nào tôi có thể giới thiệu anh ta, hoặc giải thích lý do tại sao anh ta có thể đánh giá thấp sự phức tạp của đồng thời, song song và đa luồng? Hoặc có thể tôi sai?

Chỉnh sửa: Chỉ cần một chút về những gì anh ta đã làm - lặp qua một danh sách, cho mỗi mục trong danh sách đó tạo ra một luồng thực hiện lệnh cập nhật cơ sở dữ liệu dựa trên thông tin trong mục đó. Tôi không chắc anh ấy đã kiểm soát bao nhiêu luồng được thực thi cùng một lúc, tôi đoán anh ấy đã thêm chúng vào hàng đợi nếu có quá nhiều hoạt động (anh ấy sẽ không sử dụng một semaphore).


17
Đa luồng rất dễ dàng. Đồng bộ hóa đúng là khó.
Vineet Reynold

33
Đưa ba người vào phòng, tốt nhất là với các điểm nhấn khác nhau và yêu cầu họ giải thích các phần khác nhau, chồng chéo của vấn đề đồng thời .... đồng thời.
greyfade

Đa luồng có thể rất khó hoặc rất dễ, tùy thuộc vào vấn đề trong tay và hỗ trợ ngôn ngữ. Clojure làm cho nó dễ dàng clojure.org/concien_programming
Công việc

4
@Job Lập trình đồng thời luôn khó khăn (trong các dự án trong thế giới thực), bất kể bạn đang sử dụng ngôn ngữ nào. Scala, Clojure hoặc Erlang làm cho nó có một chút lành mạnh khi bạn muốn so sánh nó với các ngôn ngữ sử dụng và khuyến khích các trạng thái có thể thay đổi.
Chiron

4
Phép ẩn dụ yêu thích của tôi cho điều này là: "Bạn có uống thuốc ngủ và thuốc nhuận tràng cùng một lúc không?" Ngay cả khi sử dụng hàng đợi tin nhắn phức tạp, trật tự là kết quả của sự tương tranh được thực hiện ngay . Đó là, trừ khi bạn có một lớn thỏa thuận kinh nghiệm với nó, là khó khăn đối với nhiều người.
Tim Post

Câu trả lời:


29
  1. Nếu bạn có thể tin tưởng vào bất kỳ trải nghiệm toán học nào, hãy minh họa cách một luồng thực thi bình thường về cơ bản mang tính quyết định không chỉ là không đặc biệt với một số luồng, mà còn phức tạp theo cấp số nhân , bởi vì bạn phải chắc chắn rằng mọi hướng dẫn xen kẽ có thể của máy sẽ vẫn làm đúng. Một ví dụ đơn giản về một bản cập nhật bị mất hoặc tình huống đọc bẩn thường là một cái mở mắt.

  2. "Tát một khóa trên nó" giải pháp tầm thường ... nó giải quyết tất cả các vấn đề của bạn nếu bạn không quan tâm đến hiệu suất. Cố gắng minh họa mức độ hiệu suất sẽ đạt được nếu, chẳng hạn, Amazon phải khóa toàn bộ bờ biển phía đông bất cứ khi nào ai đó ở Atlanta đặt mua một cuốn sách!


1
+1 cho cuộc thảo luận về độ phức tạp toán học - đây là cách tôi hiểu được sự khó khăn trong việc đồng thời trạng thái chia sẻ và là lý lẽ tôi thường đưa ra trong việc ủng hộ các kiến ​​trúc truyền thông điệp. -1 cho "tát một khóa vào nó" ... Cụm từ này bao hàm một cách tiếp cận không suy nghĩ đối với việc sử dụng các khóa, rất có thể sẽ dẫn đến bế tắc hoặc hành vi không nhất quán (vì các khách hàng của mã của bạn sống trong các luồng khác nhau tạo ra xung đột các yêu cầu, nhưng không đồng bộ hóa giữa chúng, các máy khách sẽ có các mô hình không tương thích về trạng thái thư viện của bạn).
Aidan Cully

2
Amazon không phải khóa hàng tồn kho của một mục cá nhân tại một nhà kho một thời gian ngắn trong khi xử lý một trật tự. Nếu có sự đột ngột, rất lớn đối với một mặt hàng cụ thể, hiệu suất đặt hàng cho mặt hàng đó sẽ bị ảnh hưởng cho đến khi nguồn cung cạn kiệt và quyền truy cập vào kho lưu trữ trở thành chỉ đọc (và do đó có thể chia sẻ 100%). Một điều mà Amazon đã nói rằng các chương trình khác không có khả năng xếp hàng đơn hàng cho đến khi xảy ra tình trạng tồn kho lại và tùy chọn phục vụ các đơn hàng xếp hàng trước khi có sẵn một kho cho các đơn hàng mới.
Blrfl

@Blrfl: Các chương trình có thể làm điều đó nếu chúng được viết để sử dụng tin nhắn truyền qua hàng đợi. Không cần phải có tất cả các tin nhắn đến một chuỗi cụ thể đi qua một hàng đợi đơn lẻ
Donal Fellows

4
@Donal Fellows: Nếu có 1M widget trong kho và một đơn hàng 1M đến cùng một lúc, tất cả các yêu cầu đó được tuần tự hóa ở một mức độ nào đó trong khi khớp các mặt hàng với đơn hàng cho dù chúng được xử lý như thế nào. Thực tế là Amazon có thể không bao giờ có quá nhiều vật dụng trong kho đến nỗi độ trễ trong việc xử lý một đơn đặt hàng trở nên cao không thể chấp nhận được trước khi hết hàng tồn kho và mọi người khác có thể nói (song song), "chúng tôi đã hết. " Hàng đợi tin nhắn là một cách tuyệt vời để ngăn chặn bế tắc, nhưng chúng không giải quyết được vấn đề tranh chấp cao đối với một nguồn lực hạn chế.
Blrfl

79

Đa luồng đơn giản. Mã hóa một ứng dụng cho đa luồng là rất, rất dễ dàng.

Có một mẹo đơn giản và đây là sử dụng hàng đợi tin nhắn được thiết kế tốt ( không tự cuộn) để truyền dữ liệu giữa các luồng.

Phần cứng đang cố gắng có nhiều luồng cập nhật một cách kỳ diệu một đối tượng được chia sẻ theo một cách nào đó. Đó là khi nó dễ bị lỗi vì mọi người không chú ý đến các điều kiện chủng tộc hiện có.

Nhiều người không sử dụng hàng đợi tin nhắn và cố gắng cập nhật các đối tượng được chia sẻ và tự tạo ra vấn đề.

Điều trở nên khó khăn là thiết kế một thuật toán hoạt động tốt khi truyền dữ liệu giữa một số hàng đợi. Điều đó thật khó khăn. Nhưng các cơ chế của các chủ đề cùng tồn tại (thông qua hàng đợi được chia sẻ) là dễ dàng.

Ngoài ra, lưu ý rằng các chủ đề chia sẻ tài nguyên I / O. Một chương trình ràng buộc I / O (nghĩa là các kết nối mạng, hoạt động tệp hoặc hoạt động cơ sở dữ liệu) dường như không thể đi nhanh hơn với nhiều luồng.

Nếu bạn muốn minh họa vấn đề cập nhật đối tượng chia sẻ, điều đó thật đơn giản. Ngồi qua bàn với một loạt các thẻ giấy. Viết ra một bộ tính toán đơn giản - 4 hoặc 6 công thức đơn giản - với rất nhiều chỗ trên trang.

Đây là trò chơi. Mỗi bạn đọc một công thức, viết một câu trả lời và đặt một thẻ xuống với câu trả lời.

Mỗi bạn sẽ làm một nửa công việc, phải không? Bạn đã hoàn thành trong một nửa thời gian, phải không?

Nếu sếp của bạn không suy nghĩ nhiều và chỉ bắt đầu, bạn sẽ giải quyết xung đột theo một cách nào đó và cả hai đều viết câu trả lời cho cùng một công thức. Điều đó không hiệu quả vì có một điều kiện chủng tộc cố hữu giữa cả hai bạn đọc trước khi viết. Không có gì ngăn bạn đọc cả hai công thức giống nhau và ghi đè lên câu trả lời của nhau.

Có rất nhiều, rất nhiều cách để tạo điều kiện cuộc đua với tài nguyên không tốt hoặc không bị khóa.

Nếu bạn muốn tránh tất cả các xung đột, bạn cắt giấy thành một chồng công thức. Bạn rời khỏi hàng đợi, viết câu trả lời và đăng câu trả lời. Không có xung đột vì cả hai bạn đều đọc từ hàng đợi tin nhắn chỉ có một người đọc.


Ngay cả việc cắt giấy thành một chồng cũng không hoàn toàn sửa chữa mọi thứ - bạn vẫn có tình huống bạn và sếp của bạn tiếp cận với một công thức mới cùng một lúc và bạn đập ngón tay vào ngón tay của anh ấy. Trong thực tế, tôi sẽ nói rằng đây là đại diện của loại vấn đề luồng phổ biến nhất. Các lỗi thực sự thô được tìm thấy sớm. Các lỗi thực sự bất thường tồn tại mãi mãi bởi vì không ai có thể tái tạo chúng, Các điều kiện chủng tộc hợp lý - như thế này - tiếp tục cắt xén trong thử nghiệm, và cuối cùng tất cả (hoặc nhiều khả năng nhất) đều bị loại bỏ.
Airsource Ltd

@Airsource. Chính xác thì bạn đang nói gì khi "đập ngón tay vào ngón tay của anh ấy"? Miễn là bạn có một hàng đợi tin nhắn ngăn hai luồng khác nhau nhận cùng một tin nhắn, đó sẽ không phải là vấn đề. Trừ khi tôi hiểu nhầm ý của bạn.
Zack

25

Lập trình đa luồng có lẽ là giải pháp khó nhất để tương tranh. Về cơ bản, nó là một sự trừu tượng hóa ở mức độ thấp của những gì máy thực sự làm.

Có một số cách tiếp cận, chẳng hạn như mô hình diễn viên hoặc bộ nhớ giao dịch (phần mềm) , dễ dàng hơn nhiều. Hoặc làm việc với các cấu trúc dữ liệu bất biến (như danh sách và cây).

Nói chung, một sự tách biệt thích hợp làm cho đa luồng dễ dàng hơn. Một cái gì đó, đó là tất cả thường bị lãng quên, khi mọi người sinh ra 20 luồng, tất cả đều cố gắng xử lý cùng một bộ đệm. Sử dụng các lò phản ứng trong đó bạn cần đồng bộ hóa và thường truyền dữ liệu giữa các nhân viên khác nhau với hàng đợi tin nhắn.
Nếu bạn có một khóa trong logic ứng dụng của bạn, bạn đã làm sai điều gì đó.

Vì vậy, có, về mặt kỹ thuật, đa luồng là khó khăn.
"Tát một khóa trên nó" là giải pháp có khả năng mở rộng ít nhất cho các vấn đề tương tranh và thực sự đánh bại toàn bộ mục đích của đa luồng. Những gì nó làm là hoàn nguyên một vấn đề trở lại mô hình thực thi không đồng thời. Bạn càng làm điều đó, càng có nhiều khả năng, bạn chỉ có một luồng chạy tại thời điểm đó (hoặc 0 trong một bế tắc). Nó đánh bại toàn bộ mục đích.
Điều này giống như nói "Giải quyết các vấn đề của thế giới thứ 3 thật dễ dàng. Chỉ cần ném một quả bom vào nó." Chỉ vì có một giải pháp tầm thường, điều này không khiến vấn đề trở nên tầm thường, vì bạn quan tâm đến chất lượng của kết quả.

Nhưng trong thực tế, việc giải quyết những vấn đề này cũng khó như mọi vấn đề lập trình khác và được thực hiện tốt nhất với sự trừu tượng hóa phù hợp. Điều này làm cho nó khá dễ dàng trong thực tế.


14

Tôi nghĩ rằng có một góc độ kỹ thuật cho câu hỏi này - IMO đó là một vấn đề của niềm tin. Chúng tôi thường được yêu cầu sao chép các ứng dụng phức tạp như - ồ, tôi không biết - ví dụ như Facebook. Tôi đã đi đến kết luận rằng nếu bạn phải giải thích sự phức tạp của một nhiệm vụ cho người quản lý / người không quen biết - thì điều gì đó đã thối rữa ở Đan Mạch.

Ngay cả khi các lập trình viên ninja khác có thể thực hiện nhiệm vụ trong 5 phút, ước tính của bạn dựa trên khả năng cá nhân của bạn. Người đối thoại của bạn nên học cách tin tưởng ý kiến ​​của bạn về vấn đề này hoặc thuê một người mà họ sẵn sàng chấp nhận.

Thách thức không phải là chuyển tiếp các ý nghĩa kỹ thuật, mà mọi người có xu hướng bỏ qua hoặc không thể nắm bắt thông qua cuộc trò chuyện, mà là thiết lập mối quan hệ tôn trọng lẫn nhau.


1
Câu trả lời thú vị, mặc dù đó là một câu hỏi kỹ thuật. Tuy nhiên tôi đồng ý với những gì bạn nói ... mặc dù trong trường hợp này, người quản lý của tôi là một lập trình viên khá giỏi, tuy nhiên tôi chỉ nghĩ rằng vì anh ta không bắt gặp sự phức tạp của các ứng dụng đa luồng, anh ta đánh giá thấp chúng.
Ông Shoubs

6

Một thí nghiệm đơn giản để hiểu bế tắc là vấn đề " triết gia ăn uống ". Một trong những ví dụ tôi có xu hướng sử dụng để mô tả các điều kiện chủng tộc tồi tệ như thế nào là tình huống Therac 25 .

"Chỉ cần khóa một cái khóa trên đó" là tâm lý của một người không gặp phải các lỗi khó khăn với đa luồng. Và có thể anh ta nghĩ rằng bạn đang cường điệu hóa mức độ nghiêm trọng của tình huống (tôi không - có thể thổi bay mọi thứ hoặc giết người với các lỗi tình trạng chủng tộc, đặc biệt là với phần mềm nhúng kết thúc trong xe hơi).


1
tức là vấn đề bánh sandwich: bạn tạo ra một đống bánh sandwich, nhưng chỉ có 1 đĩa bơ và 1 con dao. Nói chung tất cả đều ổn nhưng cuối cùng sẽ có người lấy bơ trong khi người khác lấy con dao .. và sau đó cả hai đứng đó chờ người kia buông tài nguyên của họ.
gbjbaanb

Các vấn đề bế tắc như thế có thể được giải quyết bằng cách luôn luôn có được các tài nguyên theo một trật tự đã đặt không?
soạn nhạc

@compman, không. Bởi vì có thể có 2 luồng cố gắng lấy cùng một tài nguyên tại cùng một thời điểm và các luồng đó không nhất thiết cần cùng một bộ tài nguyên - chỉ cần một sự chồng chéo đủ để gây ra sự cố. Một kế hoạch là đưa tài nguyên "trở lại" và sau đó đợi một khoảng thời gian ngẫu nhiên trước khi lấy lại tài nguyên. Thời kỳ backoff này xảy ra trong một số giao thức, trong đó sớm nhất là Aloha. vi.wikipedia.org/wiki/ALOHAnet
Tangurena

1
Điều gì sẽ xảy ra nếu mỗi tài nguyên trong chương trình có một số và khi một luồng / tiến trình cần một tập hợp các tài nguyên, nó luôn khóa các tài nguyên theo thứ tự tăng dần? Tôi không nghĩ rằng bế tắc có thể xảy ra.
soạn nhạc

1
@compman: Đó thực sự là một cách để tránh bế tắc. Có thể thiết kế các công cụ cho phép bạn tự động kiểm tra điều này; vì vậy nếu ứng dụng của bạn không bao giờ được tìm thấy để khóa tài nguyên ngoài việc tăng thứ tự số thì bạn sẽ không bao giờ gặp bế tắc tiềm năng . (Lưu ý rằng các bế tắc tiềm năng chỉ biến thành các bế tắc thực sự khi mã của bạn chạy trên máy tính của khách hàng).
gnasher729

3

Các ứng dụng đồng thời không mang tính quyết định. Với số lượng mã đặc biệt nhỏ mà lập trình viên đã nhận ra là dễ bị tổn thương, bạn không kiểm soát khi một phần của luồng / tiến trình thực thi liên quan đến bất kỳ phần nào của luồng khác. Kiểm tra khó hơn, mất nhiều thời gian hơn và không có khả năng tìm thấy tất cả các khiếm khuyết liên quan đến đồng thời. Khiếm khuyết, nếu được tìm thấy, sau đó tinh tế không thể được sao chép một cách nhất quán, do đó sửa chữa là khó khăn.

Do đó, ứng dụng đồng thời đúng duy nhất là một ứng dụng có thể chứng minh chính xác, một điều không thường được thực hiện trong phát triển phần mềm. Do đó, câu trả lời của S.Lot là lời khuyên chung tốt nhất, vì việc truyền thông điệp là tương đối dễ dàng để chứng minh chính xác.


3

Câu trả lời ngắn gọn trong hai từ: OBSERVABLE NONDETERMINISM

Câu trả lời dài: Nó phụ thuộc vào cách tiếp cận lập trình đồng thời mà bạn sử dụng đưa ra vấn đề của bạn. Trong cuốn sách Khái niệm, Kỹ thuật và Mô hình lập trình máy tính , các tác giả giải thích rõ ràng bốn cách tiếp cận thực tế chính để viết chương trình đồng thời:

  • Lập trình tuần tự : một cách tiếp cận cơ bản không có sự tương tranh;
  • Tuyên bố đồng thời : có thể sử dụng khi không có thuyết không điều kiện quan sát được;
  • Đồng thời chuyển thông điệp: thông điệp đồng thời chuyển giữa nhiều thực thể, trong đó mỗi thực thể xử lý thông điệp theo tuần tự;
  • Đồng thời trạng thái chia sẻ : chủ đề cập nhật các đối tượng thụ động được chia sẻ bằng các hành động nguyên tử hạt thô, ví dụ: khóa, màn hình và giao dịch;

Bây giờ cách dễ nhất trong bốn cách tiếp cận này ngoài lập trình tuần tự rõ ràng là đồng thời khai báo , bởi vì các chương trình được viết bằng cách tiếp cận này không có thuyết không điều kiện quan sát được . Nói cách khác, không có điều kiện chủng tộc , vì điều kiện chủng tộc chỉ là một hành vi không xác định có thể quan sát được.

Nhưng việc thiếu tính không thuyết phục có thể quan sát được, có một số vấn đề chúng ta không thể giải quyết bằng cách sử dụng đồng thời khai báo. Đây là nơi mà hai cách tiếp cận không dễ dàng cuối cùng đi vào chơi. Phần không dễ dàng như vậy là hậu quả của thuyết không điều trị có thể quan sát được. Bây giờ cả hai đều thuộc mô hình đồng thời có trạng thái và cũng tương đương về biểu cảm. Nhưng do số lượng lõi trên mỗi CPU ngày càng tăng, có vẻ như ngành công nghiệp gần đây đã quan tâm nhiều hơn đến việc truyền thông điệp đồng thời, như có thể thấy trong sự gia tăng của các thư viện truyền thông điệp (ví dụ Akka cho JVM) hoặc ngôn ngữ lập trình (ví dụ Erlang ) .

Thư viện Akka đã đề cập trước đây, được hỗ trợ bởi mô hình Diễn viên lý thuyết giúp đơn giản hóa việc xây dựng các ứng dụng đồng thời, vì bạn không phải đối phó với khóa, màn hình hoặc giao dịch nữa. Mặt khác, nó đòi hỏi cách tiếp cận khác nhau để thiết kế giải pháp, tức là suy nghĩ theo cách làm thế nào để phân cấp các tác nhân tổng hợp. Người ta có thể nói rằng nó đòi hỏi một tư duy hoàn toàn khác, một lần nữa có thể khó hơn so với việc sử dụng đồng thời chia sẻ trạng thái đơn giản.

Lập trình đồng thời là khó vì không có khả năng quan sát được, nhưng khi sử dụng đúng phương pháp cho vấn đề nhất định và thư viện phù hợp hỗ trợ phương pháp đó, thì có thể tránh được rất nhiều vấn đề.


0

Lần đầu tiên tôi được dạy rằng nó có thể đưa ra các vấn đề bằng cách xem một chương trình đơn giản bắt đầu 2 luồng và cả hai đều in ra bàn điều khiển cùng lúc từ 1-100. Thay vì:

1
1
2
2
3
3
...

Bạn nhận được một cái gì đó như thế này:

1
2
1
3
2
3
...

Chạy lại và bạn có thể nhận được kết quả hoàn toàn khác nhau.

Hầu hết chúng ta đã được đào tạo để cho rằng mã của chúng tôi sẽ thực thi tuần tự. Với hầu hết đa luồng, chúng ta không thể coi điều này là "ngoài luồng".


-3

Hãy thử sử dụng một số búa để nghiền trong một loạt các móng tay có khoảng cách gần nhau mà không có một số liên lạc giữa những người cầm búa ... (giả sử rằng họ bị bịt mắt).

Nâng cao điều này để xây dựng một ngôi nhà.

Bây giờ hãy ngủ vào ban đêm tưởng tượng bạn là kiến ​​trúc sư. :)


-3

Phần dễ dàng: sử dụng đa luồng với các tính năng hiện đại của khung, hệ điều hành và phần cứng, như semaphores, hàng đợi, bộ đếm lồng vào nhau, các loại hộp nguyên tử, v.v.

Phần cứng: tự thực hiện các tính năng mà không sử dụng tính năng nào ở vị trí đầu tiên, có thể ngoại trừ một số khả năng rất hạn chế của phần cứng, chỉ dựa vào việc đảm bảo sự kết hợp xung nhịp trên nhiều lõi.


3
Phần khó thực sự khó hơn, nhưng ngay cả phần dễ đó cũng không dễ.
Peter ALLenWebb
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.