Sự khác biệt giữa các tùy chọn đồng bộ hóa luồng khác nhau trong C # là gì?


164

Ai đó có thể giải thích sự khác biệt giữa:

  • khóa (someobject) {}
  • Sử dụng Mutex
  • Sử dụng Semaphore
  • Sử dụng màn hình
  • Sử dụng các lớp đồng bộ hóa .Net khác

Tôi không thể tìm ra nó. Dường như với tôi hai cái đầu giống nhau?


Liên kết này đã giúp tôi rất nhiều: albahari.com/threading
Raphael

Câu trả lời:


135

Câu hỏi tuyệt vời. Tôi có thể sai .. Hãy để tôi thử .. Bản sửa đổi số 2 trong câu trả lời gốc của tôi .. với một chút hiểu biết hơn. Cảm ơn đã khiến tôi đọc :)

khóa (obj)

  • là một cấu trúc CLR cho việc đồng bộ hóa luồng (bên trong đối tượng?). Đảm bảo rằng chỉ một luồng có thể sở hữu khóa của đối tượng và nhập khối mã bị khóa. Các luồng khác phải đợi cho đến khi chủ sở hữu hiện tại từ bỏ khóa bằng cách thoát khỏi khối mã. Ngoài ra, bạn nên khóa trên một đối tượng thành viên riêng của lớp.

Màn hình

  • khóa (obj) được triển khai trong nội bộ bằng cách sử dụng Màn hình. Bạn nên thích khóa (obj) vì nó ngăn bạn khỏi bị lừa như quên quy trình dọn dẹp. Nếu bạn muốn thì đó là bằng chứng ngu ngốc.
    Sử dụng Monitor thường được ưu tiên hơn các mutexes, vì các màn hình được thiết kế dành riêng cho .NET Framework và do đó sử dụng tài nguyên tốt hơn.

Sử dụng khóa hoặc màn hình rất hữu ích để ngăn chặn việc thực thi đồng thời các khối mã nhạy cảm với luồng, nhưng các cấu trúc này không cho phép một luồng truyền thông một sự kiện đến một sự kiện khác. Điều này đòi hỏi các sự kiện đồng bộ hóa , là các đối tượng có một trong hai trạng thái, được báo hiệu và không có tín hiệu, có thể được sử dụng để kích hoạt và tạm dừng các luồng. Mutex, Semaphores là các khái niệm cấp hệ điều hành. ví dụ: với một mutex có tên, bạn có thể đồng bộ hóa qua nhiều exes (được quản lý) (đảm bảo rằng chỉ một phiên bản ứng dụng của bạn đang chạy trên máy.)

Đột biến:

  • Tuy nhiên, không giống như màn hình, một mutex có thể được sử dụng để đồng bộ hóa các luồng trên các quy trình. Khi được sử dụng để đồng bộ hóa giữa các quá trình, một mutex được gọi là mutex có tên vì nó được sử dụng trong một ứng dụng khác, và do đó nó không thể được chia sẻ bằng một biến toàn cục hoặc biến tĩnh. Nó phải được đặt tên để cả hai ứng dụng có thể truy cập cùng một đối tượng mutex. Ngược lại, lớp Mutex là một trình bao bọc cho cấu trúc Win32. Mặc dù nó mạnh hơn màn hình, một mutex đòi hỏi các chuyển tiếp xen kẽ đắt hơn về mặt tính toán so với các màn hình được yêu cầu bởi lớp Monitor.

Semaphores (làm tổn thương não của tôi).

  • Sử dụng lớp Semaphore để kiểm soát truy cập vào nhóm tài nguyên. Các chủ đề vào semaphore bằng cách gọi phương thức WaitOne, được kế thừa từ lớp WaitHandle và giải phóng semaphore bằng cách gọi phương thức Release. Số lượng trên một semaphore được giảm xuống mỗi khi một chủ đề đi vào semaphore, và tăng lên khi một chủ đề giải phóng semaphore. Khi số đếm bằng 0, các yêu cầu tiếp theo sẽ chặn cho đến khi các luồng khác giải phóng semaphore. Khi tất cả các luồng đã phát hành semaphore, số đếm ở giá trị tối đa được chỉ định khi semaphore được tạo. Một luồng có thể nhập semaphore nhiều lần .. Lớp Semaphore không thực thi nhận dạng luồng trên WaitOne hoặc Phát hành .. lập trình viên có trách nhiệm không làm hỏng. Semaphores có hai loại: semaphores địa phương và được đặt tênhệ thống ngữ nghĩa. Nếu bạn tạo một đối tượng Semaphore bằng cách sử dụng hàm tạo chấp nhận tên, thì nó được liên kết với một semaphore hệ điều hành của tên đó. Các ngữ nghĩa hệ thống được đặt tên có thể nhìn thấy trong toàn bộ hệ điều hành và có thể được sử dụng để đồng bộ hóa các hoạt động của các quy trình. Một semaphore địa phương chỉ tồn tại trong quá trình của bạn. Nó có thể được sử dụng bởi bất kỳ luồng nào trong quy trình của bạn có tham chiếu đến đối tượng Semaphore cục bộ. Mỗi đối tượng Semaphore là một semaphore địa phương riêng biệt.

