11 Chủ đề trong C# - Phần 1: Tổng quan và khái niệm

(Post 02/10/2007) C# hỗ trợ thực thi song song mã thông qua đa luồng. Một luồng là một đường dẫn thực thi độc lập, có thể chạy đồng thời với các luồng khác.

Chương trình AC # bắt đầu trong một luồng duy nhất được tạo tự động bởi CLR và hệ điều hành (luồng “chính”), và được thực hiện đa luồng bằng cách tạo các luồng bổ sung. Đây là một ví dụ đơn giản và đầu ra của nó:

Tất cả các ví dụ giả sử các không gian tên sau được nhập, trừ khi được chỉ định khác:

sử dụng Hệ thống;
sử dụng System.Threading;

class ThreadTest {
static void Main () {
Thread t = new Thread (WriteY);
t.Start (); // Chạy WriteY trên luồng mới
while (true) Console.Write (“x”); // Ghi ‘x’ mãi mãi
}

static void WriteY () {
while (true) Console.Write (“y”); // Viết ‘y’ mãi mãi
}
}

Các chủ đề chính tạo ra một thread mới t mà trên đó nó chạy một phương pháp mà nhiều lần in các nhân vật y . Đồng thời, luồng chính liên tục in ký tự x .

CLR chỉ định từng luồng bộ nhớ riêng của nó để các biến cục bộ được giữ riêng biệt. Trong ví dụ tiếp theo, chúng tôi xác định một phương thức với một biến cục bộ, sau đó gọi phương thức đồng thời trên luồng chính và một luồng mới được tạo:

static void Main () {
new Thread (Go) .Start (); // Gọi Go () trên một luồng mới
Go (); // Gọi Go () trên chuỗi chính
}

static void Go () {
// Khai báo và sử dụng một biến cục bộ – ‘cycle’
for (int cycle = 0; cycle <5; cycle ++) Console.Write (‘?’) ;
}

Một bản sao riêng biệt của biến chu kỳ được tạo trên ngăn xếp bộ nhớ của mỗi luồng, và do đó, có thể đoán trước được kết quả là mười dấu hỏi.

Các luồng chia sẻ dữ liệu nếu chúng có một tham chiếu chung đến cùng một cá thể đối tượng. Đây là một ví dụ:

lớp ThreadTest {
bool done;

static void Main () {
ThreadTest tt = new ThreadTest (); // Tạo một thể hiện chung
new Thread (tt.Go) .Start ();
tt.Go ();
}

// Lưu ý rằng Go bây giờ là một phương thức thể hiện
void Go () {
if (! Done) {done = true; Console.WriteLine (“Xong”); }
}
}

Bởi vì cả hai luồng gọi Go () trên cùng một cá thể ThreadTest , chúng chia sẻ trường xong . Điều này dẫn đến “Xong” được in một lần thay vì hai lần:

Trường tĩnh cung cấp một cách khác để chia sẻ dữ liệu giữa các luồng. Đây là ví dụ tương tự với done dưới dạng trường tĩnh:

class ThreadTest {
static bool done; // Trường tĩnh được chia sẻ giữa tất cả các luồng

static void Main () {
new Thread (Go) .Start ();
Đi();
}

static void Go () {
if (! done) {done = true; Console.WriteLine (“Xong”); }
}
}

Cả hai ví dụ này minh họa một khái niệm chính khác – đó là an toàn luồng (hay nói đúng hơn là thiếu nó!) Đầu ra thực sự không xác định: có thể (mặc dù không chắc) rằng “Xong” có thể được in hai lần. Tuy nhiên, nếu chúng tôi hoán đổi thứ tự của các câu lệnh trong phương pháp Go , thì tỷ lệ “Xong” được in hai lần sẽ tăng lên đáng kể:

static void Go () {
if (! done) {Console.WriteLine (“Xong”); xong = true; }
}

Vấn đề là một thread có thể được đánh giá nếu tuyên bố đúng như các chủ đề khác đang thực hiện các WriteLine tuyên bố – trước khi nó có cơ hội để thiết lập thực hiện để đúng .

Biện pháp khắc phục là lấy một khóa độc quyền trong khi đọc và ghi vào trường chung. C # cung cấp câu lệnh khóa chỉ cho mục đích này:

class ThreadSafe {
static bool done;
static object locker = new object ();

static void Main () {
new Thread (Go) .Start ();
Đi();
}

static void Go () {
lock (locker) {
if (! done) {Console.WriteLine (“Xong”); xong = true; }
}
}
}

