Tải bản đầy đủ (.pdf) (12 trang)

Tài liệu C# và Các Lớp Đối Tượng part 7 pdf

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (188.97 KB, 12 trang )

Thread ( luồng )
1 thread là 1 chuỗi liên tiếp những sự thực thi trong chương trình. trong 1 chương trình
C# ,việc thực thi bắt đầu bằng phương thức main() và tiếp tục cho đến khi kết thúc hàm
main().
Cấu trúc này rất hay cho những chương trình có 1 chuỗi xác định những nhiệm vụ liên
tiếp . nhưng thường thì 1 chương trình cần làm nhiều cơng việc hơn vào cùng một lúc.ví
dụ trong internet explorer khi ta đang tải 1 trang web thì ta nhấn nút back hay 1 link nào
đó , để làm việc này Internet Explorer sẽ phải làm ít nhất là 3 việc :
- Lấy dữ liệu đưọc trả về từ internet cùng với các tập tin đi kèm .
- Thể hiện trang web
- Xem người dùng có nhập để làm thứ gì khác khơng
Để đơn giản vấn đề ta giả sử Internet Ecplorer chỉ làm 2 cơng việc :
- Trình bày trang web


- Xem người dùng có nhập gì khơng
Để thực hành việc này ta sẽ viết 1 phương thức dùng để lấy và thể hiện trang web .giả sử
rằng việc trình bày trang web mất nhiều thời gian ( do phải thi hành các đoạn javascript
hay các hiệu ứng nào đó .. ) .vì thế sau một khoảng thời gian ngắn khoảng 1/12 giây ,
phương thức sẽ kiểm tra xem người dùng có nhập gì khơng .nếu có thì nó sẽ đuơc xử lí,
nếu khơng thì việc trình bày trang sẽ được tiếp tục. và sau 1/12 giây việc kiểm tra sẽ được
lặp lại.tuy nhiên viết phương thức này thì rất phức tạp do đó ta sẽ dùng kiến trúc event
trong window nghĩa là khi việc nhập xảy ra hệ thống sẽ thông báo cho ứng dụng bằng
cách đưa ra một event . ta sẽ cập nhật phương thức để cho phép dùng các event :
Ta sẽ viết 1 bộ xử lí event để đáp ứng đối với việc nhập của người dùng.
Ta sẽ viết 1 phương thức để lấy và trình bày dữ liệu . phương thức này được thực thi khi
ta không làm bất cứ điều gì khác.

Ta hãy xem cách phương thức lấy và trình bày trang web làm việc : đầu tiên nó sẽ tự định
thời gian. trong khi nó đang chạy, máy tính khơng thể đáp ứng việc nhập của người dùng
. do đó nó phải chú ý đến việc định thời gian để gọi phương thức kiểm tra việc nhập của
người dùng ,nghĩa là phương thức vừa chạy vừa quan sát thời gian.bên cạnh đó nó cịn
phải quan tâm đến việc lưu trữ trạng thái trước khi nó gọi phương thức khác để sau khi
phương thức khác thực hiện xong nó sẽ trả về đúng chỗ nó đã dừng.Vào thời window 3.1
đây thực sự là những gì phải làm để xử lí tình huống này. tuy nhiên ở NT3.1 và sau đó là
windows 95 đã có việc xử lí đa luồng điều này làm việc giải quyết vấn đề tiện lợi hơn.
Các ứng dụng với đa luồng
Trong ví dụ trên minh hoạ tình huống 1 ứng dụng cần làm nhiều hơn 1 cơng việc.vì vậy
giải pháp rõ ràng là cho ứng dụng thực thi nhiều luồng. như ta đã nói ,1luồng đaị diện cho
1 chuỗi liên tiếp các lệnh mà máy tính thực thi . do đó khơng có lí do nào 1 ứng dụng lại

chỉ có 1 luồng.thực vậy ta có thể có nhiều luồng nếu ta muốn. tất cả điều cần là mỗi lần ta
tạo 1 luồng thực thi mới, ta chỉ định 1 phương thức mà thực thi nên bắt đầu với. luồng
đầu tiên trong ứng dụng ln được thực thi trong main() vì đây là phương thức mà .NET
runtime luôn lấy để bắt đầu.các luồng sau sẽ được bắt đầu bên trong ứng dụng.


