Bài học nào bạn học được từ một dự án gần như / thực sự thất bại do đa luồng xấu? [đóng cửa]


11

Bài học nào bạn học được từ một dự án gần như / thực sự thất bại do đa luồng xấu?

Đôi khi, khung áp đặt một mô hình luồng nhất định làm cho mọi thứ trở nên khó khăn hơn để có được quyền.

Đối với tôi, tôi vẫn chưa phục hồi từ thất bại vừa qua và tôi cảm thấy tốt hơn là tôi không nên làm việc với bất cứ điều gì phải làm với đa luồng trong khuôn khổ đó.

Tôi thấy rằng tôi rất giỏi trong các vấn đề đa luồng có ngã ba / nối đơn giản và khi dữ liệu chỉ truyền theo một hướng (trong khi tín hiệu có thể truyền theo hướng vòng tròn).

Tôi không thể xử lý GUI trong đó một số công việc chỉ có thể được thực hiện trên một luồng được tuần tự hóa nghiêm ngặt ("luồng chính") và các công việc khác chỉ có thể được thực hiện trên bất kỳ luồng nào trừ luồng chính ("luồng công nhân") và trong đó dữ liệu và tin nhắn phải di chuyển theo mọi hướng giữa N thành phần (một biểu đồ được kết nối đầy đủ).

Vào thời điểm tôi rời dự án đó cho một dự án khác, đã có những vấn đề bế tắc ở khắp mọi nơi. Tôi nghe nói rằng 2-3 tháng sau, một số nhà phát triển khác đã cố gắng khắc phục tất cả các vấn đề bế tắc, đến mức nó có thể được chuyển đến khách hàng. Tôi không bao giờ quản lý để tìm ra phần kiến ​​thức còn thiếu mà tôi đang thiếu.

Đôi điều về dự án: số ID thông báo (giá trị nguyên mô tả ý nghĩa của một sự kiện có thể được gửi vào hàng đợi tin nhắn của một đối tượng khác, bất kể luồng) chạy vào hàng ngàn. Chuỗi duy nhất (tin nhắn người dùng) cũng chạy vào khoảng một ngàn.

Thêm

Sự tương tự tốt nhất tôi nhận được từ một nhóm khác (không liên quan đến các dự án trong quá khứ hoặc hiện tại của tôi) là "đưa dữ liệu vào cơ sở dữ liệu". . được lưu trữ trong một plase duy nhất hoạt động như Cơ sở dữ liệu và để "Cơ sở dữ liệu" xử lý tất cả các "cập nhật nguyên tử" liên quan đến các phụ thuộc dữ liệu không tầm thường. Tất cả các phần khác của GUI chỉ xử lý bản vẽ màn hình và không có gì khác. Các bộ phận giao diện người dùng có thể lưu trữ nội dung và người dùng sẽ không nhận thấy nếu nó cũ trong một phần giây, nếu nó được thiết kế đúng. "Cơ sở dữ liệu" này còn được gọi là "tài liệu" trong kiến ​​trúc Xem tài liệu. Thật không may - không, ứng dụng của tôi thực sự lưu trữ tất cả dữ liệu trong Chế độ xem. Tôi không biết tại sao nó lại như vậy.

Thành viên đóng góp:

(những người đóng góp không cần sử dụng các ví dụ thực tế / cá nhân. Bài học từ các ví dụ điển hình, nếu nó được đánh giá bởi chính bạn là đáng tin cậy, cũng được hoan nghênh.)



Tôi nghĩ rằng việc có thể 'nghĩ theo chủ đề' là một phần của tài năng và ít thứ có thể học được, vì thiếu từ ngữ tốt hơn. Tôi biết rất nhiều nhà phát triển đã làm việc với các hệ thống song song trong một thời gian rất dài, nhưng họ bị nghẹt thở nếu dữ liệu phải đi theo nhiều hướng.
dauphic

Câu trả lời:


13

Bài học yêu thích của tôi - rất khó giành chiến thắng! - là trong một chương trình đa luồng, người lập lịch là một con lợn lén lút ghét bạn. Nếu mọi thứ có thể đi sai, họ sẽ, nhưng theo một cách bất ngờ. Nhận bất cứ điều gì sai, và bạn sẽ theo đuổi những con bọ hung kỳ lạ (bởi vì bất kỳ thiết bị nào bạn thêm sẽ thay đổi thời gian và cung cấp cho bạn một mô hình chạy khác nhau).