Khi hai luồng đồng thời cạnh tranh với một khóa (trong trường hợp này là khóa ), một luồng sẽ đợi hoặc chặn cho đến khi khóa khả dụng. Trong trường hợp này, nó đảm bảo mỗi lần chỉ có một luồng có thể nhập phần mã quan trọng và “Xong” sẽ được in chỉ một lần. Mã được bảo vệ theo cách như vậy – khỏi tính không xác định trong ngữ cảnh đa luồng – được gọi là an toàn luồng .

Tạm dừng, hoặc chặn, là một tính năng cần thiết trong việc điều phối hoặc đồng bộ hóa hoạt động của các luồng. Chờ đợi một khóa độc quyền là một lý do mà một luồng có thể chặn. Cách khác là nếu một chuỗi muốn tạm dừng hoặc Ngủ trong một khoảng thời gian:

Thread.Sleep (TimeSpan.FromSeconds (30)); // Chặn trong 30 giây

Một chuỗi cũng có thể đợi một chuỗi khác kết thúc, bằng cách gọi phương thức Tham gia của nó :

Thread t = new Thread (Go); // Giả sử Go là một phương thức tĩnh nào đó
t.Start ();
t.Join (); // Chờ (khối) cho đến khi chuỗi t kết thúc

Một luồng, trong khi bị chặn, không tiêu tốn tài nguyên CPU.

Cách hoạt động của luồng

Đa luồng được quản lý nội bộ bởi một bộ lập lịch luồng, một chức năng mà CLR thường ủy quyền cho hệ điều hành. Bộ lập lịch luồng đảm bảo tất cả các luồng đang hoạt động được phân bổ thời gian thực thi thích hợp và các luồng đang chờ hoặc bị chặn – ví dụ – trên một khóa độc quyền hoặc trên đầu vào của người dùng – không tiêu tốn thời gian CPU.

Trên máy tính một bộ xử lý, bộ lập lịch luồng thực hiện phân chia thời gian – thực hiện chuyển đổi nhanh chóng giữa mỗi luồng đang hoạt động. Điều này dẫn đến hành vi “thay đổi”, chẳng hạn như trong ví dụ đầu tiên, trong đó mỗi khối của một ký tự X hoặc Y lặp lại tương ứng với một lát thời gian được phân bổ cho luồng. Trong Windows XP, một lát thời gian thường nằm trong vùng hàng chục mili giây – được chọn để lớn hơn nhiều so với chi phí CPU trong thực tế chuyển ngữ cảnh giữa một luồng này và một luồng khác (thường nằm trong vùng vài micro giây) .

Trên máy tính nhiều bộ xử lý, đa luồng được thực hiện với sự kết hợp giữa cắt thời gian và đồng thời thực sự – nơi các luồng khác nhau chạy mã đồng thời trên các CPU khác nhau. Gần như chắc chắn vẫn sẽ có một số khoảng thời gian, vì hệ điều hành cần phải phục vụ các luồng riêng của nó – cũng như các luồng của các ứng dụng khác.

Một luồng được cho là được ưu tiên khi quá trình thực thi của nó bị gián đoạn do một yếu tố bên ngoài chẳng hạn như cắt thời gian. Trong hầu hết các tình huống, một luồng không có quyền kiểm soát thời gian và vị trí mà nó được ưu tiên.

Chủ đề so với Quy trình

Tất cả các luồng trong một ứng dụng được chứa một cách hợp lý trong một quy trình – đơn vị hệ điều hành mà ứng dụng chạy trong đó.

Các luồng có những điểm tương đồng nhất định với các quy trình – ví dụ, các quy trình thường được phân chia theo thời gian với các quy trình khác đang chạy trên máy tính theo cách giống như các luồng trong một ứng dụng C #. Sự khác biệt chính là các quy trình được cách ly hoàn toàn với nhau; các luồng chia sẻ bộ nhớ (heap) với các luồng khác chạy trong cùng một ứng dụng. Đây là những gì làm cho các luồng trở nên hữu ích: một luồng có thể tìm nạp dữ liệu trong nền, trong khi một luồng khác hiển thị dữ liệu khi nó đến.

Khi nào sử dụng chủ đề