Công việc này làm như thế nào ?
- Một bộ xử lí chỉ có thể làm một việc vào một lúc,nếu có một hệ thống đa xử lí , theo lí
thuyết có thể có nhiều hơn một lệnh được thi hành đồng bộ, mỗi lệnh trên một bộ xử lí,
tuy nhiên ta chỉ làm việc trên một bộ xử lí do đó các cơng việc khơng thể xảy ra cùng
lúc.thực sự thì hệ điều hành window làm điều này bằng một thủ tục gọi là pre emptive
multitasking

- Thủ tục này nghĩa là window lấy 1 luồng vào trong vài tiến trình và cho phép luồng đó
chạy 1 khoảng thời gian ngắn .gọi là time slice. khi thời gian này kế thúc. window lấy
quyền điều khiển lại và lấy 1 luồng khác và lại cấp 1 khoảng thời gian time slice . vì
khoảng thời gian này q ngắn nên ta có cảm tưởng như mọi thứ đều xảy ra cùng lúc.
- Thậm chí khi ứng dụng ta chỉ có một luồng thì thủ tục này vẫn đưọc dùng vì có thể có
nhiều tiến trình khác đang chạy trên hệ thống mỗi tiến trình cần các time slice cho mỗi
luồng của nó.vì vậy khi ta có nhiều cửa sổ trên màn hình mỗi cái đại diện cho một tiến
trình khác nhau, ta vẫn có thể nhấn vào bất kì của sổ nào
Và nó đáp ứng ngay lập tức.thực sự việc đáp ứng nay khơng phải ngay lập tức- nó xảy ra
vào sau khoảng thời gian time slice của luồng đương thời.vì thời gia này quá ngắn nên ta
cảm thấy như nó đáp ứng ngay lập tức.
Thao tác luồng

Luồng được thao tác bằng cách dùng lớp Thread nằm trong namespace System.Threading
. 1 thể hiện của luồng đaị diện cho 1 luồng.ta có thể tạo các luồng khác bằng cách khởi
tạo 1 đối tượng luồng
Bắt đầu 1 luồng
Giả sử rằng ta đang viết 1 trình biên tập hình ảnh đồ hoạ, và người dùng yêu cầu thay đổi
độ sâu của màu trong ảnh. ta bắt đầu khởi tạo 1 đối tượng luồng như sau :
// entryPoint được khai báo trước là 1 delegate kiểu ThreadStart
Thread depthChangeThread = new Thread(entryPoint);
Đoạn mã trên biểu diễn 1 hàm dựng của Thread 1 thông số chỉ định điểm nhập của 1
luồng. - đó là phương thức nơi luồng bắt đầu thi hành.trong tình huống này ta dùng thơng
số là delegate, 1 delegate đã được định nghĩa trong System.Threading gọi là aThreadStart
, chữ kí của nó như sau :

public delegate void ThreadStart();
Thông số ta truyền cho hàm dựng phải là 1 delegate kiểu này .
Ta bắt đầu luồng bằng cách gọi phương thức Thread.Start() , giả sử rằng ta có phương
thức ChangeColorDepth() :
void ChangeColorDepth()
{
// xử lí để thay đổi màu
}
Sắp xếp lại ta có đoạn mã sau :
ThreadStart entryPoint = new ThreadStart(ChangeColorDepth);
Thread depthChangeThread = new Thread(entryPoint);
depthChangeThread.Name = "Depth Change Thread";



depthChangeThread.Start();
Sau điểm này ,cả hai luồng sẽ chạy đồng bộ
Trong mã này ta đăng kí tên cho luồng bằng cách dùng thuộc tính Thread.Name. khơng
cần thiết làm điều này nhưng nó có thể hữu ích :

Lưu ý rằng bởi vì điểm đột nhập luồng ( trong ví dụ này là ChangeColorDepth() ) khơng
thể lấy bất kì thơng số nào.ta sẽ phải tìm 1 cách nào đó để truyền thơng số cho phương
thức nếu cần.cách tốt nhất là dùng các trường thành viên của lớp mà phương thức này là
thành viên.cũng vậy phương thức không thể trả về bất cứ thứ gì .
Mỗi lần ta bắt đầu 1 luồng khác ,ta cũng có thể đình chỉ, hồi phục hay bỏ qua nó.đình chỉ

