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
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
đượ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
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.
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
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ớ
giao dịch khác được thuận lợi.
14 trang |
Chia sẻ: thanhle95 | Lượt xem: 528 | Lượt tải: 1
Bạn đang xem nội dung tài liệu 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, để tải tài liệu về máy bạn click vào nút DOWNLOAD ở trên
JOURNAL OF SCIENCE OF HNUE DOI: 10.18173/2354-1075.2015-0055
Educational Sci., 2015, Vol. 60, No. 7A, pp. 80-93
This paper is available online at
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
Trương Anh Hoàng1, Nguyễn Ngọc Khải2
1Trường Đại học Công nghệ - Đại học Quốc Gia Hà Nội
2Trường Đại học Tài nguyên và Môi trường Hà Nội
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
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
đượ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
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.
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
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ớ
giao dịch khác được thuận lợi.
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.
1. Mở đầu
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
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
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].
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
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
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.
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
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
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
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
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
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
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.
Để 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
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
độ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ố
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
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
đồ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
Ngày nhận bài: 6/8/2015. Ngày nhận đăng: 15/11/2015.
Liên hệ: Trương Anh Hoàng, e-mail: truonganhhoang@gmail.com
80
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
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
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ỏ
(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
ứng được giải phóng.
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ó
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
đượ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.
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ộ.
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
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
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ể
ả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.
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
đ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.
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
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
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ế
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
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à
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:
• Đư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.
• 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.
• 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.
Ướ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
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
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,
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.
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
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ớ
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
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ớ
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ề
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õ
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ư
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
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.
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
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
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
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],
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
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
của một lớp chương trình JavaCard.
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
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
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
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.
81
Trương Anh Hoàng, Nguyễn Ngọc Khải
2. Nội dung nghiên cứu
2.1. Ví dụ minh họa
Để 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ụ
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
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
các chương trình con. Lệnh spawn tạo một luồng mới.
Đoạn mã 1: Một chương trình lồng nhau và đa luồng.
1 onacid;//thread 0
2 onacid;
3 spawn(e1;commit;commit);//thread 1
4 onacid;
5 spawn(e2;commit;commit;commit);//thread 2
6 commit;
7 e3;
8 commit;
9 e4;
10 commit
Hình 1. Các luồng song song, lồng nhau và cùng kết thúc
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
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
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
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
độ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ì
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.
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
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
đồng bộ này.
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
dịch con lồng nhau (e2=onacid; onacid; commit; commit), e3 là một giao dịch với ba
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
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
(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
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
82
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
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ó
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.
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
và giao dịch, và chúng tôi gọi là TM (transactional multi-threaded language). Trong nghiên cứu
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
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
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
phần này.
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
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
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
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
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
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
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,
chúng ta có một ước lượng số log tối đa chính xác hơn.
2.2. Ngôn ngữ TM
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
đ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.
P ::= 0 | p(e) |P ‖ P
e ::= e1; e2 | e1 + e2 |
spawn(e) | onacid | commit
Hình 2. Cú pháp của TM
2.2.1. Cú pháp
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
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
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
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
tài nguyên.
2.2.2. Ngữ nghĩa động
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
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
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
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
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
bộ và môi trường toàn cục.
Ngữ nghĩa mệnh lệnh
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. Ở
đâ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
dịch và các luồng.
Đị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
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
83
Trương Anh Hoàng, Nguyễn Ngọc Khải
dịch) và log logi.
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|
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
trường rỗng/trống kí hiệu là ǫ.
E, (e1 + e2); e→ E, (e1; e) L-COND1
E, (e1 + e2); e→ E, (e2; e) L-COND2
Bảng 1. Ngữ nghĩa mệnh lệnh
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
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
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
giá trị các biểu thức điều kiện.
Ngữ nghĩa giao dịch và đa luồng
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 ,
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
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:
Đị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
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
của luồng và Ei là môi trường cục bộ của luồng pi.
E, e→ E′, e′ p : E ∈ Γ ref lect(p,E′,Γ) = Γ′
Γ, P ‖ p(e)⇒ Γ′, P ‖ p(e′) G-PLAIN
p′ fresh spawn(p, p′,Γ) = Γ′
Γ, P ‖ p(spawn(e1); e2)⇒ Γ′, P ‖ p(e2) ‖ p′(e1) G-SPAWN
l fresh start(l, p,Γ) = Γ′
Γ, P ‖ p(onacid; e)⇒ Γ′, P ‖ p(e) G-TRANS
p : E ∈ Γ E = ..; l : log; intranse(Γ, l) = p¯ = p1..pk commit(p¯, E¯,Γ) = Γ′
Γ, P ‖∐k1 pi(commit; ei)⇒ Γ′, P ‖ (∐k1 pi(ei)) G-COMM
Γ = Γ′′; p : E |E| = 0
Γ, P ‖ p(commit; e)⇒ error G-ERROR
Bảng 2. Ngữ nghĩa giao dịch và luồng
Để mô tả ngữ nghĩa của ngôn ngữ, ta cần thêm một số hàm phụ trợ sau:
• 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
ref lect(p,E′,Γ) = Γ′ và Γ = p1 : E1, ..., pk : Ek thì Γ′ = p1 : E′1..., pk : E
′
k với|Ei| = |E′i| (với mọi i).
• Hàm spawn sinh ra một luồng mới p′ từ luồng cha p: Giả sử Γ = p : E,Γ′′ và p′ /∈ Γ và
spawn(p, p′,Γ) = Γ′, thì Γ′ = Γ, p′ : E′ với |E| = |E′|.
• 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,Γ) = Γ′
với Γ = p1 : E1, ..., pi : Ei, ..., pk : Ek và với một lệnh fresh l, thì Γ′ = p1 : E1, ..., pi :
E′i, ..., pk : Ek, với |E′i| = |Ei|+ 1.
84
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
• 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
E = E′, l : ρ và intranse(Γ, l) = p¯, thì:
- p ∈ p¯ và
- với mọi pi ∈ p¯ ta có Γ = ..., pi : (E′i, l : ρi), ...
- với mọi luồng p′ với p′ /∈ p¯ và Γ = ..., p′ : (E′, l′ : ρ′), ..., ta có l′ 6= l.
• Hàm commit kết thúc một giao dịch: nếu commit(p¯, E¯,Γ) = Γ′ với Γ = Γ′′, p : (E, l : ρ)
và p¯ = intranse(Γ, l) thì Γ′ = ..., pj : E′j , ..., pi : E;i , ... trong đó pi ∈ p¯, pj /∈ p¯, pj :
Ej ∈ Γ, với |E′j | = |Ej | và |E′i| = |Ei| − 1.
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
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ư
sau:
• 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 ∈ Γ,
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ẽ
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 Γ′.
• 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
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 Γ′.
• 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
dịch mới với nhãn l được sinh ra, môi trường được biến đổi từ Γ sang Γ′.
• 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à
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.
• 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
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à
|E| = 0 thì sẽ trả lại kết quả lỗi (error).
2.3. Hệ thống kiểu
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
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ó
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.
2.3.1. Kiểu
Để 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í
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
¬ 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
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í
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
này sử dụng dãy hữu hạn của tập số có dấu TN = {+n , −n , ♯n , ¬n | n ∈ N+}. Chúng tôi sẽ cố
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.
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à
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 đó
để 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.
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ữ
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
chèn phần tử ♯0 khi cần thiết.
Chúng ta dùng s cho các phần tử thuộc TN, kí hiệu T N¯ là tập hợp của tất cả các chuỗi của
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í
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
85
Trương Anh Hoàng, Nguyễn Ngọc Khải
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
của s (ví dụ s = tag(s)|s|). Đối với một chuỗi S ∈ T N¯, chúng ta kí hiệu tag(S) cho chuỗi của các
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
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}.
Tập T N¯ 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
đươ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
gọn nhất để đại diện cho lớp đó và gọi nó là chuỗi chính tắc.
Đị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
con ‘−−’, ‘♯♯’, ‘++’, ‘+−’, ‘+♯−’, ‘+¬’ hoặc ‘+♯¬’ và |S(i)| > 0 với mọi i.
Ở đâ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
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
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à
‘+♯¬’ sẽ được xử lí bởi hàm jc ở phần sau.
Định nghĩa 2.4 (Đơn giản hóa). Hàm seq được định nghĩa đệ quy như sau:
seq(S) = S khi S là chính tắc
seq(S ♯m ♯nS′) = seq(S ♯max(m,n)S′)
seq(S +m +nS′) = seq(S +(m+ n)S′)
seq(S −m −nS′) = seq(S −(m+ n)S′)
seq(S +m ♯l −nS′) = seq(S +(m− 1) ♯(l + 1) −(n− 1)S′)
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