Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 12 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
12
Dung lượng
109,84 KB
Nội dung
C# và các lớp cơ sở Thread ( luồng ) – Phần 2 ThreadPlayaround Interval to display results at?> 1000000 Starting thread: Main Thread Main Thread: Current Culture = en-GB Main Thread: count has reached 1000000 Starting thread: Worker Worker: Current Culture = en-GB Main Thread: count has reached 2000000 Worker: count has reached 1000000 Main Thread: count has reached 3000000 Worker: count has reached 2000000 Main Thread: count has reached 4000000 Worker: count has reached 3000000 Main Thread: count has reached 5000000 Main Thread: count has reached 6000000 Worker: count has reached 4000000 Main Thread: count has reached 7000000 Worker: count has reached 5000000 Main Thread: count has reached 8000000 Main Thread Finished Worker: count has reached 6000000 Worker: count has reached 7000000 Worker: count has reached 8000000 Worker Thread Finished Ta có thể thấy các luồng thực sự làm việc song song. luồng chính bắt đầu và đếm đến 1 triệu. ở 1 vài thời điểm , trong khi luồng chính đang đếm thì luồng công việc ( worker thread ) bắt đầu, và từ đó 2 luồng xử lí cùng tốc độ cho đến khi chúng hoàn thành. 1 điều lưu ý là khi ta xử lí đa luồng trên 1 CPU thì ta không hề tiết kiệm được thời gian , ví dụ trên máy có 1 bộ xử lí , nếu có 2 luồng đếm đến 8 triệu sẽ bằng thời gian 1 luồng đếm đến 16 triệu. nhưng lợi điểm của đa luồng là : đầu tiên ta có thể tăng tốc độ đáp ứng, nghĩa là 1 luồng có thể giao tiếp với người dùng trong khi luồng khác làm một công việc gì đó phía sau hậu trường.thứ hai, ta sẽ tiết kiệm thời gian nếu 1 hay nhiều luồng đang làm 1 công việc gì đó không dính dáng đến thời gian CPU, như là đợi cho dữ lệu đến đển nhận từ internet, bởi vì các luồng khác có thể nhảy vào tiến trình của chúng trong khi luồng không hoạt động đang chờ. Độ ưu tiên luồng Ta có thể đăng kí các độ ưu tiên khácnhau cho các luồng khác nhau trong 1 tiến trình. nói chung , 1 luồng không đưọc cấp phát 1 time slice nào nếu có 1 luồng có độ ưu tiên cao hơn đang làm việc. lợi điểm của điều này là ta có thể thiết lập độ ưu tiên cao hơn cho luồng xử lí việc nhập của người dùng. Các luồng có độ ưu tiên cao có thể cản trở các luồng có độ ưu tiên thấp cho đó ta cần thận trọng khi cấp quyền ưu tiên . độ ưu tiên của luồng được định nghĩa là các giá trị trong bản liệt kê ThreadPriority. các giá trị : Highest, AboveNormal, Normal, BelowNormal, Lowest Lưu ý rằng mỗi luồng có 1 độ ưu tiên cơ sở. và những giá trị này liên quan đến độ ưu tiên trong tiến trình. cho 1 luồng có độ ưu tiên cao hơn đảm bảo nó sẽ chiếm quyền ưu tiên so với các luồng khác trong tiến trình. nhưng cũng có 1 số luồng khác của hệ thống đang chạy có quyền ưu tiên còn cao hơn . Windows có khuynh hướng đặt độ ưu tiên cao cho các luồng hệ điều hành của riêng nó. Ta có thể thấy tác động của việc thay đổi độ ưu tiên của luồng bằng cách thay đổi phương thức main() trong ví dụ ThreadPlayaround : ThreadStart workerStart = new ThreadStart(StartMethod); Thread workerThread = new Thread(workerStart); workerThread.Name = "Worker"; workerThread.Priority = ThreadPriority.AboveNormal; workerThread.Start(); Ở đây ta thiết lập luồng công việc ( worker) có độ ưu tiên cao hơn luồng chính . kết quả là : ThreadPlayaroundWithPriorities Interval to display results at?> 1000000 Starting thread: Main Thread Main Thread: Current Culture = en-GB Starting thread: Worker Worker: Current Culture = en-GB Main Thread: count has reached 1000000 Worker: count has reached 1000000 Worker: count has reached 2000000 Worker: count has reached 3000000 Worker: count has reached 4000000 Worker: count has reached 5000000 Worker: count has reached 6000000 Worker: count has reached 7000000 Worker: count has reached 8000000 Worker Thread Finished Main Thread: count has reached 2000000 Main Thread: count has reached 3000000 Main Thread: count has reached 4000000 Main Thread: count has reached 5000000 Main Thread: count has reached 6000000 Main Thread: count has reached 7000000 Main Thread: count has reached 8000000 Main Thread Finished Sự đồng bộ 1 khía cạnh chủ yếu khác của luồng là sự đồng bộ hay là việc truy nhập 1 biến bởi nhiều luồng vào cùng thời điểm.nếu ta không đảm bảo được sự đồng bộ thì sẽ gây ra các lỗi tinh vi . Đồng bộ là gì ? Hãy nhìn câu lệnh sau : message += ", there"; // message là 1 chuỗi chứa chữ "hello" Trông có vẻ như là 1 lệnh nhưng thực sự thì lệnh phải thi hành nhiều thao tác khi thực thi câu lệnh này. bộ nhớ sẽ cần được cấp phát để lưu trữ 1 chuỗi dài hơn, biến message sẽ cần được tham chiếu đến vùng nhớ mới, chuỗi thực sự cần được sao chép Trong tính huống 1 câu lệnh đơn có thể được phiên dịch thành nhiều lệnh của mã máy , có thể xảy ra trường hợp time slice của luồng đang xử lí các lệnh trên kết thúc. nếu điều này xảy ra , 1 luồng khác trong cùng tiến trình có thể nhận time slice và nếu việc truy nhập vào biến có liên quan đến câu lệnh trên ( ví dụ như biến message ở trên ) không được đồng bộ, thì luồng khác có thể đọc và viết vào cùng 1 biến , với ví dụ trên liệu luồng khác đó sẽ thấy giá trị mới hay cũ của biến message ? thât may mắn là C# cung vấp 1 cách thức dễ dàng để giải quyết việc đồng bộ trong việc truy nhập biến bằng từ khóa lock : lock (x) { DoSomething(); } Câu lệnh lock sẽ bao 1 đối tượng gọi là mutual exclusion lock hay mutex .trong khi mutex bao 1 biến,thì không 1 luồng nào được quyền truy nhập vào biến đó.trong đoạn mã trên khi câu lệnh hợp thực thi và nếu time slice của luồng này kết thúc và luồng kế tiếp thử truy xuất vào biến x , việc truy xuất đến biến sẽ bị từ chối. thay vào đó window đơn giản đặt luồng đó ngủ cho đến khi mutex được giải phóng. Mutex là 1 cơ chế đơn giản được dùng để điều khiển việc truy nhập vào các biến.tất cả việc điều khiển này nằm trong lớp System.Threading.Monitor . câu lệnh lock gồm 1 số phương thức gọi đến lớp này . Các vấn đề đồng bộ Việc đồng bộ các luồng là quan trọng trong các ứng dụng đa luồng . tuy nhiên có 1 số lỗi tinh vi và khó thăm dò có thể xuất hiện cụ thể là deadlock và race condition Deadlock Deadlock là 1 lỗi mà có thể xuất hiện khi hai luồng cần truy nhập vào các tài nguyên mà bị khoá lẫn nhau. giả sử 1 luồng đang chạy theo đoạn mã sau , trong đó a, b là 2 đối tượng tham chiếu mà cả hai luồng cần truy nhập : lock (a) { // do something lock (b) { // do something } } Vào cùng lúc đó 1 luồng khác đang chạy : lock (b) { // do something lock (a) { // do something } } Có thể xảy ra biến cố sau : luồng đầu tiên yêu cầu 1 lock trên a, trong khi vào cùng thời điểm đó luồng thứ hai yêu cầu lock trên b. 1 khoảng thời gian ngắn sau , luồng a gặp câu lệnh lock(b) , và ngay lập tức bước vào trạng thái ngủ, đợi cho lock trên b được giải phóng . và tương tự sau đó , luồng thứ hai gặp câu lệnh lock(a) và cũng rơi vào trạng thái ngủ chờ cho đến khi lock trên a được giải phóng . không may , lock trên a sẽ không bao giờ được giải phóng bởi vì luồng đầu tiên mà đã lock trên a đang ngủ và không thức dậy . cho đến khi lock trên b được giải phóng điều này cũng không thể xảy ra cho đến khi nào luồng thứ hai thức dậy . kết quả là deadlock. cả hai luồng đều không làm gì cả, đợi lẫn nhau để giải phóng lock . loại lỗi này làm toàn ứng dụng bị treo , ta phải dùng Task Manager để hủy nó . Deadlock có thể được tránh nếu cả hai luồn yêu cầu lock trên đối tượng theo cùng thứ tự . trong ví dụ trên nếu luồng thứ hai yêu cầu lock cùng thứ tự với luồng đầu , a đầu tiên rồi tới b thì những luồng mà lock trên a đầu sẽ hoàn thành nhiệm vụ của nó sau đó các luồng khác sẽ bắt đầu. Race condition Race condition là 1 cái gì đó tinh vi hơn deadlock .nó hiếm khi nào dừng việc thực thi của tiến trình , nhưng nó có thể dẫn đến việc dữ liệu bị lỗi .nói chung nó xuất hiện khi vài luồng cố gắng truy nhập vào cùng 1 dữ liệu và không quan tâm đến các luồng khác làm gì để hiểu ta xem ví dụ sau : Giả sử ta có 1 mảng các đối tượng, mỗi phần tử cần được xử lí bằng 1 cách nào đó , và ta có 1 số luồng giữa chúng làm tiến trình naỳ .ta có thể có 1 đối tuợng gọi là ArrayController chứa mảng đối tưọng và 1 số int chỉ định số phẩn tử được xử lí .tacó phương thức : int GetObject(int index) { // trả về đối tượng với chỉ mục được cho } Và thuộc tính read/write int ObjectsProcessed { // chỉ định bao nhiêu đối tượng được xử lí } [...]... viết lại đoạn mã trên : lock(ArrayController) { int nextIndex = ArrayController.ObjectsProcessed; } Console.WriteLine("object to be processed next is " + nextIndex); lock(ArrayController) { ++ArrayController.ObjectsProcessed; object next = ArrayController.GetObject () ; } ProcessObject(next); Ta có thể gặp 1 vấn đề nếu 1 luồng lấy 1 đối tưọng ( đối tượng thứ 11 trong mảng) và đi tới trình bày thông điệp...Bây giờ mỗi luồng mà dùng để xử lí các đối tượng có thể thi hành đoạn mã sau : lock(ArrayController) { int nextIndex = ArrayController.ObjectsProcessed; Console.WriteLine("object to be processed next is " + nextIndex); ++ArrayController.ObjectsProcessed; object next = ArrayController.GetObject () ; } ProcessObject(next); Nếu ta muốn tài nguyên không bị giữ quá lâu ,... tượng này trong khi đó luồng thứ hai cũng bắt đầu thi hành cũng đoạn mã gọi ObjectProcessed, và quyết định đối tượng xử lí kế tiếp là đối tượng thứ 11, bởi vì luồng đầu tiên vẫn chưa được cập nhật ArrayController.ObjectsProcessed trong khi luồng thứ hai đang viết đến màn hình rằng bây giờ nó sẽ xử lí đối tượng thứ 11 , luồng đầu tiên yêu cầu 1 lock khác trên ArrayController và bên trong lock này tăng... màn hình rằng bây giờ nó sẽ xử lí đối tượng thứ 11 , luồng đầu tiên yêu cầu 1 lock khác trên ArrayController và bên trong lock này tăng ObjectsProcessed không may , nó quá trễ cả hai luồng đều đang xử lí cùng 1 đối tượng và loại tình huống này ta gọi là Race Condition . C# và các lớp cơ sở Thread ( luồng ) – Phần 2 ThreadPlayaround Interval to display results at?> 1000000 Starting thread: Main Thread Main Thread: Current Culture = en-GB Main Thread: . new ThreadStart(StartMethod); Thread workerThread = new Thread( workerStart); workerThread.Name = "Worker"; workerThread.Priority = ThreadPriority.AboveNormal; workerThread.Start () ; . thấy các luồng thực sự làm việc song song. luồng chính bắt đầu và đếm đến 1 triệu. ở 1 vài thời điểm , trong khi luồng chính đang đếm thì luồng công việc ( worker thread ) bắt đầu, và từ đó 2 luồng