Cách duy nhất để khắc phục điều này là xử lý nghiêm ngặt tất cả việc xử lý luồng thành một đoạn mã nhỏ, điều đó hoàn toàn đúng và rất thận trọng trong việc đảm bảo rằng các khóa được giữ đúng cách (và với lệnh mua lại liên tục trên toàn cầu) . Cách dễ nhất để làm điều đó là không chia sẻ bộ nhớ (hoặc các tài nguyên khác) giữa các luồng ngoại trừ việc nhắn tin phải không đồng bộ; cho phép bạn viết mọi thứ khác theo kiểu không có chủ đề. (Phần thưởng: nhân rộng ra nhiều máy trong một cụm dễ dàng hơn nhiều.)


+1 cho "không chia sẻ bộ nhớ (hoặc các tài nguyên khác) giữa các luồng ngoại trừ việc nhắn tin phải không đồng bộ;"
Nemanja Trifunovic

1
Cách duy nhất? Còn các loại dữ liệu bất biến?
Aaronaught

is that in a multithreaded program the scheduler is a sneaky swine that hates you.- không, không, nó thực hiện chính xác những gì bạn bảo nó làm :)
mattnz

@Aaronaught: Các giá trị toàn cầu được truyền qua tham chiếu, ngay cả khi không thay đổi, vẫn yêu cầu GC toàn cầu và điều đó giới thiệu lại một loạt các tài nguyên toàn cầu. Có thể sử dụng quản lý bộ nhớ theo từng luồng là tốt, vì nó cho phép bạn thoát khỏi một loạt các khóa toàn cầu.
Donal Fellows

Không phải là bạn không thể chuyển các giá trị của các loại không cơ bản bằng tham chiếu, nhưng nó yêu cầu mức độ khóa cao hơn (ví dụ: chủ sở hữu của Cameron giữ một tham chiếu cho đến khi một số thông báo quay trở lại, điều này rất dễ gây rối trong bảo trì) hoặc mã phức tạp trong công cụ nhắn tin để chuyển quyền sở hữu. Hoặc bạn sắp xếp mọi thứ và không thống nhất trong các chủ đề khác, chậm hơn nhiều (bạn phải làm điều đó khi đi đến một cụm dù thế nào). Cắt theo đuổi và không chia sẻ bộ nhớ chút nào là dễ dàng hơn.
Donal Fellows

6

Dưới đây là một vài bài học cơ bản tôi có thể nghĩ ra ngay bây giờ (không phải từ các dự án thất bại mà từ các vấn đề thực tế được thấy trong các dự án thực tế):

  • Cố gắng tránh mọi cuộc gọi chặn trong khi giữ tài nguyên được chia sẻ. Mẫu bế tắc phổ biến là luồng lấy mutex, tạo một hàm gọi lại, gọi lại trên cùng một mutex.
  • Bảo vệ quyền truy cập vào bất kỳ cấu trúc dữ liệu được chia sẻ nào với phần đột biến / quan trọng (hoặc sử dụng khóa miễn phí - nhưng không phát minh ra cấu trúc của riêng bạn!)
  • Đừng giả sử tính nguyên tử - sử dụng API nguyên tử (ví dụ: InterlockedIncrement).
  • RTFM liên quan đến an toàn luồng của thư viện, đối tượng hoặc API bạn đang sử dụng.
  • Tận dụng các nguyên thủy đồng bộ hóa có sẵn, ví dụ các sự kiện, semaphores. (Nhưng hãy chú ý khi sử dụng chúng mà bạn biết bạn đang ở trạng thái tốt - Tôi đã thấy nhiều ví dụ về các sự kiện được báo hiệu ở trạng thái sai sao cho các sự kiện hoặc dữ liệu có thể bị mất)
  • Giả sử các luồng có thể thực thi đồng thời và / hoặc theo bất kỳ trật tự nào và bối cảnh đó có thể chuyển đổi giữa các luồng bất cứ lúc nào (trừ khi trong một HĐH thực hiện các đảm bảo khác).

6
  • Toàn bộ dự án GUI của bạn chỉ nên được gọi từ luồng chính . Về cơ bản, bạn không nên đặt một (.net) "gọi" trong GUI của mình. Đa luồng nên bị mắc kẹt trong các dự án riêng biệt xử lý việc truy cập dữ liệu chậm hơn.

