Bị vấy bẩn bởi lỗi đa luồng


26

Trong nhóm mới mà tôi quản lý, phần lớn mã của chúng tôi là nền tảng, ổ cắm TCP và mã mạng http. Tất cả C ++. Hầu hết trong số đó có nguồn gốc từ các nhà phát triển khác đã rời khỏi nhóm. Các nhà phát triển hiện tại trong nhóm rất thông minh, nhưng chủ yếu là thiếu niên về kinh nghiệm.

Vấn đề lớn nhất của chúng tôi: lỗi đồng thời đa luồng. Hầu hết các thư viện lớp của chúng tôi được viết là không đồng bộ bằng cách sử dụng một số lớp nhóm luồng. Các phương thức trên các thư viện lớp thường ghi lại các bước chạy dài trên nhóm luồng từ một luồng và sau đó các phương thức gọi lại của lớp đó được gọi trên một luồng khác. Kết quả là, chúng ta có rất nhiều lỗi trường hợp cạnh liên quan đến các giả định luồng không chính xác. Điều này dẫn đến các lỗi tinh vi vượt ra ngoài việc chỉ có các phần quan trọng và khóa để bảo vệ chống lại các vấn đề tương tranh.

Điều làm cho những vấn đề này trở nên khó khăn hơn là những nỗ lực khắc phục thường không chính xác. Một số lỗi tôi đã quan sát thấy nhóm đang cố gắng (hoặc trong chính mã kế thừa) bao gồm một số điều như sau:

Lỗi phổ biến số 1 - Khắc phục sự cố đồng thời chỉ bằng cách khóa một dữ liệu được chia sẻ, nhưng quên đi những gì xảy ra khi các phương thức không được gọi theo thứ tự dự kiến. Đây là một ví dụ rất đơn giản:

void Foo::OnHttpRequestComplete(statuscode status)
{
    m_pBar->DoSomethingImportant(status);
}

void Foo::Shutdown()
{
    m_pBar->Cleanup();
    delete m_pBar;
    m_pBar=nullptr;
}

Vì vậy, bây giờ chúng tôi có một lỗi trong đó Shutdown có thể được gọi trong khi OnHttpNetworkRequestComplete đang xảy ra. Một người kiểm tra tìm thấy lỗi, ghi lại bãi chứa sự cố và gán lỗi cho nhà phát triển. Anh ta lần lượt sửa lỗi như thế này.

void Foo::OnHttpRequestComplete(statuscode status)
{
    AutoLock lock(m_cs);
    m_pBar->DoSomethingImportant(status);
}

void Foo::Shutdown()
{
    AutoLock lock(m_cs);
    m_pBar->Cleanup();
    delete m_pBar;
    m_pBar=nullptr;
}

Các sửa chữa ở trên có vẻ tốt cho đến khi bạn nhận ra có một trường hợp cạnh thậm chí tinh tế hơn. Điều gì xảy ra nếu Shutdown được gọi trước khi OnHttpRequestComplete được gọi lại? Các ví dụ trong thế giới thực mà nhóm của tôi thậm chí còn phức tạp hơn và các trường hợp cạnh thậm chí còn khó phát hiện hơn trong quá trình xem xét mã.

Lỗi thường gặp # 2 - khắc phục các sự cố bế tắc bằng cách thoát khỏi khóa một cách mù quáng, đợi luồng khác kết thúc, sau đó nhập lại khóa - nhưng không xử lý trường hợp đối tượng vừa được cập nhật bởi luồng khác!

Sai lầm phổ biến # 3 - Mặc dù các đối tượng được tính tham chiếu, trình tự tắt máy "giải phóng" con trỏ của nó. Nhưng quên chờ đợi chủ đề vẫn đang chạy để phát hành phiên bản của nó. Như vậy, các thành phần được tắt sạch, sau đó các cuộc gọi lại giả hoặc trễ được gọi trên một đối tượng trong trạng thái không mong đợi bất kỳ cuộc gọi nào nữa.

Có các trường hợp cạnh khác, nhưng điểm mấu chốt là đây:

Lập trình đa luồng chỉ đơn giản là khó, ngay cả đối với những người thông minh.

Khi tôi mắc phải những lỗi này, tôi dành thời gian thảo luận về các lỗi với từng nhà phát triển để phát triển một bản sửa lỗi phù hợp hơn. Nhưng tôi nghi ngờ họ thường nhầm lẫn về cách giải quyết từng vấn đề vì số lượng mã kế thừa khổng lồ mà bản sửa lỗi "đúng" sẽ liên quan đến việc chạm vào.

