Rendezvous
Tác vụ viết ở trên có phần đặc tả rỗng, nó không cho phép các tác vụ tương tác với nhau. Phân chia chương trình thành nhiều tác vụ cũng giống như phân công nhiệm vụ trong một cơ quan – các tác vụ càng độc lập với nhau càng tốt nhưng sự tương tác giữa hai và nhiều tác vụ là một điều hiển nhiên không thể tránh. Ada giải quyết vấn đề tương tác bằng một cơ cấu thật ngoạn mục. Nó dựa trên quan niệm là hai tác vụ muốn trao đổi dữ liệu với nhau thì cả hai phải cùng đồng ý, phải làm thế nào đó thỏa thuận được với nhau – nói cách khác chúng phải đồng bộ với nhau. Theo quan niệm này thì
trao đổi dữ liệu và đồng bộ hóa là không thể tách rời. Cơ cấu này trong Ada được gọi là
điểm hẹn (rendezvous) (15). Để tiến tới điểm hẹn, một trong hai tác vụ (
server) sẽ “bày hàng” và “chờ đợi” tác vụ kia (
client) gọi tới mình. Ta sẽ sửa đổi mã nguồn trên để chúng làm một phép tính gì đó hữu ích, chẳng hạn như là sắp xếp mảng. Tác vụ server cần phải cung cấp hai dịch vụ: tiếp nhận dữ liệu và giao trả kết quả.
Code: task type QuickSort_Task is
entry Input (A : in Table);
entry Output (A : out Table);
end QuickSort_Task;
task body QuickSort_Task is
Buffer : Table;
begin
accept Input (A : in Table) do
Buffer := A;
end Input;
QuickSort(Buffer);
accept Output(A : out Table) do
A := Buffer;
end Output;
end QuickSort_Task;
Và client (trong trường hợp này là chương trình chính) gọi server đại khái như sau.
Code: declare
T1, T2 : QuickSort_Task;
A, B : Table;
begin
A :=...;
B :=...;
T1.Input(A);
T2.Input(B);
...
T1.Output(A);
T2.Output(B);
end;
Vậy là trong phần đặc tả tác vụ có thêm hai câu khai báo bắt đầu bằng từ khóa
entry. Qua đó tác vụ cho biết khả năng tương tác với môi trường xung quanh. Tương ứng trong thân tác vụ xuất hiện hai lệnh ký hiệu bởi từ khóa
accept, hai lệnh này thực hiện tương tác đó. Mỗi
entry trong đặc tả tương ứng với đúng một lệnh
accept trong thân (16). Trong chương trình chính ta tạo ra hai tác vụ (T1 và T2) kiểu QuickSort_Task, tiếp nhận dữ liệu từ luồng chính, thực hiện tính toán rồi giao trả kết quả.
Việc thi hành chương trình diễn ra như sau. Trước hết hệ điều hành khởi động luồng chính. Bộ run-time của Ada tạo ra hai tác vụ T1 và T2, nên khi bắt đầu vào điểm
begin của chương trình chính ta có cả thảy 3 luồng. Tác vụ T1 và T2 tiến đến lệnh
accept Input và dừng lại ở đó chờ, chờ đến khi có một luồng gọi tới
entry Input. Song song với chúng, luồng chính chạy tới dòng T1.Input(A). Vì T1 đang chờ sẵn tại
accept Input, việc nhận dữ liệu diễn ra. Sau đó T1 chạy tiếp một mạch tới
accept Output, lại dừng và chờ tiếp. Tương tự như vậy T2 tiếp nhận dữ liệu B. Việc trả kết quả cũng hoàn toàn tương tự. Trong bất cứ trường hợp nào, nếu luồng chính đến điểm hẹn trước, nó sẽ dừng lại và chờ đối tác của mình để giao (nhận) dữ liệu. Tóm lại là luôn có một ai đó đến điểm hẹn trước và phải dừng lại một lúc chờ đối tác.
Trong kết cấu lệnh
accept như trên, server không thể rời bỏ điểm hẹn mà phải chờ client đến bằng được mới thôi. Thời gian chờ như vậy là không giới hạn. Nhưng Ada còn có kết cấu cho phép diễn tả một "người tình" sốt ruột, chỉ chờ một khoảng thời gian nhất định, nếu đối tác không đến thì thôi. Hơn nữa kết cấu này còn cho phép người tình chờ đợi cùng một lúc tại nhiều điểm hẹn khác nhau
. Thí dụ sau đây trình diễn cả hai khả năng đó.
Code: task type Teller is
entry Deposit (A : in Money);
entry Withdraw (A : in Money);
entry Balance (B : out Money);
end Teller;
task body Teller is
Current_Balance : Money;
begin
select
accept Deposit (A : in Money) do
Current_Balance := Current_Balance + A;
end Deposit;
or
accept Withdraw(A : in Money) do
Current_Balance := Current_Balance - A;
end Withdraw;
or
accept Balance(B : out Money) do
B := Current_Balance;
end Balance;
or
delay 10.0; -- chờ đợi 10 giây
end select;
end Hello_Task;
Tác vụ này mô phỏng một nhân viên quầy thu ngân, cung cấp 3 dịch vụ là gửi tiền (Deposit), rút tiền (Withdraw) và xem số dư tài khoản (Balance). Khi khởi động, tác vụ sẽ chờ đồng thời tại 3 điểm hẹn Deposit, Withdraw và Balance xem có ai gọi thì sẽ thực hiện dịch vụ tương ứng. Nếu sau 10 giây không có lời gọi, tác vụ kết thúc. Tất cả đều là nhờ vào kết cấu
select. Thực hiện một cơ cấu tương tự bằng một đối tượng mức thấp như cờ hiệu (semaphore) chắc chắn là không khó, nhưng cần phải suy nghĩ nhiều hơn và mã nguồn sẽ không rõ ràng bằng. Việc này trong lập trình song song thường có thể dẫn đến những tình huống lỗi rất khó chịu, chỉ phát sinh trong những điều kiện nhất định hầu như không thể tái tạo được.
_______________________
NOTES
(15) Rendezvous: cuộc hẹn hò gặp gỡ.
(16) Tổng quát, mỗi
entry có ít nhất một lệnh
accept tương ứng.