TRANG ĐỂ ĐỌC - Đồng bộ hóa chủ đề (C #)


18
Bạn cho rằng Monitorkhông cho phép giao tiếp là không chính xác; bạn vẫn có thể Pulsevv với mộtMonitor
Marc Gravell

3
Kiểm tra một mô tả thay thế của Semaphores - stackoverflow.com/a/40473/968003 . Hãy nghĩ về semaphores như bouncers tại một hộp đêm. Có một số người chuyên dụng được phép vào câu lạc bộ cùng một lúc. Nếu câu lạc bộ đầy, không ai được vào, nhưng ngay khi một người rời khỏi, một người khác có thể vào.
Alex Klaus

31

Re "Sử dụng các lớp đồng bộ hóa .Net khác" - một số trong những lớp khác bạn nên biết về:

Ngoài ra còn có nhiều cấu trúc khóa (chi phí thấp) trong CCR / TPL ( CTP mở rộng song song ) - nhưng IIRC, chúng sẽ được cung cấp trong .NET 4.0


Vì vậy, nếu tôi muốn một giao tiếp tín hiệu đơn giản (giả sử hoàn thành một async op) - tôi nên Monitor.Pulse? hoặc sử dụng SemaphoreSlim hoặc TaskCompletionSource?
Vivek

Sử dụng TaskCompletionSource cho hoạt động không đồng bộ. Về cơ bản, ngừng suy nghĩ về các chủ đề và bắt đầu suy nghĩ về các nhiệm vụ (đơn vị công việc). Chủ đề là một chi tiết thực hiện và không liên quan. Bằng cách trả lại TCS, bạn có thể trả về kết quả, lỗi hoặc xử lý hủy và nó có thể dễ dàng kết hợp với các hoạt động không đồng bộ khác (chẳng hạn như async await hoặc ContinWith).
Simon Gillbee

14

Như đã nêu trong ECMA và như bạn có thể quan sát từ các phương thức Reflected, câu lệnh khóa về cơ bản tương đương với

object obj = x;
System.Threading.Monitor.Enter(obj);
try {
   
}
finally {
   System.Threading.Monitor.Exit(obj);
}

Từ ví dụ đã nói ở trên, chúng ta thấy rằng Màn hình có thể khóa trên các đối tượng.

Mutexe là hữu ích khi bạn cần đồng bộ hóa quá trình vì chúng có thể khóa trên một định danh chuỗi. Cùng một quy trình nhận dạng chuỗi có thể được sử dụng bởi các quy trình khác nhau để có được khóa.

Semaphores giống như Mutexes trên steroid, chúng cho phép truy cập đồng thời bằng cách cung cấp số lượng truy cập đồng thời tối đa '. Khi đạt đến giới hạn, semaphore bắt đầu chặn mọi quyền truy cập vào tài nguyên cho đến khi một trong những người gọi giải phóng semaphore.


5
Đường cú pháp này đã được thay đổi một chút trong C # 4 Kiểm tra blogs.msdn.com/ericlippert/archive/2009/03/06/...
Peter Gfader

14

Tôi đã hỗ trợ các lớp & CLR cho luồng trong DotGNU và tôi có một vài suy nghĩ ...

Trừ khi bạn yêu cầu khóa quá trình chéo, bạn nên luôn luôn tránh sử dụng Mutex & Semaphores. Các lớp trong .NET này là các hàm bao quanh Win32 Mutex và Semaphores và có trọng lượng khá nặng (chúng yêu cầu chuyển đổi ngữ cảnh sang Kernel đắt tiền - đặc biệt là nếu khóa của bạn không bị tranh chấp).

Như những người khác được đề cập, câu lệnh khóa C # là ma thuật trình biên dịch cho Monitor. Entry và Monitor.Exit (tồn tại trong một lần thử / cuối cùng).

Màn hình có cơ chế tín hiệu / chờ đơn giản nhưng mạnh mẽ mà Mutexes không có thông qua các phương thức Monitor.Pulse / Monitor.Wait. Tương đương Win32 sẽ là các đối tượng sự kiện thông qua CreatEvent, thực tế cũng tồn tại trong .NET với tên WaitHandles. Mô hình Pulse / Wait tương tự như pthread_signal và pthread_wait của Unix nhưng nhanh hơn vì chúng có thể hoàn toàn là các hoạt động ở chế độ người dùng trong trường hợp không tranh chấp.

Monitor.Pulse / Wait rất đơn giản để sử dụng. Trong một luồng, chúng ta khóa một đối tượng, kiểm tra cờ / trạng thái / thuộc tính và nếu đó không phải là điều chúng ta mong đợi, hãy gọi Monitor.Wait sẽ giải phóng khóa và đợi cho đến khi một xung được gửi. Khi chờ đợi trở lại, chúng tôi lặp lại và kiểm tra lại cờ / trạng thái / thuộc tính. Trong luồng khác, chúng tôi khóa đối tượng bất cứ khi nào chúng tôi thay đổi cờ / trạng thái / thuộc tính và sau đó gọi PulseAll để đánh thức mọi luồng nghe.

Thông thường chúng tôi muốn các lớp của chúng tôi là luồng an toàn vì vậy chúng tôi đặt các khóa trong mã của chúng tôi. Tuy nhiên, thường thì lớp chúng ta sẽ chỉ được sử dụng bởi một luồng. Điều này có nghĩa là các khóa không cần thiết làm chậm mã của chúng tôi ... đây là nơi tối ưu hóa thông minh trong CLR có thể giúp cải thiện hiệu suất.

Tôi không chắc chắn về việc triển khai khóa của Microsoft nhưng trong DotGNU và Mono, cờ trạng thái khóa được lưu trữ trong tiêu đề của mọi đối tượng. Mọi đối tượng trong .NET (và Java) có thể trở thành một khóa vì vậy mọi đối tượng cần hỗ trợ điều này trong tiêu đề của chúng. Trong triển khai DotGNU, có một cờ cho phép bạn sử dụng hàm băm toàn cầu cho mọi đối tượng được sử dụng làm khóa - điều này có lợi ích là loại bỏ chi phí 4 byte cho mọi đối tượng. Điều này không tốt cho bộ nhớ (đặc biệt là đối với các hệ thống nhúng không được phân luồng nhiều) nhưng lại ảnh hưởng đến hiệu năng.

Cả Mono và DotGNU đều sử dụng hiệu quả các mutexes để thực hiện khóa / chờ nhưng sử dụng so sánh và trao đổi kiểu spinlock hoạt động để loại bỏ nhu cầu thực sự thực hiện khóa cứng trừ khi thực sự cần thiết:

Bạn có thể xem một ví dụ về cách màn hình có thể được thực hiện ở đây:

http://cvs.savannah.gnu.org/viewvc/dotgnu-pnet/pnet/engine/lib_monitor.c?revision=1.7&view=markup


9

Một cảnh báo bổ sung để khóa trên bất kỳ Mutex được chia sẻ nào mà bạn đã xác định bằng ID chuỗi là nó sẽ mặc định là một mutex "Local \" và sẽ không được chia sẻ giữa các phiên trong môi trường máy chủ đầu cuối.

Tiền tố định danh chuỗi của bạn với "Toàn cầu \" để đảm bảo quyền truy cập vào tài nguyên hệ thống được chia sẻ được kiểm soát chính xác. Tôi vừa gặp phải một đống vấn đề khi đồng bộ hóa thông tin liên lạc với một dịch vụ chạy trong tài khoản HỆ THỐNG trước khi tôi nhận ra điều này.


5

Tôi sẽ cố gắng tránh "lock ()", "Mutex" và "Monitor" nếu bạn có thể ...

Kiểm tra không gian tên mới System.Collections.Conc hiện tại .NET 4
Nó có một số lớp bộ sưu tập an toàn chủ đề đẹp

http://msdn.microsoft.com/en-us/l Library / system.collections.concản.aspx

Đồng thời đá từ! không khóa tay nữa cho tôi!


2
Tránh khóa nhưng sử dụng Monitor? Tại sao?
mafu

@mafutrct Vì bạn cần tự chăm sóc đồng bộ hóa.
Peter Gfader

Ồ, bây giờ tôi hiểu rồi, ý bạn là tránh TẤT CẢ ba ý tưởng được đề cập. Nghe có vẻ như bạn sẽ sử dụng Màn hình nhưng không sử dụng khóa / Mutex.
mafu

Đừng bao giờ sử dụng System.Collections.Conc hiện. Chúng là một nguồn chính của các điều kiện cuộc đua, và cũng chặn luồng người gọi.
Alexander Danilov

-2

Trong hầu hết các trường hợp, bạn không nên sử dụng khóa (= Màn hình) hoặc mutexes / semaphores. Tất cả đều chặn luồng hiện tại.

Và bạn chắc chắn không nên sử dụng System.Collections.Concurrent các lớp - chúng là nguồn chính của các điều kiện chủng tộc vì không hỗ trợ giao dịch giữa nhiều bộ sưu tập và cũng chặn chuỗi hiện tại.

Đáng ngạc nhiên là .NET không có cơ chế hiệu quả để đồng bộ hóa.

Tôi đã triển khai hàng đợi nối tiếp từ GCD ( Objc/Swiftthế giới) trên C # - rất nhẹ, không chặn công cụ đồng bộ hóa sử dụng nhóm luồng, với các bài kiểm tra.

Đó là cách tốt nhất để đồng bộ hóa mọi thứ trong hầu hết các trường hợp - từ truy cập cơ sở dữ liệu (xin chào sqlite) đến logic kinh doanh.

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.