Chúng tôi sẽ sớm giao hàng và tôi chắc chắn rằng các bản vá mà chúng tôi đang áp dụng sẽ giữ cho bản phát hành sắp tới. Sau đó, chúng tôi sẽ có thời gian để cải thiện cơ sở mã và cấu trúc lại mã khi cần thiết. Chúng tôi sẽ không có thời gian để viết lại mọi thứ. Và phần lớn mã không tệ lắm. Nhưng tôi đang tìm cách cấu trúc lại mã sao cho các vấn đề luồng có thể tránh được hoàn toàn.

Một cách tiếp cận tôi đang xem xét là điều này. Đối với mỗi tính năng nền tảng quan trọng, có một luồng chuyên dụng trong đó tất cả các sự kiện và cuộc gọi lại mạng được sắp xếp theo thứ tự. Tương tự như luồng căn hộ COM trong Windows với việc sử dụng vòng lặp tin nhắn. Các hoạt động chặn dài vẫn có thể được gửi đến một luồng nhóm công việc, nhưng cuộc gọi lại hoàn thành được gọi trên luồng của thành phần. Các thành phần thậm chí có thể chia sẻ cùng một chủ đề. Sau đó, tất cả các thư viện lớp chạy bên trong luồng có thể được viết theo giả định của một thế giới luồng đơn.

Trước khi tôi đi vào con đường đó, tôi cũng rất quan tâm nếu có các kỹ thuật tiêu chuẩn hoặc các mẫu thiết kế khác để xử lý các vấn đề đa luồng. Và tôi phải nhấn mạnh - một cái gì đó vượt ra ngoài một cuốn sách mô tả những điều cơ bản của mutexes và semaphores. Bạn nghĩ sao?

Tôi cũng quan tâm đến bất kỳ phương pháp nào khác để tiến tới quá trình tái cấu trúc. Bao gồm bất kỳ điều nào sau đây:

  1. Văn học hoặc giấy tờ về các mẫu thiết kế xung quanh chủ đề. Một cái gì đó vượt quá giới thiệu về mutexes và semaphores. Chúng ta cũng không cần song song lớn, chỉ là cách thiết kế mô hình đối tượng để xử lý các sự kiện không đồng bộ từ các luồng khác một cách chính xác .

  2. Các cách để lập sơ đồ phân luồng của các thành phần khác nhau, do đó sẽ dễ dàng nghiên cứu và phát triển các giải pháp cho. (Đó là, một UML tương đương để thảo luận về các luồng trên các đối tượng và các lớp)

  3. Giáo dục nhóm phát triển của bạn về các vấn đề với mã đa luồng.

  4. Bạn sẽ làm gì?


23
Một số người khi gặp vấn đề nghĩ rằng tôi sẽ sử dụng đa luồng. Bây giờ họ có hai đầu dò hai lần
Tom Squires

20
những gì làm việc tốt cho tôi là để thoát khỏi khả năng biến đổi bất cứ khi nào có thể. Khi tôi thấy trạng thái thay đổi đối tượng có thể thay đổi để chuyển giá trị mới, tôi cố gắng cấu trúc lại để chuyển một đối tượng bất biến mới giữ giá trị thay đổi thay thế. Nếu việc khởi tạo đối tượng được thực hiện một cách an toàn, điều này đảm bảo không có các cuộc đua dữ liệu - khá nhẹ nhõm
gnat

3
Chào mừng đến với địa ngục đa luồng. Tôi đã viết các chương trình đa luồng / paralell trong hơn 20 năm, bằng Ada, Occam, C ++. Điều đó không bao giờ dễ dàng, mọi thứ đòi hỏi phải suy nghĩ rất cẩn thận và bất cứ ai nói rằng "dễ dàng làm X" là một kẻ ngốc không thực sự hiểu những gì đang xảy ra. Chúc may mắn.
quick_now

2
Nếu bạn muốn xem đồng thời được thực hiện tốt, hãy sử dụng Erlang! Trong thực tế những gì bạn muốn là một hình thức của một mô hình diễn viên không có gì được chia sẻ trong đó các trường hợp góc kỳ lạ sẽ được loại bỏ.
Zachary K

3
@DeadMG Tôi cho rằng việc chia sẻ trạng thái đồng thời vốn dĩ dễ xảy ra với các trường hợp góc kỳ lạ và nên tránh. Nhưng Hey tôi đã viết một cuốn sách về Erlang
Zachary K

Câu trả lời:


27

Mã của bạn có các vấn đề quan trọng khác ngoài điều đó. Xóa thủ công một con trỏ? Gọi một cleanupchức năng? Owch. Ngoài ra, như được chỉ ra chính xác trong nhận xét câu hỏi, bạn không sử dụng RAII cho khóa của mình, đây là một thất bại khá hoành tráng và đảm bảo rằng khi DoSomethingImportantném ngoại lệ, những điều khủng khiếp sẽ xảy ra.