Chúng tôi thừa hưởng một phần trong đó dự án GUI đang sử dụng hàng tá luồng. Không có gì ngoài vấn đề. Bế tắc, vấn đề đua xe, cuộc gọi GUI đa luồng ...


"Dự án" có nghĩa là "lắp ráp"? Tôi không thấy cách phân phối các lớp giữa các hội đồng sẽ gây ra vấn đề luồng.
nikie

Trong dự án của tôi, nó thực sự là một hội đồng. Nhưng điểm chính là tất cả các mã trong các thư mục đó phải được gọi từ luồng chính, không có ngoại lệ.
Carra

Tôi không nghĩ rằng quy tắc này thường được áp dụng. Có, bạn không bao giờ nên gọi mã GUI từ một luồng khác. Nhưng cách bạn phân phối các lớp cho các thư mục / dự án / tập hợp là một quyết định độc lập.
nikie

1

Java 5 trở lên có Executor, nhằm mục đích làm cho cuộc sống dễ dàng hơn để xử lý các chương trình kiểu kết nối đa luồng.

Sử dụng chúng, nó sẽ loại bỏ rất nhiều nỗi đau.

(và, vâng, điều này tôi đã học được từ một dự án :))


1
Để áp dụng câu trả lời này cho các ngôn ngữ khác - sử dụng các khung xử lý song song chất lượng cao được cung cấp bởi ngôn ngữ đó bất cứ khi nào có thể. (Tuy nhiên, chỉ có thời gian mới cho biết liệu một khung có thực sự tuyệt vời và có khả năng sử dụng cao hay không.)
rwong

1

Tôi có một nền tảng trong các hệ thống nhúng thời gian thực cứng. Bạn không thể kiểm tra sự vắng mặt của các vấn đề gây ra bởi đa luồng. (Đôi khi bạn có thể xác nhận sự hiện diện). Mã phải được chứng minh chính xác. Vì vậy, thực hành tốt nhất xung quanh bất kỳ và tất cả các tương tác chủ đề.

  • Quy tắc số 1: KISS - Nếu không cần một chủ đề, đừng quay một cái. Nối tiếp càng nhiều càng tốt.
  • Quy tắc số 2: Không phá vỡ số 1.
  • # 3 Nếu bạn không thể chứng minh thông qua đánh giá thì không đúng.

+1 cho quy tắc 1. Tôi đã làm việc trên một dự án ban đầu sẽ chặn cho đến khi một luồng khác hoàn thành - về cơ bản là một cuộc gọi phương thức! May mắn thay, chúng tôi quyết định chống lại cách tiếp cận đó.
Michael K

# 3 FTW. Tốt hơn là dành hàng giờ vật lộn với sơ đồ thời gian khóa hoặc bất cứ điều gì bạn sử dụng để chứng minh rằng nó tốt hơn nhiều tháng tự hỏi tại sao đôi khi nó sụp đổ.

1

Một sự tương tự từ một lớp học về đa luồng tôi đã học năm ngoái là rất hữu ích. Đồng bộ hóa luồng giống như tín hiệu giao thông bảo vệ giao lộ (dữ liệu) khỏi bị hai xe (luồng) sử dụng cùng một lúc. Sai lầm mà rất nhiều nhà phát triển mắc phải là chuyển đèn đỏ trên hầu hết thành phố để cho một chiếc xe đi qua vì họ cho rằng quá khó hoặc nguy hiểm để tìm ra tín hiệu chính xác mà họ cần. Điều đó có thể hoạt động tốt khi lưu lượng truy cập nhẹ, nhưng sẽ dẫn đến tình trạng chặn lưới khi ứng dụng của bạn phát triển.

Đó là điều mà tôi đã biết trên lý thuyết, nhưng sau lớp học đó, sự tương tự thực sự mắc kẹt với tôi, và tôi đã rất ngạc nhiên khi sau đó tôi sẽ điều tra một vấn đề luồng và tìm một hàng đợi khổng lồ, hoặc bị gián đoạn ở mọi nơi trong quá trình ghi vào một biến chỉ có hai luồng được sử dụng hoặc các mutexes được giữ trong một thời gian dài khi nó có thể được tái cấu trúc để tránh nó hoàn toàn.

Nói cách khác, một số vấn đề luồng tồi tệ nhất là do quá mức cố gắng để tránh các vấn đề luồng.


