11 Chủ đề trong C # - Phần 3: Cơ bản về đồng bộ hóa

(Post 09/10/2007) Các bảng sau đây tóm tắt các công cụ .NET có sẵn để điều phối hoặc đồng bộ hóa các hành động của các luồng:

  • Phần 1: Tổng quan và khái niệm
  • Phần 2: Tạo và bắt đầu chủ đề

Phương pháp chặn đơn giản

Xây dựngMục đích
NgủCác khối trong một khoảng thời gian nhất định
Tham giaChờ một chuỗi khác kết thúc

Khóa cấu trúc

Xây dựngMục đíchXuyên suốt quá trình?Tốc độ
KhóaĐảm bảo chỉ một luồng có thể truy cập tài nguyên hoặc phần mãKhôngNhanh
MutexĐảm bảo chỉ một luồng có thể truy cập tài nguyên hoặc phần mã. Có thể được sử dụng để ngăn nhiều trường hợp của một ứng dụng khởi độngĐúngvừa phải
SemaphoreĐảm bảo không nhiều hơn một số lượng quy định của chuỗi có thể truy cập vào một tài nguyên hoặc phần mã.Đúngvừa phải

Các khung cảnh đồng bộ hóa cũng được cung cấp để tự động khóa).

Cấu trúc báo hiệu

Xây dựngMục đíchXuyên suốt quá trình?Tốc độ
EventWaitHandleCho phép một chuỗi chờ cho đến khi nó nhận được tín hiệu từ một chuỗi khácĐúngvừa phải
Chờ và Xung *Cho phép một chuỗi chờ cho đến khi đáp ứng điều kiện chặn tùy chỉnhKhôngvừa phải

Cấu trúc đồng bộ hóa không chặn *

Xây dựngMục đíchXuyên suốt quá trình?Tốc độ
Đã khóa *Để thực hiện các hoạt động nguyên tử không chặn đơn giảncó (giả sử bộ nhớ được chia sẻ)rất nhanh
dễ bay hơi *Để cho phép quyền truy cập không chặn an toàn vào các trường riêng lẻ bên ngoài khóarất nhanh

* Được đề cập trong Phần 4

Chặn

Khi một luồng chờ hoặc tạm dừng do sử dụng các cấu trúc được liệt kê trong bảng trên, nó được cho là bị chặn. Sau khi bị chặn, một luồng ngay lập tức ngừng phân bổ thời gian CPU, thêm WaitSleepJoin vào thuộc tính ThreadState của nó và không được lên lịch lại cho đến khi được bỏ chặn. Mở khóa xảy ra theo một trong bốn cách (nút nguồn của máy tính không được tính!):

  • bởi điều kiện chặn được thỏa mãn
  • bởi hoạt động hết thời gian (nếu thời gian chờ được chỉ định)
  • do bị gián đoạn qua Thread.Interrupt
  • bằng cách bị hủy bỏ qua Thread.Abort

Một luồng không được coi là bị chặn nếu quá trình thực thi của nó bị tạm dừng qua phương thức Tạm ngưng (không được dùng nữa) .

Ngủ và quay

Đang gọi Thread.Sleep chặn chuỗi hiện tại trong một khoảng thời gian nhất định (hoặc cho đến khi bị gián đoạn):

static void Main () {
Thread.Sleep (0); // từ bỏ lát cắt thời gian CPU
Thread.Sleep (1000); // ngủ trong 1000 mili giây
Thread.Sleep (TimeSpan.FromHours (1)); // ngủ trong 1 giờ
Thread.Sleep (Timeout.Infinite); // ngủ cho đến khi bị gián đoạn
}

Chính xác hơn, Thread.Sleep giải phóng CPU, yêu cầu luồng không được lên lịch lại cho đến khi hết khoảng thời gian nhất định. Thread.Sleep (0) ngừng hoạt động của CPU vừa đủ lâu để cho phép bất kỳ luồng nào đang hoạt động khác có trong hàng đợi cắt thời gian (nếu có) được thực thi.

Thread.Sleep là duy nhất trong số các phương pháp chặn trong đó đình chỉ việc bơm thông báo Windows trong ứng dụng Windows Forms hoặc môi trường COM trên một chuỗi mà mô hình căn hộ đơn luồngĐược sử dụng. Điều này ít gây hậu quả với các ứng dụng Windows Forms, trong đó bất kỳ thao tác chặn kéo dài nào trên chuỗi giao diện người dùng chính sẽ khiến ứng dụng không phản hồi – và do đó thường được tránh – bất kể việc bơm thông báo có bị tạm ngưng “về mặt kỹ thuật” hay không. Tình hình phức tạp hơn trong môi trường lưu trữ COM cũ, nơi đôi khi có thể mong muốn ngủ trong khi vẫn giữ thông báo bơm. Chris Brumme của Microsoft thảo luận về vấn đề này trong nhật ký web của anh ấy (tìm kiếm: ‘ COM “Chris Brumme” ‘).