Thực tế là lỗi đa luồng này đang xảy ra chỉ là một triệu chứng của vấn đề cốt lõi - mã của bạn có ngữ nghĩa cực kỳ xấu trong bất kỳ tình huống phân luồng nào và bạn đang sử dụng các công cụ và thành ngữ hoàn toàn không đáng tin cậy. Nếu tôi là bạn, tôi sẽ ngạc nhiên khi nó hoạt động với một luồng duy nhất, chứ đừng nói gì thêm.

Sai lầm phổ biến # 3 - Mặc dù các đối tượng được tham chiếu, trình tự tắt máy "giải phóng" con trỏ của nó. Nhưng quên chờ đợi chủ đề vẫn đang chạy để phát hành phiên bản của nó. Như vậy, các thành phần được tắt sạch, sau đó các cuộc gọi lại giả hoặc trễ được gọi trên một đối tượng trong trạng thái không mong đợi bất kỳ cuộc gọi nào nữa.

Toàn bộ điểm của tính tham chiếu là luồng đã phát hành ví dụ của nó . Bởi vì nếu không, thì nó không thể bị hủy vì luồng vẫn có tham chiếu.

Sử dụng std::shared_ptr. Khi tất cả các luồng đã phát hành (và do đó, không ai có thể gọi hàm, vì chúng không có con trỏ tới nó), thì hàm hủy được gọi. Điều này được đảm bảo an toàn.

Thứ hai, sử dụng thư viện phân luồng thực, như Khối xây dựng luồng của Intel hoặc Thư viện mẫu song song của Microsoft. Viết của riêng bạn là tốn thời gian và không đáng tin cậy và mã của bạn có đầy đủ các chi tiết luồng mà nó không cần. Làm khóa riêng của bạn cũng tệ như quản lý bộ nhớ của riêng bạn. Họ đã triển khai nhiều thành ngữ phân luồng rất hữu dụng cho mục đích chung, hoạt động chính xác cho mục đích sử dụng của bạn.


Đây là một câu trả lời ok, nhưng không phải là hướng tôi đang tìm kiếm, vì nó dành quá nhiều thời gian để đánh giá một đoạn mã mẫu được viết chỉ vì đơn giản (và không phản ánh mã thực của chúng tôi trong sản phẩm của chúng tôi). Nhưng tôi tò mò về một bình luận bạn đã đưa ra - "công cụ không đáng tin cậy". Một công cụ không đáng tin cậy là gì? Bạn khuyên dùng công cụ gì?
koncurrency

5
@koncurrency: Một công cụ không đáng tin cậy là một công cụ như quản lý bộ nhớ thủ công hoặc viết đồng bộ hóa của riêng bạn, theo lý thuyết, nó giải quyết vấn đề X nhưng thực tế là rất tệ, bạn có thể đảm bảo khá nhiều lỗi lớn và cách duy nhất có thể giải quyết vấn đề trong tầm tay hợp lý là bởi sự đầu tư lớn và không cân xứng của thời gian dành cho nhà phát triển - đó là những gì bạn có ngay bây giờ.
DeadMG

9

Các áp phích khác đã bình luận tốt về những gì nên được thực hiện để khắc phục các vấn đề cốt lõi. Bài đăng này liên quan đến vấn đề tức thời hơn là vá mã kế thừa đủ để bạn có thời gian làm lại mọi thứ đúng cách. Nói cách khác, đây không phải là cách làm đúng đắn , bây giờ nó chỉ là một cách để đi khập khiễng.

Ý tưởng của bạn về hợp nhất các sự kiện quan trọng là một khởi đầu tốt. Tôi sẽ đi xa hơn để sử dụng một luồng công văn duy nhất để xử lý tất cả các sự kiện đồng bộ hóa chính, bất cứ nơi nào có sự phụ thuộc đơn hàng. Thiết lập hàng đợi tin nhắn an toàn cho luồng và bất cứ nơi nào bạn hiện đang thực hiện các hoạt động nhạy cảm đồng thời (phân bổ, dọn dẹp, gọi lại, v.v.), thay vào đó hãy gửi tin nhắn đến luồng đó và thực hiện hoặc kích hoạt hoạt động. Ý tưởng là một luồng này kiểm soát tất cả các đơn vị công việc bắt đầu, dừng, phân bổ và dọn dẹp.

