Chương 9: Ngôn ngữ lập trình song song
Giảng viên: Ph.D Nguyễn Văn Hòa Khoa KT-CN-MT – ðH An Giang
1
Nội dung
(cid:1) Giới thiệu (cid:1) SubProprogam-level (cid:1) Semaphores (cid:1) Chương trình giám sát (monitor) (cid:1) Truyền thông ñiệp (massage passing) (cid:1) Luồng (Java thread)
2
Giới thiệu
(cid:1) Sự tương tranh (concurrency) có thể xảy ra ở 4
mức sau: 1. Lệnh mã máy 2. Câu lệnh của NN LT cấp cao (lệnh lặp) 3. Chương trình con 4. Chương trình
(cid:1) Vì không có một NN LT nào hỗ trợ tương tranh ở
mức chương trình, và lệnh mã máy nên 2 sự tương tranh này không ñược trình bày ở chương này
3
Giới thiệu (tt)
(cid:1) ðN: Thread ñiều khiển trong một chương trình là
thứ tự các ñiểm cần ñến của CT
(cid:1) Phân loại sự tương tranh:
1. Tương tranh vật lý (physical concurrency) – Multiple processors ñộc lập (ñiều khiển với multiple threads) 2. Trương tranh logic (logical concurrency) – Sự tương tranh này xuất hiện khi có sự chia sẽ trên cùng một processor (Một phần mền có thể ñược thiết kết với multiple thread)
4
Giới thiệu (tt)
(cid:1) Tại sao phải học sự tương tranh trong NN LT
1. Rất hữu dụng cho việc thiết kế chương trình hỗ trợ
tính toán song song
2. Máy tính hỗ trợ tương tranh vật lý (multi-core
processors) rất phổ biến
5
Kiến trúc máy tính multi-core
Single instruction multiple data (SIMD)
Multiple Instruction multiple data (MIMD)
6
Tương tranh ở mức chương trình con
(cid:1) ðN: Một công việc (task) hoặc tiến trình (process) là một ñơn vị chương trình ñược thực hiện ñồng thời với những chương trình khác
(cid:1) Task khác với chương trình con như thế nào? (cid:2) Task có thể ñược bắt ñầu ở thời ñiểm tường minh (cid:2) Khi một chương trình bắt ñầu thực thi một task, thông thường thì
không bị ñình hoãn
(cid:2) Khi việc thực thi một task kết thúc thì không nhất thiết phải trả
quyền ñiều khiển cho caller
(cid:1) Các công việc (tasks) có thể trao ñổi qua lại
7
Tương tranh ở mức CT con (tt)
(cid:1) Thông thường có 2 loại tasks
(cid:2) Heavyweight tasks thực thi với không gian ñịa chỉ và
run-time stack của chính nó
(cid:2) Lightweight tasks luôn luôn thực thi với cùng không
gian ñịa chỉ và cùng run-time stack
8
Tương tranh ở mức CT con (tt)
(cid:1) ðN: một task riêng biệt (disjoint) nếu như nó
không giao tiếp hoặc ảnh hường ñến sự thực thi của một task nào ñó trong một chương trình bất kỳ
(cid:1) Một task giao tiếp với một task khác thì cần
thiết phải có sự ñồng bộ hóa (synchronization) (cid:2) Sự giao tiếp có thể bằng: 1. Chia sẽ các biến không cục bộ 2. Tham số 3. Truyền thông ñiệp
9
Tương tranh ở mức CT con (tt)
(cid:1) Các kiểu ñồng bộ hóa : 1. Hợp tác (Cooperation) (cid:2) Task A phải ñợi cho ñến khi task B hoàn thành một vài
tác vụ nhất ñịnh nào ñó trước khi task A có thể thực hiện tiếp tục → mô hình producer-consumer
2. Cạnh tranh (competition) (cid:2) Khi hai hoặc nhiều tasks cùng dùng chung một tài nguyên (resource) nhưng tài nguyên này không thể dùng ñồng thời ñược
(cid:2) Sự canh tranh thường ñược cung cấp bởi quyền truy cập
loại trừ lẫn nhau
10
Sự cần thiết của ñông bộ hóa trong cạnh tranh
11
Tương tranh ở mức CT con (tt)
(cid:1) Việc ñồng bộ hóa ñòi hỏi một cơ chế của sự trị
hoãn việc thực thi các task
(cid:1) Sự ñiều khiển việc thực thi ñược ñiều hành bởi một chương trình, gọi là scheduler, có nhiệu vụ sắp ñặt việc thực thi task vào những processors sẵn có
12
Tương tranh ở mức CT con (tt)
(cid:1) Các Task có thể ở một trong vài trạng thái sau
ñây: 1. New – mới khởi tạo nhưng chưa ñược thực hiện 2. Runnable hoặc ready – sẵn sàng ñể chạy nhưng chưa
chạy (vì không có processor sẵn có)
3. Running 4. Blocked – ñã chạy, nhưng không thể tiếp tục vì ñang
ñợi vài sự kiện nào ñó xảy ra)
5. Dead
13
Tương tranh ở mức CT con (tt)
(cid:1) Liveness là ñặc ñiểm mà một chương trình có thể
có hoặc không (cid:2) Trong code tuần tự, nghĩa là nếu một CT tiếp tục thực
thi → dẫn ñến sự cạnh tranh
(cid:2) Trong môi trường tương tranh, một task có thể dễ dàng
mất liveness của nó
(cid:2) Nếu tất cả các task trong môi trường tương tranh ñều
mất liveness của chúng, trường hợp này gọi là deadlock
14
Tương tranh ở mức CT con (tt)
(cid:1) Các NN LT hỗ trợ tương tranh ñều có 2 cơ chế: ñồng bộ hóa cạnh tranh và ñồng bộ hóa hợp tác
(cid:1) Các yếu tố khi thiết kế tương tranh:
1. Sự ñồng bộ hóa hợp tác ñược cung cấp như thế nào? 2. Sự ñồng bộ hóa tương tranh ñược cung cấp như thế
nào?
3. Cách gì và khi nào một task bắt ñầu và kết thúc thực
thi?
4. Các task có ñược sinh ra một cách tĩnh hay ñộng?
15
Tương tranh ở mức CT con (tt)
(cid:1) Các phương thức ñồng bộ hóa:
1. Semaphores 2. Chương trình giám sát (Monitors) 3. Truyền thông ñiệp (Message Passing)
16
Semaphores
(cid:1) Dijkstra - 1965 (cid:1) Một semaphore là một cấu trúc dữ liệu chứa một
counter và một hàng ñợi (queue) nhằm lưu trữ các mô tả của task
(cid:1) Các semaphores ñược dùng ñể cài ñặt các bảo vệ trong code có sự truy nhập các cấu trúc dữ liệu chia sẽ
(cid:1) Các semaphores chỉ có 2 thao tác vụ, wait và release (hay ñược gọi P và V bởi Dijkstra)
(cid:1) Các semaphores có thể ñược dùng trong cả ñồng
bộ hóa cạnh tranh và hợp tác
17
Semaphores
(cid:1) ðồng bộ hóa hợp tác với Semaphores
(cid:2) Example: Một buffer chia sẽ (cid:2) Buffer ñược cài ñặt với hai tác vụ DEPOSIT và FETCH như là hai cách thức ñể truy nhập buffer
(cid:2) Sử dụng hai semaphores cho sự hợp tác:
emptyspots và fullspots
(cid:2) Counter của hai semaphore dùng ñể lưu trữ số empty
spots và full spots trong buffer
18
Semaphores
(cid:1) Trước tiên DEPOSIT phải kiểm tra
emptyspots xem có còn khoảng trống trong buffer không
(cid:1) Nếu còn khoảng trống thì counter của emtyspots
giảm ñi một và giá trị ñược ñưa vào buffer
(cid:1) Nếu không còn khoảng trống thì chương trình gọi DEPOSIT ñược ñặt trong hàng ñợi cho ñến khi có một emtyp spot rỗng
(cid:1) Khi kết thúc DEPOSIT, giá trị của counter của
fullspots ñược tăng lên một
19
Semaphores
(cid:1) Trước tiên FETCH phải kiểm tra fullspots
xem có còn giá trị nào trong buffer không (cid:2) Nếu còn thì một giá trị ñược lấy ra và counter của
fullspots bị giảm ñi một
(cid:2) Nếu không còn giá trị nào thì tiến trình của FETCH
ñược ñặt trong hàng ñợi cho ñến khi có một giá trị xuất hiện
(cid:2) Khi kết thúc FETCH, tăng counter của emptyspots
lên một
(cid:1) Hai thao tác FETCH và DEPOSIT trên các
semaphore thành công thông qua hai thao tác wait và release
20
Semaphores
wait(aSemaphore)
if aSemaphore’s counter > 0 then Decrement aSemaphore’s counter else Put the caller in aSemaphore’s queue Attempt to transfer control to some ready task (If the task ready queue is empty, deadlock occurs)
end
21
Semaphores
release(aSemaphore)
if aSemaphore’s queue is empty {no one waiting} then Increment aSemaphore’s counter
else Put the calling task in the task ready
queue
Transfer control to a task from
aSemaphore’s queue
end
22
Producer Consumer Code
semaphore fullspots, emptyspots; fullspots.count = 0; emptyspots.count = BUFLEN; task producer;
loop -- produce VALUE –- wait (emptyspots); {wait for space} DEPOSIT(VALUE); release(fullspots); {increase
filled}
end loop; end producer;
23
Producer Consumer Code
task consumer;
loop wait (fullspots);{to make sure it is not empty} FETCH(VALUE); release(emptyspots); {increase empty space} -- consume VALUE –- end loop;
end consumer;
24
Semaphores
(cid:1) ðồng bộ hóa cạnh tranh với semaphores
(cid:2) Semaphore thứ ba, có tên là access, dùng ñể kiểm
soát truy cập (ñồng bộ hóa cạnh tranh)
(cid:1) Counter của access sẽ chỉ có hai giá trị 0 và 1 (cid:1) Tương ñương như là một semaphore nhị phân (binary
semaphore)
(cid:1) Giá trị khởi tạo của access phải là 1, ñồng nghĩa là tài nguyên ñang ở trạng thái sẵn sàng. 0 nghĩa là bận
25
Code của Producer-Consumer
semaphore access, fullspots, emptyspots; access.count = 1; fullstops.count = 0; emptyspots.count = BUFLEN; task producer;
loop -- produce VALUE –- wait(emptyspots); {wait for space} wait(access); {wait for access) DEPOSIT(VALUE); release(access); {relinquish access} release(fullspots); {increase filled space} end loop; end producer;
26
Code của Producer-Consumer
task consumer;
loop wait(fullspots);{make sure it is not empty} wait(access); {wait for access} FETCH(VALUE); release(access); {relinquish access} release(emptyspots); {increase empty space} -- consume VALUE –- end loop; end consumer;
27
Semaphores : nhận xét (cid:1) Môi trường lập trình không an toàn (Unsafe)
(cid:2) Sử dụng sai các semaphores có thể là nguyên nhân thất bại trong ñồng bộ hóa hợp tác, e.g., buffer sẽ bị tràn (overflow) nếu không có dòng code wait(emptyspots) trong producer task. Hoặc buffer sẽ bị underflow nếu không có dòng code wait(fullspots)
(cid:2) Trình biên dịch không thể kiểm tra việc dùng sai
(cid:1) Sự tin cậy
(cid:2) Sử dụng sai có thể là nguyên nhân thất bại của ñồng bộ hóa cạnh tranh, e.g., chương trình sẽ bị deadlock nếu loại bỏ dòng code release(access)
28
Chương trình giám sát (Monitors)
(cid:1) NNLT : concurrent Pascal, Modula, Mesa, tiếp
theo là C#, Ada and Java
(cid:1) Ý tưởng: bao ñống dữ liệu chia sẽ và giới hạn các
thao tác truy nhập
(cid:1) CT giám sát là một trù tường hóa dữ liệu cho
những dữ liệu chia sẽ
29
Monitor Buffer Operation
30
ðồng bộ hóa cạnh tranh
(cid:1) Dữ liệu chia sẽ ñược ñặt bên trong CT giám sát
(tốt hơn là ñặt trong các client)
(cid:1) Tất cả các truy nhập ñều diễn ra ở trong CT giám
sát (cid:2) Việc cài ñặt CT giám sát phải bảo ñảm các truy cập ñược ñồng bộ bằng cách chỉ có một truy cập tại một thời ñiểm nhất ñịnh
(cid:2) Nếu CT giám sát bận vào thời ñiểm gọi, thì các lời gọi
sẽ ñược ñặt vào trong hàng ñợi
31
ðồng bộ hóa hợp tác
(cid:1) Sự hợp tác giữa các tiến trình (processes) vẫn là
một tác vụ trong lập trình (cid:2) Lập trình viên phải bảo ñảm là không xảy ra underflow
và overflow trong một buffer chia sẽ
32
Chương Trình giám sát: nhận xét
(cid:1) Hỗ trợ tốt cho sự ñồng bộ hóa cạnh tranh (cid:1) ðối với ñồng bộ hóa hợp tác thì tương tự như semaphore nên → sẽ gặp các vấn ñề như semaphore
33
Truyền thông ñiệp
(cid:1) ðược ñưa ra bởi Hansen & Hoare vào 1978 (cid:1) Vấn ñề: làm thế nào giải quyết vấn ñề khi có nhiều
yêu cầu giao tiếp từ nhiều task với một task cho trước (cid:2) Vài dạng của cơ chế không quyết ñịnh cho sự công bằng (cid:2) Guarded commands của Dijkstra: kiểm soát truyền thông
ñiệp
(cid:1) Ý tưởng chính: giao tiếp giữa các task giống như ñến
phòng mạch (cid:2) Phần lớn thời gian BS ñợi bệnh nhân (cid:2) Hoặc bệnh nhân ñợi BS, BS sẽ khám bệnh cho bệnh nhân
nếu cả hai ñều rãnh
(cid:2) Hoặc lấy cái hẹn
34
Truyền thông ñiệp (tt)
(cid:1) Truyền thông ñiệp là mô hình tương tranh
(cid:2) Có thể là mô hình của cả semaphore và CT giám sát (cid:2) Không chỉ cho ñồng bộ hóa cạnh tranh (cid:2) Truyền thông ñiệp ñồng bộ, khi bận các task không
muốn bị gián ñoạn
35
Truyền thông ñiệp
(cid:1) Trong phạm vi tasks, chúng ta cần:
a. Một cơ chế ñể cho phép một task biểu thị khi nào nó
sẵn sàng nhận các thông ñiệp
b. Các tasks cần cách ghi nhớ các task khác ñang ñợi nó nhận message và có sự lựa chọn các message tiếp theo
(cid:1) ðN: Khi message của một task ñược nhận bởi
một task nào ñó, thì sự truyền message ñược gọi là rendezvous
36
VD về Rendezvous
37
Tương tranh trong Java: Java thread
(cid:1) Khi chương trình Java thực thi hàm main() tức là
tạo ra một luồng chính (main thread)
(cid:1) Trong luồng main
(cid:2) Có thể tạo các luồng con (cid:2) Khi luồng main ngừng thực thi, chương trình sẽ kết
thúc
(cid:1) Luồng có thể ñược tạo ra bằng 2 cách:
(cid:2) Tạo lớp dẫn xuất từ lớp Thread (cid:2) Tạo lớp hiện thực giao tiếp Runnable
38
Tương tranh trong Java (tt)
(cid:1) VD public class Mythread extends Thread{
private String data public Mythread(String data){
this.data = data;
} public void run(){
System.out.println(‘‘I am a thread!’’); System.out.println(‘‘The data is:’’,data);
}
}
39
Tương tranh trong Java (tt)
(cid:1) Tạo ra một thể hiện của lớp Thread (hoặc dẫn xuất của nó)
và gọi phương thức start()
(cid:1) Khi gọi myThread.start() một luồng mới tạo ra và chạy
phương thức run() của myThread.
40
Tương tranh trong Java: Runnable
(cid:1) Giao tiếp Runnable
(cid:2) Ngoài tạo luồng bằng cách thừa kế từ lớp Thread, cũng
có một cách khác ñể tạo luồng trong Java
(cid:2) Luồng có thể tạo bằng cách tạo lớp mới hiện thực giao
tiếp Runnable và ñịnh nghĩa phương thức: public abstract void run()
(cid:2) ðiều này ñặc biệt hữu ích nếu muốn ñể tạo ra một ñối
tượng Thread nhưng muốn sử dụng một lớp cơ sở khác Thread
41
Tương tranh trong Java: Runnable
(cid:1) VD
42
Tương tranh trong Java: Runnable
(cid:1) ðể tạo ra một luồng mới từ một ñối tượng hiện thực giao tiếp Runnable, cần phải khởi tạo một ñối tượng Thread mới với ñối tượng Runnable như ñích của nó
(cid:1) Khi gọi start() trên ñối tượng luồng sẽ tạo ra một luồng mới và phương thức run() của ñối tượng Runnable sẽ ñược thực hiện
43
Tương tranh trong Java: ñiều khiển
44
Tương tranh trong Java: ñiều khiển
(cid:1) Khi một luồng giành quyền sử dụng CPU, nó sẽ thực hiện
cho ñến khi một sự kiện sau xuất hiện: (cid:2) Phương thức run() kết thúc (cid:2) Một luồng quyền ưu tiên cao hơn (cid:2) Nó gọi phương thức sleep() hay yield() – nhượng bộ
(cid:1) Khi gọi yield(), luồng ñưa cho các luồng khác với cùng quyền ưu tiên cơ hội sử dụng CPU. Nếu không có luồng nào khác cùng quyền ưu tiên tồn tại, luồng tiếp tục thực hiện
(cid:1) Khi gọi sleep(), luồng ngủ trong một số mili-giây xác
ñịnh, trong thời gian ñó bất kỳ luồng nào khác có thể sử dụng CPU
45
Tương tranh trong Java: ñiều khiển
(cid:1) Phương thức join()
(cid:2) Khi một luồng (A) gọi phương thức join() của một
luồng nào ñó (B), luồng hiện hành (A) sẽ bị khóa chờ (blocked) cho ñến khi luồng ñó kết thúc (B).
46
ðồng bộ hóa cạnh tranh với java
(cid:1) Dùng từ khóa synchronized trước tên các hàm khi ñịnh nghĩa ñể cấm các lớp khác thực thi các hàm này khi nó ñang thực thi
class ManageBuf{
private int [100] buf; … public synchonized void deposit(int item){…} public synchonized void fetch(int item){…} …
}
47
ðồng bộ hóa hợp tác với java
(cid:1) Các phương thức wait(), notify() và notifyAll()
ñược sử dụng ñể thóa khóa trên một ñối tượng và thông báo các luồng ñang ñợi chúng có thể có lại ñiều khiển
(cid:1) wait() ñược gọi trong vòng lập (cid:1) notify() thông báo cho thread ñang chờ ñợi là sự
kiện ñang ñợi ñã xãy ra
(cid:1) notifyall() ñánh thức các thread ñang ñợi là có thể
thực thi sau lệnh wait()
48
ðồng bộ hóa hợp tác với java: VD
49
ðồng bộ hóa hợp tác với java: VD
50
SimpleThread
class SimpleThread extends Thread { public SimpleThread(String str) {
super(str);
} public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i + " " + getName()); try {
sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {}
} System.out.println("DONE! " + getName());
}
}
51
TwoThreads
class TwoThreadsTest {
public static void main (String[] args) { new SimpleThread("Jamaica").start(); new SimpleThread("Fiji").start();
}
}
52

