JOURNAL OF SCIENCE OF HNUE DOI: 10.18173/2354-1075.2015-0055<br />
Educational Sci., 2015, Vol. 60, No. 7A, pp. 80-93<br />
This paper is available online at http://stdb.hnue.edu.vn<br />
<br />
<br />
<br />
<br />
HỆ THỐNG KIỂU TÍNH CẬN TRÊN SỐ LOG<br />
CHO NGÔN NGỮ GIAO DỊCH ĐA LUỒNG TỐI GIẢN<br />
<br />
Trương Anh Hoàng1 , Nguyễn Ngọc Khải2<br />
1 Trường Đại học Công nghệ - Đại học Quốc Gia Hà Nội<br />
2 Trường Đại học Tài nguyên và Môi trường Hà Nội<br />
<br />
Tóm tắt. Trong bài báo này, chúng tôi đưa ra một hệ thống kiểu để ước lượng cận trên tài<br />
nguyên sử dụng của các chương trình đa luồng và sử dụng bộ nhớ giao dịch. Tài nguyên<br />
được đơn giản hóa là số vùng bộ nhớ giao dịch được tạo ra. Hệ thống kiểu được xây dựng<br />
cho một ngôn ngữ lõi với các lệnh cơ bản nhất liên quan đến việc tạo luồng và giao dịch.<br />
Việc sử dụng ngôn ngữ lõi này giúp việc kiểm tra tính đúng đắn và chính xác của việc ước<br />
lượng được rõ ràng hơn đồng thời cũng cho phép mở rộng ra các ngôn ngữ sử dụng bộ nhớ<br />
giao dịch khác được thuận lợi.<br />
Từ khóa: Hệ thống kiểu, biên tài nguyên, bộ nhớ giao dịch, đa luồng, ngôn ngữ lập trình.<br />
<br />
1. Mở đầu<br />
Bộ nhớ giao dịch phần mềm [12] là một cấu trúc lập trình nhằm thay thế cho cơ chế đồng<br />
bộ bộ nhớ dùng chung dựa trên khóa. Một trong những cơ chế giao dịch mạnh cho phép các giao<br />
dịch đan xen việc lồng nhau và việc tạo luồng mới trong giao dịch đang mở được mô tả trong [9].<br />
Theo cơ chế này, một giao dịch được gọi là lồng trong giao dịch khác nếu nó bắt đầu và kết thúc<br />
trong giao dịch khác; giao dịch đầu gọi là giao dịch cha, giao dịch sau gọi là giao dịch con. Khi<br />
lồng nhau, các giao dịch con phải kết thúc (commit) trước giao dịch cha của chúng được kết thúc.<br />
Một giao dịch được gọi là đa luồng khi người lập trình có thể tạo ra luồng mới ngay bên trong giao<br />
dịch khi nó chưa kết thúc. Các luồng được tạo ra này chạy song song với luồng đang có giao dịch<br />
chưa kết thúc. Để cho phép truy cập đồng thời đến các biến dùng chung, luồng mới sẽ tạo bản sao<br />
của các biến thuộc các giao dịch đang mở. Khi luồng cha kết thúc một giao dịch, tất cả luồng con<br />
của nó đã được tạo ra bên trong giao dịch đó phải cùng kết thúc với luồng cha của chúng. Chúng<br />
tôi gọi loại kết thúc này là đồng kết thúc (joint commit) và thời điểm kết thúc này là điểm đồng kết<br />
thúc. Các đồng kết thúc này là một dạng đồng bộ ẩn của các luồng chạy song song với nhau.<br />
Để cài đặt cơ chế bộ nhớ giao dịch, mỗi giao dịch có một vùng bộ nhớ cục bộ riêng biệt<br />
cho từng luồng, được gọi là log, để lưu trữ các biến dùng chung (chia sẻ) để các luồng truy cập<br />
độc lập các bản sao của biến dùng chung trong quá trình thực hiện. Mỗi luồng có thể tạo một số<br />
giao dịch lồng nhau nên sẽ có tương ứng một số lượng log được tạo ra cho mỗi giao dịch. Hơn<br />
nữa, một luồng con khi tạo ra trong một giao dịch cũng sẽ có các bản sao như cha của nó. Khi<br />
đồng kết thúc, các log được so sánh với nhau và nếu không có mâu thuẫn trong việc cập nhật các<br />
Ngày nhận bài: 6/8/2015. Ngày nhận đăng: 15/11/2015.<br />
Liên hệ: Trương Anh Hoàng, e-mail: truonganhhoang@gmail.com<br />
<br />
<br />
<br />
80<br />
Hệ thống kiểu tính cận trên số log cho ngôn ngữ giao dịch đa luồng tối giản<br />
<br />
<br />
biến thì giao dịch được hoàn tất. Ngược lại nếu có mâu thuẫn trong việc cập nhật các biến của các<br />
luồng tham gia vào quá trình đồng kết thúc thì giao dịch có thể được thử lại (retry), hoặc hủy bỏ<br />
(rollback). Tại các điểm đồng kết thúc, các log được giải phóng, tức là tài nguyên bộ nhớ tương<br />
ứng được giải phóng.<br />
Cơ chế bộ nhớ giao dịch này tuy có ưu điểm là tạo thuận lợi cho người lập trình nhưng nó<br />
cũng gây một số vấn đề không tầm thường như: việc tạo bản sao của các log khi một luồng mới<br />
được tạo ra là ngầm, hay việc đồng bộ các trạng thái kết thúc với các trạng thái khác cũng là ngầm.<br />
Ngầm ở đây được hiểu là người lập trình không chủ động viết lệnh tạo các log hay lệnh đồng bộ.<br />
Các hoạt động ngầm này làm việc ước lượng tài nguyên sử dụng của các chương trình trong các<br />
trường hợp này trở nên khó khăn. Hơn nữa, việc tạo bản sao của các log cũng làm ảnh hưởng đến<br />
khả năng cùng kết thúc của các luồng đang hoạt động song song, và việc tạo ra nhiều log có thể<br />
ảnh hưởng đến sự an toàn của chương trình khi tài nguyên bộ nhớ của các máy đều là hữu hạn.<br />
Vì vậy, việc ước lượng cận trên số log cùng tồn tại trong một chương trình bộ nhớ giao dịch<br />
đa luồng, lồng nhau có một vai trò quan trọng để đánh giá tính hiệu quả của chương trình.<br />
Trong nghiên cứu trước đây [10, 14] chúng tôi đã đưa ra hệ thống kiểu và hiệu ứng để ước<br />
lượng cận trên số log cùng tồn tại trong một thời điểm. Tuy nhiên, hệ thống kiểu và hiệu ứng trong<br />
nghiên cứu này còn phức tạp và biên tìm được chưa sắc - tức là ước lượng cận trên cao hơn thực tế<br />
có thể xảy ra. Trong nghiên cứu này, chúng tôi sẽ đưa ra một hệ thống kiểu mới đơn giản hơn cho<br />
một ngôn ngữ cũng được đơn giản hóa nhưng vẫn có đầy đủ các lệnh cơ bản để tạo giao dịch và<br />
luồng con, và suy luận biên chính xác hơn. Các đóng góp chính trong bài báo này gồm:<br />
• Đưa ra một ngôn ngữ lõi chỉ gồm các lệnh liên quan đến giao dịch và đa luồng.<br />
• Trình bày hệ thống kiểu có khả năng xác định cận trên của số log chương trình có thể tạo ra.<br />
• Nêu tính chất và chứng minh tính đúng đắn của hệ thống kiểu chúng tôi đề xuất.<br />
Ước lượng tài nguyên sử dụng đã được nghiên cứu trong nhiều ngữ cảnh khác nhau. Tuy<br />
nhiên, hầu hết các nghiên cứu đều hạn chế trong các ngôn ngữ lập trình tuần tự hoặc hàm. Trong<br />
nghiên cứu này, chúng tôi tập trung xác định số lượng log tối đa của các chương trình đa luồng,<br />
các cấu trúc đồng bộ phức tạp và ngầm định với các mô hình giao dịch có kế thừa.<br />
Hughes và Pareto [8] đã giới thiệu một cấu trúc dữ liệu đệ quy và động để xác định biên<br />
tài nguyên sử dụng. Tofte và Talpin [13] sử dụng hệ thống suy luận để mô tả việc quản lí bộ nhớ<br />
cho các chương trình thực hiện cấp phát và giải phóng bộ nhớ động. Hofmann và Jost [6] tính toán<br />
biên trên không gian heap cho một ngôn ngữ hàm đầu tiên. Wei-Ngan Chin [5] xác minh bộ nhớ<br />
sử dụng cho các chương trình hướng đối tượng. Những người lập trình được yêu cầu chú thích về<br />
bộ nhớ sử dụng và kích thước liên quan cho các phương thức cũng như việc giải phóng bộ nhớ rõ<br />
ràng. Trong [7], Hofmann và Jost sử dụng hệ thống kiểu để tính toán biên không gian heap như<br />
một hàm của đầu vào cho một ngôn ngữ hướng đối tượng. Trong [4] các tác giả tính toán cận trên<br />
tài nguyên sử dụng của một phương thức sử dụng hàm phi tuyến với các tham số của phương thức.<br />
Các biên ở đây chưa được chính xác và nghiên cứu của họ cũng không dựa trên kiểu. Braberman<br />
và cộng sự [2] tính toán sấp xỉ hình thức phi tuyến của biên bộ nhớ cho những chương trình Java<br />
liên quan đến cả cấu trúc dữ liệu và vòng lặp. Trong [3] các tác giả đề xuất hệ thống kiểu cho các<br />
ngôn ngữ thành phần với các thành phần chạy song song nhưng các luồng chạy độc lập. Trong [1],<br />
Albert và các cộng sự tính toán kích thước bộ nhớ (heap) của một chương trình là một hàm của<br />
biến đầu vào. Trong [11] đề xuất một thuật toán nhanh để xác định tĩnh cận trên của bộ nhớ heap<br />
của một lớp chương trình JavaCard.<br />
Trong các phân tích của chúng tôi sẽ tập trung vào ngôn ngữ cho phép linh hoạt việc tạo<br />
luồng con và linh hoạt trong việc mở, đóng và kéo theo sự đồng bộ ngầm giữa các luồng khi kết<br />
thúc giao dịch. Ngôn ngữ và hệ thống kiểu của chúng tôi được tối giản sẽ là nền tảng tốt cho các<br />
nghiên cứu sau này về tài nguyên bộ nhớ của các ngôn ngữ sử dụng bộ nhớ giao dịch.<br />
<br />
<br />
<br />
81<br />
Trương Anh Hoàng, Nguyễn Ngọc Khải<br />
<br />
<br />
2. Nội dung nghiên cứu<br />
2.1. Ví dụ minh họa<br />
Để minh họa bài toán và hướng giải quyết chúng tôi sử dụng Đoạn mã 1 dưới đây làm ví dụ<br />
minh họa. Trong đoạn chương trình này, cặp lệnh onacid và commit để bắt đầu và kết thúc một<br />
giao dịch và chúng phải đi đôi với nhau. Các biểu thức e1, e2, e3 và e4 đại diện cho các đoạn<br />
các chương trình con. Lệnh spawn tạo một luồng mới.<br />
<br />
Đoạn mã 1: Một chương trình lồng nhau và đa luồng.<br />
1 onacid;//thread 0<br />
2 onacid;<br />
3 spawn(e1;commit;commit);//thread 1<br />
4 onacid;<br />
5 spawn(e2;commit;commit;commit);//thread 2<br />
6 commit;<br />
7 e3;<br />
8 commit;<br />
9 e4;<br />
10 commit<br />
<br />
<br />
<br />
<br />
Hình 1. Các luồng song song, lồng nhau và cùng kết thúc<br />
<br />
Ngữ nghĩa của chương trình này được minh họa trong Hình 1. Lệnh onacid và lệnh<br />
commit được kí hiệu bằng [ và ] tương ứng trong hình. Lệnh spawn tạo ra một luồng mới chạy<br />
song song với luồng cha của nó và được mô tả bằng các đường nằm ngang. Luồng mới sẽ tạo bản<br />
sao của các log của luồng cha để luồng mới truy cập các biến này một cách độc lập, tránh xung<br />
đột với các luồng khác. Trong ví dụ này, khi tạo ra e1 luồng chính đã mở được hai giao dịch, vì<br />
vậy luồng 1 thực thi e1 trong hai giao dịch này và phải thực hiện hai lệnh commit để đóng chúng.<br />
Các luồng song song của một giao dịch sẽ cùng kết thúc tại một thời điểm được mô tả bằng các<br />
hình chữ nhật có đường kẻ chấm chấm. Các cạnh bên phải của hình chữ nhật thể hiện thời điểm<br />
đồng bộ này.<br />
Giả sử e1 là một giao dịch đơn không có giao dịch con, e2 là một giao dịch với hai giao<br />
dịch con lồng nhau (e2=onacid; onacid; commit; commit), e3 là một giao dịch với ba<br />
giao dịch con bên trong lồng nhau, và e4 có bốn giao dịch bên trong lồng nhau. Có thể tính tay ra<br />
số log tối đa của chương trình là ngay sau khi tạo ra e2. Tại thời điểm đó, e1 đóng góp 3 giao dịch<br />
(2 từ luồng chính, và 1 của chính nó), e2 đóng góp 5 giao dịch (3 từ luồng chính, và 2 của chính<br />
nó), luồng chính đóng góp 3 giao dịch. Tổng sẽ là 3 + 5 + 3 = 11 giao dịch. Trong thời điểm thực<br />
<br />
<br />
82<br />
Hệ thống kiểu tính cận trên số log cho ngôn ngữ giao dịch đa luồng tối giản<br />
<br />
<br />
hiện e3 và e4, số giao dịch được mở ít hơn bởi vì nhiều giao dịch đã đóng trước đó. Một điểm khó<br />
khăn của việc phân tích đó là phải nắm bắt được những điểm đồng bộ ngầm ở thời điểm biên dịch.<br />
Ví dụ trên sử dụng một ngôn ngữ lõi với các lệnh cơ bản nhất liên quan đến việc tạo luồng<br />
và giao dịch, và chúng tôi gọi là TM (transactional multi-threaded language). Trong nghiên cứu<br />
trước đây của chúng tôi, việc chia tách chương trình thành các thành phần con và sau đó xây dựng<br />
các kiểu cho các thành phần con này được thực hiện một cách khá tự do. Điều này dẫn đến một<br />
hệ thống kiểu phức tạp vì sau đó chúng ta phải xử lí khá nhiều khả năng kết hợp của các thành<br />
phần này.<br />
Trong nghiên cứu này, để hạn chế việc kết hợp tùy ý các thành phần, chúng tôi sẽ xác định<br />
kiểu cho luồng trong cùng trước, sau đó sẽ kết hợp với các luồng anh chị (cha) của nó, và quá trình<br />
này được lặp lại đến khi chỉ còn môt luồng chính. Trong ví dụ này, chúng ta xác định kiểu cho biểu<br />
thức trong dòng 5 trước tiên, sau đó kết hợp nó với phần từ dòng 6 tới dòng 10. Tiếp theo dòng 4<br />
sẽ được xác định kiểu với phần từ dòng 5 tới dòng 10. Bây giờ phần từ dòng 4 tới dòng 10 sẽ kết<br />
hợp với luồng con trong dòng 3. Sau đó dòng 2 sẽ được xác định kiểu với dòng 3 tới 10, và sau<br />
cùng chúng ta có thể xác định kiểu toàn bộ chương trình từ dòng 1 tới dòng 10. Bằng cách này,<br />
chúng ta có một ước lượng số log tối đa chính xác hơn.<br />
2.2. Ngôn ngữ TM<br />
TM là một ngôn ngữ mệnh lệnh hỗ trợ cấu trúc giao dịch và đa luồng. Đây là những đặc<br />
điểm cần thiết của các mô hình tương tranh và các hệ thống giao dịch lồng nhau.<br />
<br />
P ::= 0 | p(e) | P k P<br />
e ::= e1 ; e2 | e1 + e2 |<br />
spawn(e) | onacid | commit<br />
Hình 2. Cú pháp của TM<br />
<br />
2.2.1. Cú pháp<br />
Cú pháp của TM được thể hiện trong Hình 2. Dòng thứ nhất để biểu diễn các luồng/tiến<br />
trình. Dòng thứ 2 xác định các lệnh cơ bản tạo thành chương trình. e có thể là e1 nối tiếp bởi e2<br />
hoặc nhận một trong hai giá trị e1 hoặc e2 . Dòng sau cùng là ba lệnh để tạo một luồng, bắt đầu<br />
và kết thúc một giao dịch. Hai lệnh cuối này chính là các lệnh sử dụng (cấp phát) và giải phóng<br />
tài nguyên.<br />
2.2.2. Ngữ nghĩa động<br />
Ngữ nghĩa của TM được thể hiện bởi hai tập quy tắc hoạt động: tập quy tắc cho ngữ nghĩa<br />
mệnh lệnh (Bảng 1) và tập quy tắc cho ngữ nghĩa giao dịch và đa luồng (Bảng 2). Hiểu một cách<br />
tổng quát, môi trường lúc chạy (toàn cục) là một tập hợp các luồng. Mỗi luồng có một môi trường<br />
cục bộ là một chuỗi các log. Để quản lí các luồng và log này chúng ta gán cho mỗi luồng và mỗi<br />
log một định danh. Phần tiếp theo chúng tôi sẽ định nghĩa một cách hình thức về môi trường cục<br />
bộ và môi trường toàn cục.<br />
Ngữ nghĩa mệnh lệnh<br />
Ngữ nghĩa mệnh lệnh là các quy tắc chuẩn tương tự như các ngôn ngữ thông dụng khác. Ở<br />
đây, chúng tôi chỉ trình bày những điểm chính liên quan đến việc tạo và hủy các log của các giao<br />
dịch và các luồng.<br />
Định nghĩa 2.1 (Môi trường cục bộ). Một môi trường cục bộ E là một dãy hữu hạn các log được<br />
gắn nhãn l1 :log1 ; . . . ; lk :logk , trong đó phần tử thứ i của dãy bao gồm một nhãn li (một tên giao<br />
<br />
83<br />
Trương Anh Hoàng, Nguyễn Ngọc Khải<br />
<br />
<br />
dịch) và log logi .<br />
<br />
Cho E = l1 :log1 ; . . . ; lk :logk , ta gọi k là kích cỡ của E, và được kí hiệu là |E|. Kích cỡ |E|<br />
là độ sâu của các giao dịch lồng nhau (l1 là giao dịch ngoài cùng, lk là giao dịch trong cùng). Môi<br />
trường rỗng/trống kí hiệu là ǫ.<br />
<br />
E, (e1 + e2 ); e → E, (e1 ; e) L-COND1<br />
E, (e1 + e2 ); e → E, (e2 ; e) L-COND2<br />
Bảng 1. Ngữ nghĩa mệnh lệnh<br />
<br />
Ngữ nghĩa ở mức cục bộ được biểu diễn dạng E, e → E ′ , e′ . Trong đó E, E ′ là các môi<br />
trường cục bộ, e, e′ là các biểu thức được đánh giá trong các môi trường đó. Hai quy tắc L-COND1<br />
và L-COND2 được hiểu như việc lựa chọn thực thi một trong hai biểu thức e1 hoặc e2 tùy thuộc vào<br />
giá trị các biểu thức điều kiện.<br />
Ngữ nghĩa giao dịch và đa luồng<br />
Các quy tắc cho các ngữ nghĩa được thể hiện dưới dạng Γ, P ⇒ Γ′ , P ′ hoặc Γ, P ⇒ error ,<br />
trong đó Γ là một môi trường toàn cục và P là một tập tiến trình có dạng p(e). Một môi trường<br />
toàn cục bao gồm các môi trường cục bộ của các luồng và được định nghĩa như sau:<br />
Định nghĩa 2.2 (Môi trường toàn cục). Một môi trường toàn cục Γ là một ánh xạ xác định từ định<br />
danh của luồng tới môi trường cục bộ của nó, Γ = p1 :E1 , . . . , pk :Ek , trong đó pi là một định danh<br />
của luồng và Ei là môi trường cục bộ của luồng pi .<br />
<br />
E, e → E ′ , e′ p : E ∈ Γ ref lect(p, E ′ , Γ) = Γ′<br />
G-PLAIN<br />
Γ, P k p(e) ⇒ Γ′ , P k p(e′ )<br />
p′ f resh spawn(p, p′ , Γ) = Γ′<br />
G-SPAWN<br />
Γ, P k p(spawn(e1 ); e2 ) ⇒ Γ′ , P k p(e2 ) k p′ (e1 )<br />
l f resh start(l, p, Γ) = Γ′<br />
G-TRANS<br />
Γ, P k p(onacid; e) ⇒ Γ′ , P k p(e)<br />
p : E ∈ Γ E = ..; l : log; intranse(Γ, l) = p¯ = p1 ..pk commit(¯ ¯ Γ) = Γ′<br />
p, E,<br />
G-COMM<br />
Γ, P k k1 pi (commit; ei ) ⇒ Γ′ , P k ( k1 pi (ei ))<br />
` `<br />
Γ = Γ′′ ; p : E |E| = 0<br />
G-ERROR<br />
Γ, P k p(commit; e) ⇒ error<br />
Bảng 2. Ngữ nghĩa giao dịch và luồng<br />
<br />
Để mô tả ngữ nghĩa của ngôn ngữ, ta cần thêm một số hàm phụ trợ sau:<br />
• Hàm ref lect cập nhật thay đổi từ môi trường cục bộ lên môi trường toàn cục: Nếu<br />
ref lect(p, E ′ , Γ) = Γ′ và Γ = p1 : E1 , ..., pk : Ek thì Γ′ = p1 : E1′ ..., pk : Ek′ với<br />
|Ei | = |Ei′ | (với mọi i).<br />
• Hàm spawn sinh ra một luồng mới p′ từ luồng cha p: Giả sử Γ = p : E, Γ′′ và p′ ∈ / Γ và<br />
spawn(p, p′ , Γ) = Γ′ , thì Γ′ = Γ, p′ : E ′ với |E| = |E ′ |.<br />
• Hàm start(l, pi , Γ) tạo ra một giao dịch mới nhãn l từ luồng pi : Nếu start(l, pi , Γ) = Γ′<br />
với Γ = p1 : E1 , ..., pi : Ei , ..., pk : Ek và với một lệnh f resh l, thì Γ′ = p1 : E1 , ..., pi :<br />
Ei′ , ..., pk : Ek , với |Ei′ | = |Ei | + 1.<br />
<br />
<br />
84<br />
Hệ thống kiểu tính cận trên số log cho ngôn ngữ giao dịch đa luồng tối giản<br />
<br />
<br />
• Hàm intranse(Γ, l) trả lại một tập các luồng trong giao dịch l: Giả sử Γ = Γ′′ , p : E với<br />
E = E ′ , l : ρ và intranse(Γ, l) = p¯, thì:<br />
- p ∈ p¯ và<br />
- với mọi pi ∈ p¯ ta có Γ = ..., pi : (Ei′ , l : ρi ), ...<br />
- với mọi luồng p′ với p′ ∈/ p¯ và Γ = ..., p′ : (E ′ , l′ : ρ′ ), ..., ta có l′ 6= l.<br />
• Hàm commit kết thúc một giao dịch: nếu commit(¯ ¯ Γ) = Γ′ với Γ = Γ′′ , p : (E, l : ρ)<br />
p, E,<br />
′ ′<br />
và p¯ = intranse(Γ, l) thì Γ = ..., pj : Ej , ..., pi : E;i , ... trong đó pi ∈ p¯, pj ∈ / p¯, pj :<br />
Ej ∈ Γ, với |Ej′ | = |Ej | và |Ei′ | = |Ei | − 1.<br />
Chú ý là các hàm spawn tạo ra nhiều nhãn giống nhau ở các luồng khác nhau và đây là dấu<br />
vết để chúng cùng được đồng kết thúc ở quy tắc G-SPAWN. Các quy tắc trong Bảng 2 có nghĩa như<br />
sau:<br />
• Quy tắc G-PLAIN để cập nhật các thay đổi ở mức cục bộ lên mức toàn cục. Giả sử p : E ∈ Γ,<br />
khi luồng p tạo ra biến đổi E, e → E ′ , e′ trên môi trường cục bộ E của nó. Hàm ref lect sẽ<br />
cập nhật những biến đổi này lên môi trường toàn cục, chuyển từ môi trường Γ thành Γ′ .<br />
• Quy tắc G-SPAWN dùng cho trường hợp tạo luồng mới với lệnh spawn. Lệnh spawn(e1 ) tạo<br />
ra luồng mới p′ thực thi e1 song song với luồng cha p, biến đổi môi trường từ Γ sang Γ′ .<br />
• Quy tắc G-TRANS dùng trong trường hợp luồng p tạo một giao dịch mới với lệnh onacid. Giao<br />
dịch mới với nhãn l được sinh ra, môi trường được biến đổi từ Γ sang Γ′ .<br />
• Quy tắc G-COMM để thực hiện việc kết thúc các giao dịch. Giao dịch hiện tại của luồng p là<br />
l. Tất cả các luồng trong giao dịch l phải cùng kết thúc (commit) khi giao dịch l kết thúc.<br />
• Quy tắc G-ERROR dùng trong trường hợp việc kết thúc một giao dịch không thành công. Trong<br />
trường hợp luồng p thực hiện việc kết thúc giao dịch bên ngoài môi trường của nó nghĩa là<br />
|E| = 0 thì sẽ trả lại kết quả lỗi (error).<br />
<br />
2.3. Hệ thống kiểu<br />
Mục đích của hệ thống kiểu của chúng ta là để xác định số log tối đa có thể được tạo ra của<br />
một chương trình TM. Ở đây chúng tôi đề xuất kiểu của một đoạn chương trình là một dãy số có<br />
dấu. Các dãy số có dấu này là trừu tượng của các hành vi giao dịch của đoạn chương trình.<br />
2.3.1. Kiểu<br />
Để mổ tả hành vi giao dịch của một đoạn chương trình, chúng tôi sử dụng một tập bốn kí<br />
hiệu {+, −, ¬, ♯}. Kí hiệu + và − lần lượt mô tả cho sự bắt đầu và kết thúc một giao dịch. Kí hiệu<br />
¬ miêu tả các điểm đồng kết thúc của các giao dịch trong các luồng song song và ♯ miêu tả số log<br />
tối đa được tạo ra. Để tiện lợi hơn cho việc tính toán về sau trên các dãy này, chúng ta gán một kí<br />
hiệu với một số tự nhiên không âm n ∈ N+ = {0, 1, 2, ..} thành dạng số có dấu. Vì vậy, các kiểu<br />
này sử dụng dãy hữu hạn của tập số có dấu T N = { +n , −n , ♯n , ¬ n | n ∈ N+ }. Chúng tôi sẽ cố<br />
gắng để đưa ra các quy tắc để miêu tả một thành phần (biểu thức) của TM bằng một dãy số có dấu.<br />
Một đoạn chương trình có kiểu, +n ( −n ) được hiểu là có liên tiếp n onacid (commit) và<br />
với n thì được hiểu là trong thành phần này có n luồng cần phải đồng bộ với một onacid nào đó<br />
¬<br />
<br />
để hoàn tất một giao dịch, và với kí hiệu ♯n thì được hiểu là thành phần này tạo ra tối đa n log.<br />
Trong quá trình tính toán, phần tử có giá trị 0 có thể xuất hiện nhưng nó không ảnh hưởng đến ngữ<br />
nghĩa của chuỗi nên chúng ta sẽ tự động bỏ chúng. Để đơn giản khi trình bày, chúng ta cũng được<br />
chèn phần tử ♯0 khi cần thiết.<br />
Chúng ta dùng s cho các phần tử thuộc T N, kí hiệu T N ¯ là tập hợp của tất cả các chuỗi của<br />
các số có dấu, dùng S cho các phần tử thuộc T N ¯ và m, n, l, .. thuộc N. Các chuỗi rỗng được kí<br />
hiệu là ǫ. Đối với một chuỗi S chúng ta kí hiệu độ dài là |S|, và kí hiệu S(i) cho phần tử thứ i của<br />
<br />
85<br />
Trương Anh Hoàng, Nguyễn Ngọc Khải<br />
<br />
<br />
S. Đối với một số có dấu s, chúng ta kí hiệu tag(s) là tag của s, và |s| là số tự nhiên tương ứng<br />
¯ chúng ta kí hiệu tag(S) cho chuỗi của các<br />
của s (ví dụ s = tag(s) |s|). Đối với một chuỗi S ∈ T N,<br />
tag của các phần tử của S, ví dụ, tag(s1 . . . sk ) = tag(s1 ) . . . tag(sk ) và kí hiệu {S} cho tập các<br />
kí hiệu xuất hiện trong S. Để đơn giản chúng ta cũng kí hiệu tag(s) ∈ S thay cho tag(s) ∈ {S}.<br />
¯ có thể được phân chia thành các lớp tương đương. Các phần tử trong cùng lớp tương<br />
Tập T N<br />
đương sẽ cùng biểu diễn một hành vi giao dịch. Trong mỗi lớp này chúng ta sẽ sử dụng chuỗi rút<br />
gọn nhất để đại diện cho lớp đó và gọi nó là chuỗi chính tắc.<br />
Định nghĩa 2.3 (Chuỗi chính tắc). Một chuỗi S là chuỗi chính tắc nếu tag(S) không chứa chuỗi<br />
con ‘−−’, ‘♯♯’, ‘++’, ‘+−’, ‘+♯−’, ‘+¬’ hoặc ‘+♯¬’ và |S(i)| > 0 với mọi i.<br />
<br />
Ở đây chúng ta thường có thể đơn giản/rút gọn chuỗi S mà không làm thay ngữ nghĩa của<br />
nó. Hàm seq dưới đây sẽ rút gọn một chuỗi S thành một chuỗi chính tắc. Trong định nghĩa này<br />
mẫu +− không xuất hiện bên trái, nhưng chúng ta có thể chèn ♯0 để áp dụng. Hai mẫu ‘+¬’ và<br />
‘+♯¬’ sẽ được xử lí bởi hàm jc ở phần sau.<br />
Định nghĩa 2.4 (Đơn giản hóa). Hàm seq được định nghĩa đệ quy như sau:<br />
seq(S) = S khi S là chính tắc<br />
seq(S ♯m ♯n S ′ ) = seq(S ♯max(m, n) S ′ )<br />
seq(S +m +n S ′ ) = seq(S +(m + n) S ′ )<br />
seq(S −m −n S ′ ) = seq(S −(m + n) S ′ )<br />
seq(S +m ♯l −n S ′ ) = seq(S +(m − 1) ♯(l + 1) −(n − 1) S ′ )<br />
<br />
Như minh họa trong Hình 1, các luồng được đồng bộ bằng các đồng kết thúc (hình chữ nhật<br />
nét đứt). Vì vậy các đồng kết thúc này tách một luồng thành các đoạn và chỉ một số đoạn có thể<br />
chạy song song với nhau. Trong ví dụ trên e1 có thể chạy song song với e2 và e3, nhưng không<br />
chạy song song được với e4.<br />
Với kiểu của chúng tôi đưa ra cho biểu thức e, các đoạn có thể được xác định bằng việc xem<br />
xét kiểu của biểu thức e bên trong spawn(e) xem có các thành phần − hoặc ¬ không. Ví dụ, trong<br />
spawn(e1 ); e2 , nếu chuỗi chính tắc của e1 có − hoặc ¬, thì một số đoạn của e1 phải đồng bộ với<br />
đoạn của e2 . Hàm merge trong Định nghĩa 2.6 được sử dụng trong tình huống này, nhưng để định<br />
nghĩa nó chúng ta cần một vài hàm phụ.<br />
Với S ∈ T N ¯ và sig ∈ {+, −, ¬, ♯}, chúng ta định nghĩa hàm phụ f irst(S, sig) trả về chỉ<br />
số i nhỏ nhất có dấu là sig, tức là tag(S(i)) = sig. Nếu không có phần tử như vậy, hàm sẽ trả<br />
về 0.<br />
Một lệnh kết thúc giao dịch có thể là một kết thúc thường (cục bộ) hoặc một đồng kết thúc.<br />
Ban đầu, chúng ta giả sử tất cả các lệnh kết thúc đều là kết thúc thường. Sau đó nếu chúng ta thấy<br />
không có lệnh bắt đầu giao dịch (onacid) để khớp với lệnh kết thúc cục bộ thì chúng ta đổi kết<br />
thúc này thành một đồng kết thúc. Hàm sau đây thực hiện công việc đó và nó chuyển một chuỗi<br />
chính tắc (không có phần tử +) thành một chuỗi đồng bộ.<br />
Định nghĩa 2.5 (Chuyển dạng). S = s1 . . . sk là một chuỗi chính tắc, giả sử i = f irst(S, −).<br />
Hàm join(S) sẽ thay thế − trong S bằng ¬ như sau:<br />
join(S) = S nếu i = 0<br />
¬<br />
join(S) = s1 ..si−1 1 join( −(|si | − 1) si+1 ..sk ) nếu i 6= 0<br />
<br />
Bởi vì hàm join là lũy đẳng, định nghĩa này là hợp lệ. Chú ý là ở đây dạng chính tắc S chỉ<br />
chứa các phần tử ♯ xen kẽ với các phần tử − hoặc ¬. Chuỗi đồng bộ được sử dụng cho các biểu<br />
<br />
86<br />
Hệ thống kiểu tính cận trên số log cho ngôn ngữ giao dịch đa luồng tối giản<br />
<br />
<br />
thức trong spawn hoặc một phần của luồng chính. Các chuỗi đồng bộ này được hợp với nhau theo<br />
định nghĩa sau.<br />
Định nghĩa 2.6 (Hợp). S1 và S2 là các chuỗi đồng bộ với số lượng các phần tử ¬ trong S1 và S2<br />
là như nhau. Hàm merge được định nghĩa đệ quy như sau:<br />
merge( ♯m1 , ♯m2 ) = ♯(m1 + m2 )<br />
merge( ♯m1 ¬ n1 S1′ , ♯m2 ¬ n2 S2′ ) = ♯(m1 + m2 ) ¬ (n1 + n2 ) merge(S1′ , S2′ )<br />
<br />
Định nghĩa là hợp lệ vì S1 , S2 là các chuỗi đồng bộ nên chúng chỉ có các phần tử ♯ và ¬.<br />
Hơn nữa, số lượng 6= là như nhau trong giả thiết của định nghĩa. Vì vậy, chúng ta có thể chèn ♯0<br />
để tạo ra hai chuỗi phù hợp với định nghĩa.<br />
Đối với các lệnh điều kiện e1 + e2 , chúng ta cần hành vi giao dịch của chúng với bên ngoài<br />
phải tương tự nhau. Tức là, khi loại bỏ tất cả các phần tử ♯ của chúng thì hai chuỗi còn lại phải<br />
giống hệt nhau. Giả sử S1 và S2 là hai chuỗi như vậy thì chúng ta có thể có Si = ♯mi ∗ n Si′ ,<br />
i = 1, 2, ∗ = {+, −, ¬}, trong đó S1′ và S2′ có các giao dịch tương tự. Với S1 và S2 này, chúng ta<br />
định nghĩa phép toán chọn như sau:<br />
Định nghĩa 2.7 (Chọn). S1 và S2 là hai chuỗi mà nếu loại bỏ tất cả các phần tử ♯ của chúng thì<br />
hai chuỗi là giống hệt nhau. Hàm alt được định nghĩa đệ quy như sau:<br />
alt( ♯m1 , ♯m2 ) = ♯max(m1 , m2 )<br />
alt( ♯m1 ∗ n S1′ , ♯m2 ∗ n S2′ ) = ♯max(m1 , m2 ) ∗ n alt(S1′ , S2′ )<br />
<br />
2.3.2. Các quy tắc kiểu<br />
<br />
<br />
<br />
T-ONACID T-COMMIT<br />
−1 ⊢ onacid : +1 1 ⊢ commit : −1<br />
<br />
n⊢e:S n 1 ⊢ e 1 : S1 n2 ⊢ e2 : S2 S = seq(S1 S2 )<br />
T-SPAWN T-SEQ<br />
n ⊢ spawn(e) : join(S)ρ n1 + n2 ⊢ e 1 ; e 2 : S<br />
<br />
n1 ⊢ e1 : S1 n2 ⊢ e2 : S2ρ S = jc(S1 , S2 )<br />
T-JC<br />
n1 + n2 ⊢ e 1 ; e 2 : S<br />
<br />
n ⊢ e1 : S1ρ n ⊢ e2 : S2ρ S = merge(S1 , S2 )<br />
T-MERGE<br />
n ⊢ e1 ; e2 : S ρ<br />
kind(Ti )<br />
n⊢e:S n ⊢ ei : T i i = 1, 2 kind(T1 ) = kind(T2 ) Ti = Si<br />
T-PREP T-COND<br />
n ⊢ e : join(S)ρ n ⊢ e1 + e2 : alt(S1 , S2 )kind(S1 )<br />
<br />
Bảng 3. Quy tắc kiểu<br />
<br />
Cú pháp ngôn ngữ kiểu T được định nghĩa như sau:<br />
T = S | Sρ<br />
S ρ được sử dụng cho biểu thức spawn để đánh dấu chúng cần được đồng bộ với luồng cha của<br />
chúng. Chúng ta định nghĩa hàm kind(T ) trả về trống cho kiểu thông thường S hoặc ρ nếu T có<br />
dạng S ρ .<br />
<br />
87<br />
Trương Anh Hoàng, Nguyễn Ngọc Khải<br />
<br />
<br />
Môi trường kiểu cung cấp thông tin ngữ cảnh cho biểu thức đang được tính kiểu. Các phát<br />
biểu về kiểu có dạng sau:<br />
n⊢e:T<br />
Trong đó n ∈ N là môi trường kiểu. Khi n là dương thì nó thể hiện số giao dịch đang mở mà e sẽ<br />
đóng bởi kết thúc thường hoặc đồng kết thúc trong e.<br />
Các quy tắc kiểu cho các phép tính của chúng tôi được mô tả trong Bảng 3. Ở đây chúng ta<br />
ngầm hiểu là qui tắc được áp dụng khi các hàm tương ứng seq, jc, merge, alt là có thể thực hiện<br />
được. Tức là các đối số của chúng đáp ứng các điều kiện của hàm. Quy tắc T-SPAWN chuyển đổi<br />
S thành chuỗi đồng bộ và tạo ra kiểu mới bởi ρ để có thể hợp các luồng chạy song song với nó<br />
bằng qui tắc T-MERGE. Quy tắc T-PREP cho phép chúng ta tạo một kiểu phù hợp cho e2 để áp dụng<br />
T-MERGE. Các quy tắc còn lại thì khá tự nhiên theo ngữ nghĩa của chương trình, ngoại trừ quy tắc<br />
T-JC có hàm jc. Hàm này sẽ được giải thích trước khi chúng ta định nghĩa hình thức nó ở Định nghĩa<br />
2.8.<br />
Trong quy tắc T-JC, e2 có thể có một vài đoạn, và l (trong Định nghĩa 2.8) là số các luồng<br />
cùng kết thúc. Phần tử + cuối cùng trong S1 , là +n , sẽ được khớp với phần tử ¬ đầu tiên trong<br />
S2 , là ¬ l . Nhưng sau +n , có thể có một phần tử ♯, gọi là ♯n′ , và số lượng log cục bộ tối đa cho<br />
+n ♯n′ là n + n′ (nhưng chúng ta sẽ định nghĩa từng bước một vì vậy trong định nghĩa sau đây của<br />
<br />
jc chúng ta chỉ thêm 1 vào n′ tại một thời điểm). Tương tự, trước ¬ l có thể có một ♯l′ , vì vậy số<br />
lượng log tối đa ở điểm này là ít nhất l + l′ . Sau khi gỡ bỏ một + từ S1 và một ¬ từ S2 chúng ta<br />
rút gọn chuỗi mới để có thể áp dụng đệ quy tiếp jc. Do đó chúng ta có định nghĩa hình thức cho jc<br />
như sau.<br />
Định nghĩa 2.8 (Đồng kết thúc). Hàm jc được định nghĩa đệ quy như sau:<br />
jc(S1′ +n ♯n′ , ♯l′ ¬ l S2′ ) = jc(seq(S1′ +(n − 1) ♯(n′ + 1) ), seq( ♯(l′ + l) S2′ )) if l > 0<br />
jc( ♯n′ , ♯l′ ¬ l S2′ ) = seq( ♯max(n′ , l′ ) ¬ l S2′ ) otherwise<br />
<br />
Vì kiểu của chúng ta thể hiện các hành vi của một đoạn chương trình nên một chương trình<br />
có kiểu hợp lệ sẽ có kiểu là một phần tử đơn ♯n và n là số log tối đa cùng tồn tại khi chương trình<br />
chạy.<br />
Định nghĩa 2.9 (Kiểu hợp lệ). Một biểu thức e có kiểu hợp lệ nếu tồn tại một kiểu dẫn xuất cho e<br />
sao cho 0 ⊢ e : ♯n với n nào đó.<br />
<br />
Một phát biểu kiểu có một đặc điểm đó là môi trường của nó chỉ đủ để các giao dịch đang<br />
mở (đồng) kết thúc trong e như mô tả bởi T .<br />
Định lý 2.1 (Tính chất của một phát biểu kiểu). Nếu n ⊢ e : T và n ≥ 0 thì sim( +n , T ) = ♯m và<br />
m ≥ n trong đó sim(T1 , T2 ) = seq(jc(S1 , S2 )) với Si là Ti được bỏ ρ.<br />
<br />
Chứng minh. Bằng phương pháp quy nạp dựa trên các quy tắc xác định kiểu được mô tả trong<br />
Bảng 3.<br />
• Trường hợp T-ONACID không áp dụng bởi vì n < 0.<br />
• Trường hợp T-COMMIT là tầm thường vì sejc( +1 −1 ) = ♯1 vì m = 1 = n.<br />
• Đối với T-SEQ, theo giả thuyết quy nạp (IH) chúng ta có seq(jc( +ni , Si )) = seq( +ni Si ) =<br />
♯m với i = 1, 2 bởi vì S không có các phần tử ¬. Cần phải chứng minh rằng<br />
i i<br />
<br />
<br />
<br />
<br />
88<br />
Hệ thống kiểu tính cận trên số log cho ngôn ngữ giao dịch đa luồng tối giản<br />
<br />
<br />
sim( +(n1 + n2 ) , S) = ♯m và m ≥ m1 + m2 . Ta có:<br />
sim( +(n1 + n2 ) , S) = seq(jc( +(n1 + n2 ) , seq(S1 S2 )))<br />
= seq( +n2 +n1 S1 S2 ) ¬∈<br />
/ S1 S2<br />
= seq( +n2 ( +n1 S1 )S2 ) Định nghĩa 2.4<br />
+ ♯<br />
= seq( n2 m1 S2 )) IH<br />
= ♯(m1 + m2 ) IH, Định nghĩa 2.4<br />
• For T-JC, theo giả thuyết quy nạp ta có seq( +n1 S1 ) = ♯m1 and seq(jc( +n2 , S2 )) = ♯m2 .<br />
Tương tự như trường hợp trước, ta có:<br />
sim( +(n1 + n2 )) , S) = seq(jc( +(n1 + n2 )) , jc(S1 , S2 ))) S = jc(S1 , S2 )<br />
= seq(jc( +(n1 + n2 ) S1 , S2 ))) jc<br />
= seq(jc( +n2 ♯m1 , S2 ))) IH<br />
♯<br />
= max(n2 + m1 , m2 ) jc, IH<br />
≥ ♯max(n2 + n1 ) m1 ≥ n1 theo IH<br />
• Đối với T-MERGE, theo giả thuyết quy nạp, ta có: seq(jc( +n1 , S1 ) = ♯m<br />
1 và<br />
seq(jc( +n2 S2 ) = ♯m2 . Tương tự như trường hợp trước, ta có:<br />
seq(jc( +n , S)) = seq(jc( +n , merge(S1 , S2 ))) S = merge(S1 , S2 )<br />
+ +<br />
= seq(jc( n , S1 )) + seq(jc( n , S2 )) tính chất của S1 , S2<br />
♯<br />
= (m1 + m2 ) IH<br />
• Đối với các quy tắc còn lại, bổ đề đúng theo các giả thuyết quy nạp.<br />
<br />
Xác định kiểu cho chương trình ví dụ<br />
Bây giờ ta sẽ thử tạo một kiểu dẫn xuất cho chương trình trong Đoạn mã 1. Kí hiệu elm cho<br />
phần của chương trình từ dòng l tới dòng m. Đầu tiên, sử dụng T-SEQ, T-ONACID, T-COMMIT ta có các<br />
biểu thức bên trong spawn ở dòng 5 có kiểu là ♯2 −1 −1 −1 . Sau đó, áp dụng quy tắc T-SPAWN, ta<br />
có:<br />
3 ⊢ e55 : ( ♯2 ¬ 1 ¬ 1 ¬ 1 )ρ<br />
Bây giờ, ta có thể sử dụng T-MERGE và cần một biểu thức như vậy mà kiểu của nó phù hợp với<br />
♯2 ¬ 1 ¬ 1 ¬ 1 . Ta tìm được e6 thỏa mãn điều kiện này bởi vì kiểu của nó được suy diễn sử dụng<br />
10<br />
các quy tắc T-SEQ, T-ONACID, T-COMMIT như:<br />
3 ⊢ e610 : −1 ♯3 −1 ♯4 −1<br />
Bằng cách áp dụng T-PREP, ta có một kiểu phù hợp với (2.3.2.). Vì vậy, ta có thể áp dụng với<br />
T-MERGE và có:<br />
3 ⊢ e510 : ( ♯2 ¬ 2 ♯3 ¬ 2 ♯4 ¬ 2 )ρ<br />
Bây giờ có thể áp dụng T-JC để có kiểu cho e410 :<br />
2 ⊢ e410 : ♯4 ¬ 2 ♯4 ¬ 2<br />
vì jc( +1 , ♯2 ¬ 2 ♯3 ¬ 2 ♯4 ¬ 2 ) = seq( ♯4 ♯3 ¬ 2 ♯4 ¬ 2 ) = ♯4 ¬ 2 ♯4 ¬ 2 .<br />
Bằng cách tương tự, ta có thể tính kiểu cho e310 :<br />
2 ⊢ e310 : ♯5 ¬ 3 ♯4 ¬ 3<br />
<br />
89<br />
Trương Anh Hoàng, Nguyễn Ngọc Khải<br />
<br />
<br />
Áp dụng T-JC với −1 ⊢ e12 : ♯2 ta có:<br />
0 ⊢ e110 : ♯11<br />
Chương trình có kiểu phù hợp và số lượng log tối đa cùng tồn tại trong suốt quá trình chương<br />
trình chạy là 11.<br />
Tính chính xác hơn của hệ thống kiểu<br />
Hệ thống kiểu của chúng tôi sắc hơn hệ thống kiểu trong các nghiên cứu trước đây [14]<br />
qua ví dụ chương trình onacid; onacid; commit; spawn(commit); commit;. Biểu thức<br />
trong dòng 4-5 có kiểu ( ¬ 2 )ρ . Dòng 1-3 có kiểu +1 ♯1 . Áp dụng quy tắc T-JC ta có jc( +1 ♯1 , ¬ 2 ) =<br />
♯max(1 + 1, 0 + 2) = ♯2 . Hệ thống kiểu trước đây của chúng tôi sẽ trả lại biên là ♯3 , chúng không<br />
<br />
chính xác bằng biên ♯2 khi tính bằng hệ thống kiểu trong bài báo này.<br />
<br />
Đoạn mã 2: Chương trình ví dụ chứng minh tính sắc của biên.<br />
1 onacid;<br />
2 onacid;<br />
3 commit;<br />
4 spawn(commit);<br />
5 commit<br />
<br />
<br />
2.4. Tính đúng đắn<br />
Để chứng minh tính đúng của hệ thống kiểu, ta cần chứng minh một chương trình có kiểu<br />
hợp lệ không có số log (tại bất kì thời điểm nào trong quá trình thực hiện của chương trình) lớn<br />
hơn số thể hiện trong kiểu của nó. Ta gọi chương trình có kiểu hợp lệ là e và kiểu của nó là ♯n . Ta<br />
cần chứng minh khi thực thi e theo ngữ nghĩa trong Phần 2.2., số log trong môi trường toàn cục<br />
luôn nhỏ hơn n.<br />
Một trạng thái của chương trình là một cặp Γ, P trong đó Γ = p1 : E1 ; . . . ; pk : Ek<br />
và P = k1 pi (ei ). Ta nói rằng Γ thỏa mãn P , kí hiệu là Γ |= P , nếu tồn tại S1 , . . . , Sk mà<br />
`<br />
|Ei | ⊢ ei : Si với mọi i = 1, . . . , k. Với mỗi i, Ei miêu tả số log đã được tạo ra hoặc sao chép<br />
trong luồng pi và Si miêu tả số log sẽ được tạo ra khi thực thi ei . Vì vậy, luồng pi có hành vi về<br />
log được miêu tả bởi sim( +|Ei | , Si ), trong đó hàm sim được định nghĩa trong Định lí 2.1. Ta sẽ<br />
chứng minh rằng sim( +|Ei | , S) = ♯n với một số n. Ta kí hiệu giá trị n này là |Ei , Si |. Tổng số<br />
log của một trạng thái chương trình, bao gồm trong Γ và các log sẽ được tạo ra khi thực thi phần<br />
còn lại của chương trình, được kí hiệu là |Γ, P |, được định nghĩa như sau:<br />
k<br />
X<br />
|Γ, P | = |Ei , Si |<br />
i=1<br />
Bởi vì |Γ, P | thể hiện số log tối đa từ trạng thái hiện tại và |Γ| là số log ở trạng thái hiện tại, chúng<br />
ta có bổ đề sau.<br />
Bổ đề 2.1 (Trạng thái). Nếu Γ |= P thì |Γ, P | ≥ |Γ|.<br />
<br />
Chứng minh. Theo định nghĩa của |Γ, P | và |Γ|, ta chỉ cần chứng minh |Ei , Si | ≥ |Ei | với mọi i.<br />
Điều này đúng theo Định lí 2.1.<br />
<br />
Bổ đề 2.2 (Bất biến cục bộ). Nếu E, e → E ′ , e′ , và |E| ⊢ e : S thì tồn tại S ′ sao cho |E ′ | ⊢ e′ : S ′<br />
và |E, S| ≥ |E ′ , S ′ |.<br />
<br />
<br />
<br />
90<br />
Hệ thống kiểu tính cận trên số log cho ngôn ngữ giao dịch đa luồng tối giản<br />
<br />
<br />
Chứng minh. Việc chứng minh được thực hiện bằng cách kiểm tra trực tiếp các luật ngữ nghĩa đối<br />
tượng trong Bảng 1.<br />
<br />
Bổ đề 2.3 (Bất biến toàn cục). Nếu Γ |= P và Γ, P ⇒ Γ′ , P ′ thì Γ′ |= P ′ và |Γ, P | ≥ |Γ′ , P ′ |.<br />
<br />
Chứng minh. Việc chứng minh được thực hiện bằng cách kiểm tra từng quy tắc một tất cả các quy<br />
tắc ngữ nghĩa trong Bảng 2. Với mỗi quy tắc, ta cần chứng minh hai phần: (i) Γ′ |= P ′ và (ii)<br />
|Γ, P | ≥ |Γ′ , P ′ |.<br />
• Với quy tắc G-SPAWN, từ giả thiết Γ |= P và định nghĩa của |= ta có |E| ⊢ spawn(e1 ); e2 : S<br />
và |E, S| = n với một số E, S, n. Với G-SPAWN, ta cần chứng minh hai phần tử mới trong<br />
Γ′ là |E| ⊢ spawn(e1 ) : S1 và |E| ⊢ e2 : S2 cho S1 , S2 , điều này được suy ra trực tiếp<br />
từ T-MERGE vì spawn(e1 ) chỉ có thể kết hợp với e2 trong quy tắc kiểu này. Thêm nữa, ta có<br />
S = merge(S1 , S2 ).<br />
Theo định nghĩa của merge, ta có số ¬ trong S, S1 và S2 là tương tự, và theo định nghĩa của<br />
jc, nó bằng |E|. Vì vậy |E, Si | = ni với một số ni với i = 1, 2. Vì vậy (i) được chứng minh.<br />
Đối với (ii), trước tiên, theo định nghĩa của merge và jc ta có n = n1 +n2 . Ta cần chứng minh<br />
|Γ, P |−|Γ′ , P ′ | ≥ 0. Bởi vì Γ và Γ′ chỉ khác trong luồng E, E1 , E2, ta có |Γ, P |−|Γ′ , P ′ | =<br />
|E, S| − |E, S1 | − |E, S2 | = n − n1 − n2 = 0. Vì vậy (ii) được chứng minh.<br />
• Đối với G-TRANS, tương tự như các trường hợp trước, theo giả thiết ta có |E| ⊢ onacid; e : S<br />
và |e, S| = n đối với một số E, S, n. Quy tắc G-TRANS tạo ra một log mới trong E vì vậy ta<br />
cần chứng minh |E| + 1 ⊢ e : S ′ cho một số S ′ . Bởi vì onacid; e có thể được định kiểu bởi<br />
T-SEQ hoặc T-JC, ta có hai trường hợp cần xem xét.<br />
- Với quy tắc T-SEQ, (i) được suy ra trực tiếp bởi quy tắc kiểu khi thành phần đầu tiên<br />
là −1 ⊢ onacid : +1. Đối với (ii), tương tự như các trường hợp trước, ta có |Γ, P | −<br />
|Γ′ , P ′ | = | +|E| , +1 S ′ | − | +(|E| + 1) , S ′ | = 0. Vì vậy (ii) được chứng minh.<br />
- Với quy tắc T-JC, (i) được suy ra trực tiếp bởi quy tắc kiểu khi thành phần đầu tiên là<br />
−1 ⊢ onacid : +1 . Đối với (ii) ta có<br />
|Γ, P | − |Γ′ , P ′ | = | +|E| , jc( +1 , S ′ )| − | +(|E| + 1) , S ′ |<br />
= sim(|E|, jc( +1 , S ′ ) − sim( +(|E| + 1) , S ′ )<br />
= seq(jc(|E|, jc( +1 , S ′ )) − seq(jc( +(|E| + 1) , S ′ ))<br />
= seq(jc(|E|, jc( +1 , S ′ )) − seq(jc( +|E| , jc( +1 , S ′ )))<br />
=0<br />
Vì vậy (ii) được chứng minh.<br />
• Đối với quy tắc G-COMM, với giả thuyết Γ |= P ta có Ei ⊢ commit; ei : Si đối với một số<br />
Ei , Si , i = 1..k′ .<br />
Vì commit; ei có thể được xác định kiểu bởi T-SEQ và commit là một joint commit, (i) được<br />
suy ra bởi quy tắc kiểu khi biểu thức đầu tiên là +1 ⊢ commit : −1 và ta có Si = ¬ 1 Si′<br />
trong đó Si′ là kiểu của ei .<br />
Đối với (ii) ta có:<br />
|Γ, P | − |Γ′ , P ′ |<br />
Pk′<br />
= 1 (|Ei , ¬ 1 Si′ | − |Ei′ , Si′ |)<br />
Pk′<br />
= 1 (seq(jc( +|Ei | , ¬ 1 Si′ )) − seq(jc( +|Ei′ | , Si′ ))2cm định nghĩa |E, S|<br />
<br />
<br />
91<br />
Trương Anh Hoàng, Nguyễn Ngọc Khải<br />
<br />
<br />
<br />
k ′<br />
X<br />
= (seq(jc( +|Ei | , ¬ 1 Si′ )) − seq(jc( +(|Ei | − 1) , Si′ )) |Ei′ | = |Ei | − 1<br />
1<br />
k′<br />
X<br />
= (seq(jc( +(|Ei | − 1) , ♯1 Si′ )) − seq(jc( +(|Ei | − 1) , Si′ )) định nghĩa jc<br />
1<br />
≥0<br />
Vì vậy (ii) được chứng minh.<br />
• Các quy tắc còn lại được chứng minh tương tự.<br />
<br />
Tiếp theo chúng ta xem xét tính đúng đắn của hệ thống kiểu đã đề xuất.<br />
Định lý 2.2 (Tính đúng đắn). Giả sử 0 ⊢ e : ♯n và p1 : ǫ, p1 (e) ⇒∗ Γ, P . Thì |Γ| < n.<br />
<br />
Chứng minh. Với môi trường ban đầu ta có |p1 : ǫ, p1 (e)| = sim(0, ♯n ) = ♯n . Vì vậy từ Bổ<br />
đề 2.2, 2.3 và Định lí 2.1, định lí được chứng minh bằng quy nạp theo độ dài của dẫn xuất.<br />
Định lí cuối cùng này đã khẳng định nếu chương trình có kiểu hợp lệ, thì chương trình đó<br />
khi thực hiện sẽ không bao giờ tạo ra số log cùng tồn tại lớn hơn giá trị thể hiện trong kiểu của<br />
chương trình.<br />
<br />
3. Kết luận<br />
Chúng tôi đã trình bày một hệ thống kiểu cho một ngôn ngữ lập trình lập trình được tối<br />
giản, chỉ có các lệnh liên quan đến bộ nhớ giao dịch và tạo luồng. Hệ thống kiểu này có một số<br />
ưu điểm so với hệ thống kiểu trước đây [10, 14] ở điểm vừa đơn giản hơn lại vừa tính được chính<br />
xác hơn số lượng log tối đa cùng tồn tại trong quá trình thực thi của chương trình. Hệ thống kiểu<br />
cho ngôn ngữ lõi này là nền tảng để áp dụng vào các ngôn ngữ lập trình trên thực tế. Đây cũng là<br />
hướng nghiên cứu tiếp theo của chúng tôi để áp dụng cho các ngôn ngữ lập trình trên thực tế.<br />
<br />
Lời cảm ơn. Nghiên cứu này được tài trợ bởi Quỹ Phát triển khoa học và công nghệ Quốc<br />
gia (NAFOSTED) trong đề tài mã số 102.03-2014.23.<br />
<br />
<br />
TÀI LIỆU THAM KHẢO<br />
<br />
[1] Elvira Albert, Samir Genaim, and Miguel Gomez-Zamalloa, 2007. Heap space analysis for<br />
Java bytecode. In ISMM ’07, New York, NY, USA. ACM.<br />
[2] David Aspinall, Robert Atkey, Kenneth MacKenzie, and Donald Sannella, 2010. Symbolic<br />
and analytic techniques for resource analysis of Java bytecode. In TGC’10, number 6084 in<br />
LNCS. Springer-Verlag.<br />
[3] Marc Bezem, Dag Hovland, and Hoang Truong, 2012. A type system for counting instances<br />
of software components. Theor. Comput. Sci., 458:29-48.<br />
[4] Victor Braberman, Diego Garbervetsky, and Sergio Yovine, 2006. A static analysis for<br />
synthesizing parametric specifications of dynamic memory consumption. Journal of Object<br />
Technology, 5(5).<br />
[5] Wei-Ngan Chin, Huu Hai Nguyen, Shengchao Qin, and Martin C. Rinard, 2005. Memory<br />
usage verification for OO programs. In Proceedings of SAS ’05, Volume 3672 of LNCS.<br />
Springer-Verlag.<br />
<br />
92<br />
Hệ thống kiểu tính cận trên số log cho ngôn ngữ giao dịch đa luồng tối giản<br />
<br />
<br />
[6] Martin Hofmann and Steffen Jost, 2003. Static prediction of heap space usage for first-order<br />
functional programs. In Proceedings of POPL ’03. ACM.<br />
[7] Martin Hofmann and Steffen Jost, 2006. Type-based amortised heap-space analysis (for an<br />
object-oriented language). In Peter Sestoft, editor, Proceedings of ESOP 2006, volume 3924<br />
of LNCS. Springer-Verlag.<br />
[8] John Hughes and Lars Pareto, 1999. Recursion and dynamic data-structures in bounded<br />
space: Towards embedded ML programming. SIGPLAN Notices, 34(9).<br />
[9] Suresh Jagannathan, Jan Vitek, Adam Welc, and Antony Hosking, 2005. A transactional<br />
object calculus. Sci. Comput. Program., 57(2):164-186.<br />
[10] Thi Mai Thuong Tran, Martin Steffen, and Hoang Truong, 2013. Compositional static<br />
analysis for implicit join synchronization in a transactional setting. In Software Engineering<br />
and Formal Methods, Volume 8137 of LNCS, pages 212-228. Springer Berlin Heidelberg.<br />
[11] Tuan-Hung Pham, Anh-Hoang Truong, Ninh-Thuan Truong, and Wei-Ngan Chin, 2008. A<br />
fast algorithm to compute heap memory bounds of Java Card applets. In SEFM’08.<br />
[12] Nir Shavit and Dan Touitou, 1995. Software transactional memory. In Symposium on<br />
Principles of Distributed Computing, pages 204 - 213.<br />
[13] Mads Tofte and Jean-Pierre Talpin, 1997. Region-based memory management. Information<br />
and Computation, 132(2).<br />
[14] Xuan-Tung Vu, Thi Mai Thuong Tran, Anh-Hoang Truong, and Martin Steffen, 2012.<br />
A type system for finding upper resource bounds of multi-threaded programs with<br />
nested transactions. In Symposium on Information and Communication Technology 2012,<br />
SoICT ’12, Halong City, Quang Ninh, Viet Nam, August 23-24, 2012, pages 21-30.<br />
<br />
ABSTRACT<br />
<br />
A type system for counting logs of a minimal language with multithreaded and nested<br />
transactions<br />
<br />
We present a type system to estimate an upper bound for resource consumption of a<br />
multi-threaded, nested transactional memory program. The resource is abstracted as transaction<br />
logs. We build our type system for a core language with basic commands related to creating<br />
threads and transaction. Using the core language not only makes proving the soundness easier,<br />
it also allows us to extend the language with more features easier.<br />
Keywords: Type systems, resource bound, software transactional memory, static analysis,<br />
programming language.<br />
<br />
<br />
<br />
<br />
93<br />