0

Hãy thử làm lại.

Ít nhất là đối với tôi, điều tạo ra sự khác biệt là thực hành. Sau khi thực hiện công việc đa luồng và phân phối khá nhiều lần, bạn chỉ cần nắm bắt được nó.

Tôi nghĩ gỡ lỗi là điều thực sự gây khó khăn. Tôi có thể gỡ lỗi mã đa luồng bằng cách sử dụng VS nhưng tôi thực sự bị mất hoàn toàn nếu tôi phải sử dụng gdb. Lỗi của tôi, có lẽ.

Một điều nữa là tìm hiểu thêm về khóa cấu trúc dữ liệu miễn phí.

Tôi nghĩ rằng câu hỏi này có thể thực sự được cải thiện nếu bạn chỉ định khung. Ví dụ, nhóm luồng .NET và công nhân nền thực sự khác với QThread. Luôn luôn có một vài nền tảng cụ thể.


Tôi thích nghe những câu chuyện từ bất kỳ khuôn khổ nào, bởi vì tôi tin rằng có những điều cần học hỏi từ mỗi khung, đặc biệt là những câu chuyện mà tôi chưa được tiếp xúc.
rwong

1
trình gỡ lỗi phần lớn là vô dụng trong môi trường đa luồng.
Pemdas

Tôi đã có các bộ theo dõi thực thi đa luồng cho tôi biết vấn đề là gì, nhưng sẽ không giúp tôi giải quyết nó. Mấu chốt của vấn đề của tôi là "theo thiết kế hiện tại, tôi không thể chuyển thông điệp X cho đối tượng Y theo cách này (trình tự); nó phải được thêm vào một hàng đợi khổng lồ và cuối cùng nó sẽ được xử lý, nhưng vì điều này , không có cách nào để tin nhắn xuất hiện cho người dùng vào đúng thời điểm - nó sẽ luôn xảy ra lỗi thời và khiến người dùng rất, rất bối rối. Thậm chí, bạn có thể cần thêm thanh tiến trình, hủy nút hoặc thông báo lỗi vào những nơi không nên ' t có những cái đó . "
rwong

0

Tôi đã học được rằng các cuộc gọi lại từ các mô-đun cấp thấp hơn đến các mô-đun cấp cao hơn là một tội ác lớn vì chúng gây ra các khóa theo thứ tự ngược lại.


cuộc gọi lại không phải là xấu xa ... thực tế họ làm bất cứ điều gì khác ngoài việc đứt chỉ có lẽ là gốc rễ của tội ác. Tôi rất nghi ngờ về bất kỳ cuộc gọi lại nào mà không gửi mã thông báo đến hàng đợi tin nhắn.
Pemdas

Việc giải quyết vấn đề tối ưu hóa (như tối thiểu hóa f (x)) thường được thực hiện bằng cách cung cấp con trỏ cho hàm f (x) cho quy trình tối ưu hóa, trong đó "gọi lại" trong khi tìm kiếm mức tối thiểu. Làm thế nào bạn sẽ làm điều đó mà không cần gọi lại?
quant_dev

1
Không downvote, nhưng gọi lại không phải là xấu xa. Gọi lại trong khi giữ một khóa là xấu xa. Đừng gọi bất cứ thứ gì trong ổ khóa khi bạn không biết liệu nó có thể khóa hoặc chờ. Điều đó không chỉ bao gồm các cuộc gọi lại mà cả các chức năng ảo, chức năng API, chức năng trong các mô-đun khác ("cấp cao hơn" hoặc "cấp thấp hơn").
nikie

@nikie: Nếu khóa phải được giữ trong khi gọi lại, phần còn lại của API cần được thiết kế để được cấp lại (khó!) Hoặc thực tế là bạn đang giữ khóa cần phải là một phần của tài liệu API ( không may, nhưng đôi khi tất cả những gì bạn có thể làm).
Donal Fellows

@Donal Fellows: Nếu khóa phải được giữ trong khi gọi lại, tôi muốn nói rằng bạn có một lỗi thiết kế. Nếu thực sự không có cách nào khác, thì có, bằng mọi cách hãy ghi lại điều đó! Giống như bạn sẽ ghi lại nếu cuộc gọi lại sẽ được gọi trong một luồng nền. Đó là một phần của giao diện.
nikie
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.