Chuỗi công văn không giải quyết các vấn đề bạn mô tả, nó chỉ hợp nhất chúng ở một nơi. Bạn vẫn phải lo lắng về các sự kiện / tin nhắn xảy ra theo thứ tự bất ngờ. Các sự kiện có thời gian chạy đáng kể vẫn sẽ cần phải được gửi đến các luồng khác, do đó vẫn có vấn đề với sự tương tranh trên dữ liệu được chia sẻ. Một cách để giảm thiểu điều đó là tránh truyền dữ liệu bằng cách tham chiếu. Bất cứ khi nào có thể, dữ liệu trong tin nhắn công văn phải là bản sao mà người nhận sẽ sở hữu. (Đây là dọc theo dòng làm cho dữ liệu bất biến mà những người khác đã đề cập.)

Ưu điểm của phương pháp điều phối này là trong luồng công văn, bạn có một loại nơi trú ẩn an toàn mà ít nhất bạn biết rằng một số hoạt động nhất định đang diễn ra tuần tự. Nhược điểm là nó tạo ra một nút cổ chai và thêm chi phí CPU. Tôi đề nghị không nên lo lắng về một trong những điều đó lúc đầu: tập trung vào việc đạt được một số biện pháp hoạt động chính xác trước bằng cách di chuyển càng nhiều càng tốt vào luồng công văn. Sau đó thực hiện một số hồ sơ để xem những gì đang chiếm nhiều thời gian CPU nhất và bắt đầu chuyển nó ra khỏi luồng công văn bằng cách sử dụng các kỹ thuật đa luồng chính xác.

Một lần nữa, những gì tôi mô tả không phải là cách đúng đắn để làm mọi thứ, nhưng đó là một quá trình có thể đưa bạn đi đúng hướng theo gia số đủ nhỏ để đáp ứng thời hạn thương mại.


+1 cho một đề xuất hợp lý, trung gian về việc vượt qua thử thách hiện tại.

Vâng, đây là cách tiếp cận tôi đang điều tra. Bạn nâng cao điểm tốt về hiệu suất.
koncurrency

Thay đổi mọi thứ để đi qua một chuỗi công văn không có vẻ như là một bản vá nhanh mà là một công cụ tái cấu trúc lớn đối với tôi.
Sebastian Redl

8

Dựa trên mã được hiển thị, bạn có một đống WTF. Sẽ vô cùng khó khăn nếu không phải là không thể sửa chữa một ứng dụng đa luồng được viết kém. Nói với các chủ sở hữu rằng ứng dụng sẽ không bao giờ đáng tin cậy nếu không làm lại đáng kể. Cung cấp cho họ một ước tính dựa trên việc kiểm tra và làm lại từng bit của mã đang tương tác với các đối tượng được chia sẻ. Đầu tiên cung cấp cho họ một ước tính cho việc kiểm tra. Sau đó, bạn có thể đưa ra một ước tính cho việc làm lại.

Khi bạn làm lại mã, bạn nên lập kế hoạch viết mã sao cho đúng. Nếu bạn không biết làm thế nào, hãy tìm ai đó làm, hoặc bạn sẽ ở cùng một nơi.


Chỉ cần đọc điều này bây giờ, sau khi câu trả lời của tôi đã được nêu lên. Chỉ muốn nói rằng tôi thích câu giới thiệu :)
back2dos

7

Nếu bạn có thời gian dành để tái cấu trúc ứng dụng của mình, tôi khuyên bạn nên xem mô hình diễn viên (xem ví dụ Theron , Casablanca , libcppa , CAF để triển khai C ++).

Các tác nhân là các đối tượng chạy đồng thời và giao tiếp với nhau chỉ bằng cách sử dụng trao đổi thông điệp không đồng bộ. Vì vậy, tất cả các vấn đề về quản lý luồng, mutexes, deadlocks, v.v., được giải quyết bởi thư viện triển khai diễn viên và bạn có thể tập trung vào việc thực hiện hành vi của các đối tượng của mình (diễn viên), có khả năng lặp lại vòng lặp

  1. Nhận tin nhắn
  2. Thực hiện tính toán
  3. Gửi tin nhắn / tạo / giết các diễn viên khác.

Một cách tiếp cận cho bạn có thể là đọc một số chủ đề trước, và có thể xem một hoặc hai thư viện để xem liệu mô hình diễn viên có thể được tích hợp trong mã của bạn không.

Tôi đã sử dụng (một phiên bản đơn giản hóa) của mô hình này trong một dự án của tôi trong vài tháng nay và tôi ngạc nhiên bởi sự mạnh mẽ của nó.


1
Thư viện Akka cho Scala là một triển khai tốt đẹp này, trong đó suy nghĩ rất nhiều về cách giết các diễn viên phụ huynh khi trẻ em chết, hoặc ngược lại. Tôi biết đó không phải là C ++, nhưng đáng xem: akka.io
GlenPeterson

