intTypePromotion=1
zunia.vn Tuyển sinh 2024 dành cho Gen-Z zunia.vn zunia.vn
ADSENSE

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

Chia sẻ: ViHasaki2711 ViHasaki2711 | Ngày: | Loại File: PDF | Số trang:14

25
lượt xem
3
download
 
  Download Vui lòng tải xuống để xem tài liệu đầy đủ

Bài viết đư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.

Chủ đề:
Lưu

Nội dung Text: 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

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 />
ADSENSE

CÓ THỂ BẠN MUỐN DOWNLOAD

 

Đồng bộ tài khoản
2=>2