Một ứng dụng phổ biến cho đa luồng đang thực hiện các tác vụ tốn thời gian trong nền. Luồng chính tiếp tục chạy, trong khi luồng công nhân thực hiện công việc nền của nó. Với các ứng dụng Windows Forms, nếu chuỗi chính bị ràng buộc thực hiện một thao tác kéo dài, các thông báo trên bàn phím và chuột sẽ không thể được xử lý và ứng dụng sẽ không phản hồi. Vì lý do này, nên chạy các tác vụ tốn thời gian trên các luồng công nhân ngay cả khi luồng chính có người dùng bị kẹt trên hộp thoại phương thức “Đang xử lý… vui lòng đợi” trong trường hợp chương trình không thể tiếp tục cho đến khi một tác vụ cụ thể hoàn tất. Điều này đảm bảo ứng dụng không bị hệ điều hành gắn thẻ là “Không phản hồi”, khiến người dùng buộc phải kết thúc quá trình một cách thất vọng! Cách tiếp cận hộp thoại phương thức cũng cho phép triển khai nút “Hủy”, vì biểu mẫu phương thức sẽ tiếp tục nhận các sự kiện trong khi tác vụ thực được thực hiện trên luồng công nhân. CácLớp BackgroundWorker chỉ hỗ trợ kiểu sử dụng này.

Trong trường hợp các ứng dụng không phải giao diện người dùng, chẳng hạn như Windows Service, đa luồng có ý nghĩa đặc biệt khi một tác vụ có khả năng tốn thời gian vì nó đang chờ phản hồi từ một máy tính khác (chẳng hạn như máy chủ ứng dụng, máy chủ cơ sở dữ liệu hoặc máy khách). Có một luồng công nhân thực hiện nhiệm vụ có nghĩa là luồng kích động ngay lập tức có thể tự do làm những việc khác.

Một cách sử dụng khác cho đa luồng là trong các phương pháp thực hiện các phép tính chuyên sâu. Các phương thức như vậy có thể thực thi nhanh hơn trên máy tính đa xử lý nếu khối lượng công việc được chia cho nhiều luồng. (Người ta có thể kiểm tra số lượng bộ xử lý thông qua thuộc tính Environment.ProcessorCount ).

Ứng dụng AC # có thể trở thành đa luồng theo hai cách: bằng cách tạo và chạy các luồng bổ sung một cách rõ ràng hoặc sử dụng một tính năng của .NET framework tạo ra các luồng một cách ngầm định – chẳng hạn như BackgroundWorker , luồng tổng hợp , bộ đếm thời gian phân luồng, máy chủ Từ xa, hoặc Dịch vụ Web hoặc ứng dụng ASP.NET. Trong những trường hợp sau này, không có lựa chọn nào khác ngoài việc nắm lấy đa luồng. Một máy chủ web ASP.NET đơn luồng sẽ không hay ho – ngay cả khi điều đó là khả thi! May mắn thay, với các máy chủ ứng dụng không trạng thái, đa luồng thường khá đơn giản; mối quan tâm duy nhất của một người có lẽ là cung cấp các cơ chế khóa thích hợp xung quanh dữ liệu được lưu trong bộ nhớ cache trong các biến tĩnh.

Khi nào không sử dụng chủ đề

Đa luồng cũng đi kèm với những bất lợi. Điều lớn nhất là nó có thể dẫn đến các chương trình phức tạp hơn rất nhiều. Có nhiều luồng không tự nó tạo ra sự phức tạp; đó là sự tương tác giữa các chuỗi tạo ra sự phức tạp. Điều này áp dụng cho dù tương tác có chủ ý hay không và có thể dẫn đến chu kỳ phát triển dài, cũng như tính nhạy cảm liên tục với các lỗi gián đoạn và không thể tái tạo. Vì lý do này, việc duy trì sự tương tác như vậy trong một thiết kế đa luồng đơn giản – hoặc hoàn toàn không sử dụng đa luồng – trừ khi bạn có thiên hướng đặc biệt là viết lại và gỡ lỗi!

Đa luồng cũng đi kèm với chi phí tài nguyên và CPU trong việc phân bổ và chuyển đổi luồng nếu được sử dụng quá mức. Đặc biệt, khi tham gia vào I / O đĩa nặng, có thể nhanh hơn nếu chỉ có một hoặc hai luồng công nhân thực hiện các tác vụ theo trình tự, thay vì có vô số luồng mỗi luồng thực hiện một tác vụ cùng một lúc. Sau đó, chúng tôi mô tả cách triển khai hàng đợi Nhà sản xuất / Người tiêu dùng , hàng đợi chỉ cung cấp chức năng này.

(Sưu tầm)

FPT Aptech – Hệ Thống Đào Tạo Lập Trình Viên Quốc Tế

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