1
@GlenPeterson: Cảm ơn, tôi biết về akka (lúc này tôi coi là giải pháp thú vị nhất và hoạt động cả với Java và Scala) nhưng câu hỏi đề cập cụ thể đến C ++. Nếu không, người ta có thể xem xét Erlang. Tôi đoán ở Erlang tất cả những vấn đề đau đầu của lập trình đa luồng đều biến mất. Nhưng có lẽ các khuôn khổ như akka đến rất gần.
Giorgio

"Tôi đoán ở Erlang, tất cả những vấn đề đau đầu về lập trình đa luồng đều biến mất." Tôi nghĩ có lẽ đây là một chút cường điệu. Hoặc nếu đúng, thì hiệu suất có thể thiếu. Tôi biết Akka không hoạt động với C ++, chỉ nói rằng nó trông giống như công nghệ tiên tiến để quản lý nhiều luồng. Nó không, tuy nhiên, an toàn chủ đề. Bạn vẫn có thể vượt qua trạng thái đột biến giữa các diễn viên và tự bắn vào chân mình.
GlenPeterson

Tôi không phải là chuyên gia về Erlang nhưng AFAIK mỗi diễn viên được thực hiện trong sự cô lập và các thông điệp bất biến được trao đổi. Vì vậy, bạn thực sự không phải đối phó với các chủ đề và chia sẻ trạng thái có thể thay đổi. Hiệu suất có thể thấp hơn C ++, nhưng điều này luôn xảy ra khi bạn tăng mức độ trừu tượng (bạn tăng thời gian thực hiện nhưng giảm thời gian phát triển).
Giorgio

Downvoter có thể để lại một bình luận và đề nghị làm thế nào tôi có thể cải thiện câu trả lời này?
Giorgio

6

Lỗi phổ biến số 1 - Khắc phục sự cố đồng thời chỉ bằng cách khóa một dữ liệu được chia sẻ, nhưng quên đi những gì xảy ra khi các phương thức không được gọi theo thứ tự dự kiến. Đây là một ví dụ rất đơn giản:

Lỗi ở đây không phải là "quên", mà là "không sửa nó". Nếu bạn có những điều xảy ra theo một trật tự bất ngờ, bạn có một vấn đề. Bạn nên giải quyết nó thay vì cố gắng làm việc xung quanh nó (vỗ một cái khóa vào một cái gì đó thường là một công việc xung quanh).

Bạn nên cố gắng điều chỉnh mô hình diễn viên / nhắn tin ở một mức độ nhất định và tách biệt mối quan tâm. Vai trò củaFoo rõ ràng là xử lý một số loại giao tiếp HTTP. Nếu bạn muốn thiết kế hệ thống của mình để thực hiện việc này song song, thì lớp bên trên phải xử lý vòng đời đối tượng và truy cập đồng bộ hóa tương ứng.

Cố gắng để có một số luồng hoạt động trên cùng một dữ liệu có thể thay đổi là khó khăn. Nhưng nó cũng hiếm khi cần thiết. Tất cả các trường hợp phổ biến đòi hỏi điều này, đã được trừu tượng hóa thành các khái niệm dễ quản lý hơn và thực hiện một số lần đối với bất kỳ ngôn ngữ mệnh lệnh chính nào. Bạn chỉ cần sử dụng chúng.


2

Các vấn đề của bạn khá tệ, nhưng điển hình là việc sử dụng C ++ kém. Xem xét mã sẽ khắc phục một số vấn đề này. 30 phút, một bộ nhãn cầu cho 90% kết quả. (Trích dẫn cho điều này là có thể hiểu được)

Vấn đề # 1 Bạn cần đảm bảo có một hierachy khóa chặt chẽ để ngăn chặn bế tắc khóa của bạn.

Nếu bạn thay thế Autolock bằng trình bao bọc và macro, bạn có thể thực hiện việc này.

Giữ một bản đồ toàn cầu tĩnh của các khóa được tạo ở mặt sau của trình bao bọc của bạn. Bạn sử dụng một macro để chèn thông tin tên và số dòng vào hàm tạo của trình bao bọc Autolock.

Bạn cũng sẽ cần một biểu đồ thống trị tĩnh.

Bây giờ bên trong khóa, bạn phải cập nhật biểu đồ thống trị và nếu bạn nhận được thay đổi thứ tự, bạn xác nhận lỗi và hủy bỏ.

Sau khi thử nghiệm rộng rãi, bạn có thể thoát khỏi hầu hết các bế tắc tiềm ẩn.