nghĩa là cho luồng đó ngủ ( sleep) - nghĩa là không chạy trong 1 khoảng thời gian. sau đó
nó thể đưọc phục hồi ,nghĩa là trả nó về thời diểm mà nó bị định chỉ. nếu luồng đưọc bỏ ,
nó dừng chạy. window sẽ huỷ tất cả dữ liệu mà liên hệ đến luồng đó, để luồng khơng thể
được bắt đầu lại.
tiếp tục ví dụ trên , ta giả sử vì lí do nào đó luồng giao diện người dùng trình bày 1 hộp
thoại cho người dùng cơ hội để đình chỉ tạm thời sự đổi tiến trình . ta sẽ soạn mã đáp ứng
trong luồng main :
depthChangeThread.Suspend();
Và nếu người dùng được yêu cầu cho tiến trình được phục hồi :
depthChangeThread.Resume();
Cuối cùng nếu người dùng muốn huỷ luồng :
depthChangeThread.Abort();

Phương thức Suspend() có thể khơng làm cho luồng bị định chỉ tức thời mà có thể là sau
1 vài lệnh, điều này là để luồng được đình chỉ an tồn. đối với phương thức Abort() nó
làm việc bằng cách ném ra biệt lệ ThreadAbortException. ThreadAbortException là 1 lớp
biệt lệ đặc biệt mà khơng bao giờ được xử lí.nếu luồng đó thực thi mã bên trong khối try,
bất kì khối finally sẽ được thực thi trước khi luồng bị huỷ.
Sau khi huỷ luồng ta có thể muốn đợi cho đến khi luồng thực sự bị huỷ trước khi tiếp tục
luồng khác ta có thể đợi bằng cách dùng phương thức join() :
depthChangeThread.Abort();
depthChangeThread.Join();
Join() cũng có 1 số overload khác chỉ định thời gian đợi . nếu hết thời gian này việc thi
hành sẽ được tiếp tục.
nếu một luồng chính muốn thi hành 1 vài hành động trên nó , nó cần 1 tham chiếu đến

đối tượng luồng mà đại diện cho luồng riêng. nó có thể lấy 1 tham chiếu sử dụng thuộc
tính static, CurrentThread ,của lớp Thread :


Thread myOwnThread = Thread.CurrentThread;
Có hai cách khác nhau mà ta có thể thao tác lớp Thread:
- Ta có thể khởi tạo 1 đối tượng luồng , mà sẽ đại diện cho luồng đang chạy và các
thành viên thể hiện của nó áp dụng đến luồng đang chạy
- Ta có thể gọi 1 số phương thức static . những phương thức này sẽ áp dụng đến luồng
mà ta thực sự đang gọi phương thức từ nó.
1 phương thức static mà ta muốn gọi là Sleep(), đơn giản đặt luồng đang chạy ngủ 1
khoảng thời gian, sau đó nó sẽ tiếp tục.

Ví dụ ThreadPlayaround
Phần chính của ví dụ này là 1 phương thức ngắn , DisplayNumber() , phương thức này
cũng bắt đầu bằng việc trình bày tên và bản điạ ( culture) của luồng mà phương thức đang
chạy :
static void DisplayNumbers()
{
Thread thisThread = Thread.CurrentThread;
string name = thisThread.Name;
Console.WriteLine("Starting thread: " + name);
Console.WriteLine(name + ": Current Culture = " +
thisThread.CurrentCulture);
for (int i=1 ; i<= 8*interval ; i++)

{
if (i%interval == 0)
Console.WriteLine(name + ": count has reached " + i);
}
}
Việc trình bày số tuỳ thuộc vào interval,là 1 trường mà giá trị được gõ bởi người dùng
.nếu người dùng gõ 100 sẽ trình bày đến 800, trình bày giá trị
100,200,300,400,500,600,700,800. nếu ngươì dùng gõ 1000 thì sẽ trình bày đến 8000,
trình bày giá trị 1000,2000,3000,4000,5000,6000,7000,8000 .
Những gì ThreadPlayaround() làm là bắt đầu luồng cơng việc thứ hai,mà chạy
DisplayNumber(), nhưng ngay sau khi bắt đầu luồng công việc, luồng chính bắt đầu thi
hành cùng phương thức. nghĩa là ta thấy cả hai việc đếm xảy ra cùng lúc.