Lớp Thread cũng cung cấp một phương thức SpinWait , phương thức này không làm mất bất kỳ thời gian nào của CPU, thay vào đó là vòng lặp CPU – giữ cho nó “bận vô ích” trong số lần lặp đã cho. 50 lần lặp có thể tương đương với khoảng thời gian tạm dừng khoảng một micro giây, mặc dù điều này phụ thuộc vào tốc độ và tải của CPU. Về mặt kỹ thuật, SpinWait không phải là một phương pháp chặn: một chuỗi đang chờ quay không có ThreadState của WaitSleepJoin và không thể bị gián đoạn sớm bởi một chuỗi khác. SpinWaithiếm khi được sử dụng – mục đích chính của nó là đợi trên một tài nguyên dự kiến ​​sẽ sẵn sàng rất sớm (bên trong có thể là một micro giây) mà không gọi Sleep và lãng phí thời gian CPU bằng cách buộc thay đổi luồng. Tuy nhiên, kỹ thuật này chỉ có lợi trên máy tính đa bộ xử lý: trên máy tính đơn bộ xử lý, không có cơ hội để trạng thái của tài nguyên thay đổi cho đến khi luồng quay kết thúc lát thời gian – điều này làm mất đi mục đích quay đầu tiên. Và việc gọi SpinWait thường xuyên hoặc trong thời gian dài sẽ gây lãng phí thời gian của CPU.

Chặn so với Spinning

Một chuỗi có thể đợi một điều kiện nhất định bằng cách xoay vòng rõ ràng bằng cách sử dụng vòng lặp thăm dò, ví dụ:

while (! tiến hành);
hoặc:
while (DateTime.Now <nextStartTime);

Điều này rất lãng phí thời gian của CPU: theo như CLR và hệ điều hành, luồng đang thực hiện một phép tính quan trọng, và do đó được phân bổ tài nguyên cho phù hợp! Một luồng lặp trong trạng thái này không được tính là bị chặn, không giống như một luồng đang chờ trên EventWaitHandle (cấu trúc thường được sử dụng cho các tác vụ báo hiệu như vậy).

Một biến thể đôi khi được sử dụng là sự kết hợp giữa chặn và quay:

while (! tiến hành) Thread.Sleep (x); // “Quay-Ngủ!”

X càng lớn thì CPU càng hiệu quả; sự đánh đổi là độ trễ tăng lên. Bất cứ điều gì trên 20ms đều phải chịu chi phí không đáng kể – trừ khi điều kiện trong vòng lặp while đặc biệt phức tạp.

Ngoại trừ độ trễ nhỏ, sự kết hợp giữa quay và ngủ này có thể hoạt động khá tốt (tùy thuộc vào các vấn đề đồng thời trên cờ tiếp diễn , được thảo luận trong Phần 4 ). Có lẽ công dụng lớn nhất của nó là khi một lập trình viên từ bỏ việc bắt một cấu trúc báo hiệu phức tạp hơn hoạt động!

Tham gia một chuỗi

Bạn có thể chặn cho đến khi một chuỗi khác kết thúc bằng cách gọi Tham gia :

lớp JoinDemo {
static void Main () {
Thread t = mới Thread ( đại biểu () {Console.ReadLine ();});
t.Start ();
t.Join (); // Chờ cho đến khi luồng t kết thúc
Console.WriteLine (“ReadLine của luồng t hoàn tất!”);
}
}

Các Tham gia phương pháp cũng chấp nhận một đối số timeout – trong mili giây, hoặc là một TimeSpan , trở về false nếu Tham gia timed out chứ không phải là tìm thấy sự kết thúc của chủ đề. Tham gia với các chức năng thời gian chờ thay vì ngủ – trên thực tế, hai dòng mã sau gần như giống hệt nhau:

Chủ đề .Sleep (1000);
Chủ đề .CurrentThread.Join (1000);

(Sự khác biệt của chúng chỉ rõ ràng trong các ứng dụng căn hộ đơn luồng với khả năng tương tác COM và bắt nguồn từ sự tinh tế trong ngữ nghĩa bơm tin nhắn của Windows được mô tả trước đây: Tham gia giữ cho việc bơm tin nhắn vẫn tồn tại trong khi bị chặn; Sleep tạm dừng việc bơm tin nhắn).

(Sưu tầm)

FPT Aptech trực thuộc Tổ chức Giáo dục FPT có hơn 25 năm kinh nghiệm đào tạo lập trình viên quốc tế tại Việt Nam, và luôn là sự lựa chọn ưu tiên của các sinh viên và nhà tuyển dụng.
0981578920
icons8-exercise-96