Mã được để lại như một bài tập cho học sinh.

Vấn đề # 2 sau đó sẽ biến mất (chủ yếu)

Giải pháp lưu trữ của bạn sẽ làm việc. Tôi đã sử dụng nó trước đây trong các hệ thống crtical nhiệm vụ và cuộc sống. Tôi nhận nó là cái này

  • Vượt qua các đối tượng bất biến hoặc tạo các bản sao của chúng trước khi vượt qua.
  • Không chia sẻ dữ liệu thông qua các biến công khai hoặc getters.

  • Các sự kiện bên ngoài đến thông qua một công văn đa luồng đến một hàng đợi được phục vụ bởi một luồng. Bây giờ bạn có thể sắp xếp lý do về xử lý sự kiện.

  • Thay đổi dữ liệu mà các luồng chéo đi vào một luồng an toàn luồng, được xử lý bởi một luồng. Đăng ký. Bây giờ bạn có thể sắp xếp lý do về các luồng dữ liệu.

  • Nếu dữ liệu của bạn cần đi qua thị trấn, hãy xuất bản nó vào hàng đợi dữ liệu. Điều đó sẽ sao chép nó và chuyển nó cho người đăng ký không đồng bộ. Cũng phá vỡ tất cả các phụ thuộc dữ liệu trong chương trình.

Đây là khá nhiều một mô hình diễn viên trên giá rẻ. Liên kết của Giorgio sẽ giúp.

Cuối cùng, vấn đề của bạn với các đối tượng tắt máy.

Khi bạn đang tham khảo đếm, bạn đã giải quyết được 50%. 50% còn lại là để giới thiệu lại các cuộc gọi lại. Vượt qua chủ sở hữu gọi lại một giới thiệu. Tắt cuộc gọi sau đó phải chờ không tính vào số tiền được tính. Không giải quyết các đồ thị đối tượng phức tạp; đó là vào bộ sưu tập rác thực sự. (Đó là động lực khiến Java không đưa ra bất kỳ lời hứa nào về việc khi nào hoặc nếu hoàn thành () sẽ được gọi; để giúp bạn thoát khỏi chương trình theo cách đó.)


2

Đối với những nhà thám hiểm trong tương lai: để bổ sung cho câu trả lời về mô hình diễn viên tôi muốn thêm CSP ( truyền đạt các quy trình tuần tự ), với một cái gật đầu cho gia đình tính toán quy trình lớn hơn. CSP tương tự như mô hình diễn viên, nhưng tách ra khác nhau. Bạn vẫn có một loạt các luồng, nhưng chúng giao tiếp qua các kênh cụ thể, thay vì cụ thể với nhau và cả hai quá trình phải sẵn sàng để gửi và nhận tương ứng trước khi xảy ra. Cũng có một ngôn ngữ chính thức để chứng minh mã CSP chính xác. Tôi vẫn đang chuyển sang sử dụng CSP rất nhiều, nhưng tôi đã sử dụng nó trong một vài dự án trong vài tháng nay và đó là những điều đơn giản hóa rất nhiều.