Phương thức main() cho ThreadPlayaround và lớp chứa đựng của nó là :
class EntryPoint
{
static int interval;
static void Main()
{
Console.Write("Interval to display results at?> ");
interval = int.Parse(Console.ReadLine());


Thread thisThread = Thread.CurrentThread;
thisThread.Name = "Main Thread";

ThreadStart workerStart = new ThreadStart(StartMethod);
Thread workerThread = new Thread(workerStart);
workerThread.Name = "Worker";
workerThread.Start();
DisplayNumbers();
Console.WriteLine("Main Thread Finished");
Console.ReadLine();
}
Trong phương thức main() đầu tiên ta hỏi người dùng interval, sau đó ta lấy 1 tham chiếu
đến đối tượng luồng mà đại diện cho luồng chính .
Kế tiếp ta tạo ra luồng công việc , đặt cùng tên , và bắt đầu nó ,truyền cho nó 1 delegate
mà chỉ định phương thức mà nó phải bắt đầu trong đó , phương thức workerStart. cuối

cùng , ta gọi phương thức DisplayNumber() để bắt đầu đếm. điểm đột nhập của luồng này
là :
static void StartMethod()
{
DisplayNumbers();
Console.WriteLine("Worker Thread Finished");
}
Lưu ý rằng tất cả các phương thức này đều là phương thức static trong cùng 1
lớp,EntryPoint. biến i trong phương thức DisplayNumber() được dùng để đếm là 1 biến
cục bộ. biến này khơng chỉ có phạm vi đối với phương thức mà được định nghĩa mà còn
khả kiến đối với luồng đang thực thi phương thức đó. nếu 1 luồng khác bắt đầu thi hành
cùng 1 phương thức , thì luồng đó sẽ lấy 1 bản sao chép riêng của biến điạ phương . ta sẽ

bắt đầu bằng việc chạy đoạn mã cho giá trị interval là 100 :
ThreadPlayaround
Interval to display results at?> 100
Starting thread: Main Thread
Main Thread: Current Culture = en-GB
Main Thread: count has reached 100
Main Thread: count has reached 200
Main Thread: count has reached 300
Main Thread: count has reached 400
Main Thread: count has reached 500
Main Thread: count has reached 600



Main Thread: count has reached 700
Main Thread: count has reached 800
Main Thread Finished
Starting thread: Worker
Worker: Current Culture = en-GB
Worker: count has reached 100
Worker: count has reached 200
Worker: count has reached 300
Worker: count has reached 400
Worker: count has reached 500
Worker: count has reached 600

Worker: count has reached 700
Worker: count has reached 800
Worker Thread Finished
Qua kết quả ta thấy luồng chính bắt đầu đếm đến 800 và sau đó hồn thành.sau đó luồng
worker bắt đầu và chạy riêng rẻ.
vấn đề ở đây là việc bắt đầu 1 luồng là 1 tiến trình lớn .sau khi khởi tạo 1 luồng mới
,luồng chính gặp dịng mã :
workerThread.Start();
Cái này sẽ gọi đến Thread.Call() thơng báo cho window rằng luồng mới được bắt đầu,
sau đó trả về ngay lập tức. torng khi ta đang đếm đến 800, thì window đang bận rộn trong
việc sắp xếp cho các luồng được bắt đầu .vào lúc luồn mới thực sự bắt đầu thì luồng
chính vừa mới hồn thành cơng việc của nó.

Ta có thể giải quyết vấn đề này bằng cách chọn interval lớn hơn ,để cả hai luồng chạy lâu
hơn trong phương thức DisplayNumbes() , ta thử cho interval là 1 triệu :
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 hồ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 tồ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ẽ hồ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í
}
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ữ q lâu , ta có thể khơng giữ lock trên
ArrayController trong khi ta đang trình bày thơng điệp người dùng . do đó ta 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 nói về việc xử lí đối 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 ObjectsProcessed. không may , nó q 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
Code for Download:
ThreadPlayaround
ThreadPlayaroundWithPriorities



 



×