Đại học Kent có triển khai C ++ ( https://www.cs.kent.ac.uk/projects/ofa/c++csp/ , được nhân bản tại https://github.com/themasterchef/cppcsp2 ).


1

Văn học hoặc giấy tờ về các mẫu thiết kế xung quanh chủ đề. Một cái gì đó vượt quá giới thiệu về mutexes và semaphores. Chúng ta cũng không cần song song lớn, chỉ là cách thiết kế mô hình đối tượng để xử lý các sự kiện không đồng bộ từ các luồng khác một cách chính xác.

Tôi hiện đang đọc bài này và nó giải thích tất cả các vấn đề bạn có thể gặp phải và cách tránh chúng, trong C ++ (sử dụng thư viện luồng mới nhưng tôi nghĩ rằng các giải thích toàn cầu là hợp lệ cho trường hợp của bạn): http: //www.amazon. com / C-Đồng thời-Hành động-Thực hành-Đa luồng / dp / 1933988770 / ref = sr_1_1? tức là = UTF8 & qid = 1337934534 & sr = 8-1

Các cách để lập sơ đồ phân luồng của các thành phần khác nhau, do đó sẽ dễ dàng nghiên cứu và phát triển các giải pháp cho. (Đó là, một UML tương đương để thảo luận về các luồng trên các đối tượng và các lớp)

Cá nhân tôi sử dụng một UML được đơn giản hóa và chỉ cho rằng các thông báo được thực hiện không đồng bộ. Ngoài ra, điều này đúng giữa các "mô-đun" nhưng bên trong các mô-đun tôi không muốn biết.

Giáo dục nhóm phát triển của bạn về các vấn đề với mã đa luồng.

Cuốn sách sẽ giúp ích, nhưng tôi nghĩ rằng các bài tập / tạo mẫu và cố vấn có kinh nghiệm sẽ là beter.

Bạn sẽ làm gì?

Tôi hoàn toàn sẽ tránh việc mọi người không hiểu các vấn đề tương tranh làm việc trong dự án. Nhưng tôi đoán bạn không thể làm điều đó, vì vậy trong trường hợp cụ thể của bạn, ngoài việc cố gắng đảm bảo nhóm được giáo dục nhiều hơn, tôi không có ý kiến ​​gì.


Cảm ơn lời đề nghị của cuốn sách. Tôi có thể sẽ chọn nó.
koncurrency

Threading thực sự khó khăn. Không phải mọi lập trình viên đều phải đối mặt với thử thách. Trong thế giới kinh doanh, mỗi khi tôi thấy các luồng được sử dụng, chúng được bao quanh bởi các khóa theo cách mà không có hai luồng nào có thể chạy cùng một lúc. Có những quy tắc bạn có thể tuân theo để làm cho nó dễ dàng hơn, nhưng nó vẫn khó.
GlenPeterson

@GlenPeterson Đồng ý, bây giờ tôi có nhiều kinh nghiệm hơn (vì câu trả lời này) Tôi thấy rằng chúng ta cần sự trừu tượng tốt hơn để làm cho nó có thể quản lý được và không khuyến khích chia sẻ dữ liệu. May mắn thay, các nhà thiết kế ngôn ngữ dường như làm việc chăm chỉ về điều này.
Klaim

Tôi đã thực sự ấn tượng với Scala, đặc biệt là mang lại lợi ích lập trình chức năng về tính bất biến, tác dụng phụ tối thiểu cho Java, là hậu duệ trực tiếp của C ++. Nó chạy trên Máy ảo Java, do đó có thể không có hiệu suất bạn cần. Cuốn sách "Java hiệu quả" của Joshua Bloch là tất cả về việc giảm thiểu khả năng biến đổi, làm cho giao diện kín khí và an toàn luồng. Mặc dù dựa trên Java, tôi cá là bạn có thể áp dụng 80-90% cho C ++. Đặt câu hỏi về tính đột biến và trạng thái chia sẻ (hoặc tính biến đổi của trạng thái chia sẻ) trong các đánh giá mã của bạn có thể là bước đầu tiên tốt cho bạn.
GlenPeterson

1

Bạn đang trên đường bằng cách thừa nhận vấn đề và tích cực tìm kiếm một giải pháp. Đây là những gì tôi sẽ làm:

  • Hãy ngồi xuống và thiết kế một mô hình luồng cho ứng dụng của bạn. Đây là một tài liệu trả lời các câu hỏi như: Bạn có loại chủ đề nào? Những điều nên được thực hiện trong chủ đề nào? Những loại mô hình đồng bộ hóa khác nhau bạn nên sử dụng? Nói cách khác, nó nên mô tả "quy tắc tham gia" khi chiến đấu với các vấn đề đa luồng.
  • Sử dụng các công cụ phân tích luồng để kiểm tra lỗi cơ sở mã của bạn. Valgrind có một trình kiểm tra luồng được gọi là Helgrind , rất tốt trong việc phát hiện ra những thứ như trạng thái chia sẻ đang bị thao túng mà không đồng bộ hóa thích hợp. Có nhiều công cụ tốt nhất hiện có, hãy tìm chúng.
  • Xem xét việc di chuyển khỏi C ++. C ++ là một cơn ác mộng khi viết các chương trình đồng thời. Lựa chọn cá nhân của tôi sẽ là Erlang , nhưng đó là vấn đề của hương vị.

8
Chắc chắn -1 cho bit cuối cùng. Có vẻ như mã của OP đang sử dụng các công cụ nguyên thủy nhất chứ không phải các công cụ C ++ thực tế.
DeadMG

2
Tôi không đồng ý. Đồng thời trong C ++ là một cơn ác mộng ngay cả khi bạn sử dụng các cơ chế và công cụ C ++ thích hợp. Và xin lưu ý rằng tôi đã chọn từ " xem xét ". Tôi hoàn toàn hiểu rằng nó có thể không phải là sự thay thế thực tế, nhưng ở lại với C ++ mà không xem xét các lựa chọn thay thế chỉ là sự ngớ ngẩn đơn giản.
JesperE

4
@JesperE - xin lỗi, nhưng không. Đồng thời trong C ++ chỉ là một cơn ác mộng nếu bạn biến nó thành một mức độ quá thấp. Sử dụng một sự trừu tượng luồng phù hợp và nó không tệ hơn bất kỳ ngôn ngữ hoặc thời gian chạy nào khác. Và với cấu trúc ứng dụng phù hợp, nó thực sự khá dễ dàng như mọi thứ tôi từng thấy.
Michael Kohne

2
Nơi tôi làm việc tôi tin rằng chúng ta có một cấu trúc ứng dụng phù hợp, sử dụng các tóm tắt luồng chính xác, v.v. Mặc dù vậy, chúng tôi đã dành vô số thời gian trong nhiều năm để gỡ lỗi các lỗi mà đơn giản là nó sẽ không xuất hiện trong các ngôn ngữ được thiết kế đúng cho đồng thời. Nhưng tôi có cảm giác rằng chúng ta sẽ phải đồng ý không đồng ý về điều này.
JesperE

1
@JesperE: Tôi đồng ý với bạn. Mô hình Erlang (đã tồn tại các triển khai cho Scala / Java, Ruby và, theo như tôi biết, đối với C ++) mạnh hơn nhiều so với mã hóa trực tiếp bằng các luồng.
Giorgio

1

Nhìn vào ví dụ của bạn: Ngay sau khi Foo :: Shutdown bắt đầu thực thi, không thể gọi OnHttpRequestComplete để chạy nữa. Điều đó không có gì để làm với bất kỳ thực hiện, nó chỉ không thể làm việc.

Bạn cũng có thể lập luận rằng Foo :: Shutdown không thể gọi được trong khi cuộc gọi đến OnHttpRequestComplete đang chạy (chắc chắn là đúng) và có lẽ không nếu cuộc gọi đến OnHttpRequestComplete vẫn còn tồn tại.

Điều đầu tiên để có được không phải là khóa, vv mà là logic của những gì được phép hay không. Một mô hình đơn giản là lớp của bạn có thể có 0 hoặc nhiều yêu cầu chưa hoàn thành, 0 hoặc nhiều lần hoàn thành chưa được gọi, không hoặc nhiều lần hoàn thành đang chạy và đối tượng của bạn có muốn tắt máy hay không.

Foo :: Tắt máy sẽ được yêu cầu hoàn thành chạy hoàn thành, để chạy các yêu cầu chưa hoàn thành đến mức có thể tắt máy nếu có thể, không cho phép bắt đầu thêm bất kỳ yêu cầu nào nữa, không cho phép bắt đầu thêm yêu cầu.

Những gì bạn cần làm: Thêm thông số kỹ thuật vào các chức năng của bạn nói chính xác những gì họ sẽ làm. (Ví dụ: bắt đầu một yêu cầu http có thể thất bại sau khi Shutdown đã được gọi). Và sau đó viết các chức năng của bạn để họ đáp ứng các thông số kỹ thuật.

Khóa chỉ được sử dụng tốt nhất trong khoảng thời gian nhỏ nhất có thể để kiểm soát sửa đổi các biến được chia sẻ. Vì vậy, bạn có thể có một biến "PerformanceShutDown" được bảo vệ bởi Khóa.


0

Bạn sẽ làm gì?

Một cách trung thực; Tôi sẽ chạy đi thật nhanh.

Các vấn đề đồng thời là NASTY . Một cái gì đó có thể hoạt động hoàn hảo trong nhiều tháng và sau đó (do thời gian cụ thể của một số thứ) đột nhiên nổ tung trong khuôn mặt của khách hàng, không có cách nào để biết điều gì đã xảy ra, không hy vọng sẽ thấy một báo cáo lỗi (có thể tái tạo) tốt đẹp và không có cách nào để chắc chắn rằng đó không phải là một trục trặc phần cứng không liên quan gì đến phần mềm.

Tránh các vấn đề tương tranh cần bắt đầu trong giai đoạn thiết kế, bắt đầu với chính xác cách bạn sẽ thực hiện ("thứ tự khóa toàn cầu", mô hình diễn viên, ...). Đó không phải là thứ mà bạn cố gắng khắc phục trong cơn hoảng loạn điên cuồng với hy vọng mọi thứ sẽ không tự hủy sau khi phát hành sắp tới.

Lưu ý rằng tôi không nói đùa ở đây. Câu nói của bạn (" Hầu hết bắt nguồn từ các nhà phát triển khác đã rời nhóm. Các nhà phát triển hiện tại trong nhóm rất thông minh, nhưng chủ yếu là thiếu kinh nghiệm. ") Cho thấy tất cả những người có kinh nghiệm đã làm những gì tôi đã làm Tôi đang đề nghị.

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.