1. Trang chủ
  2. » Luận Văn - Báo Cáo

Nghiên cứu cơ chế lập trình tương tranh cho ngôn ngữ lập trình hàm sml

91 14 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 91
Dung lượng 915,43 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Ngôn ngữ hỗ trợ lập trình song song và phân tán gọi tắt là ngôn ngữ lập trình song song không những phải giúp lập trình viên chuyển tải ý tưởng thiết kế thành các phần mềm mà còn phải đả

Trang 1

TRẦN

BỘ GIÁO DỤC VÀ ĐÀO TẠO

Trang 2

TRƯỜNG ĐẠI HỌC BÁCH KHOA HÀ NỘI

LUẬN VĂN THẠC SĨ CÔNG NGHỆ THÔNG TIN

NGƯỜI HƯỚNG DẪN KHOA HỌC:

TS NGUYỄN HỮU ĐỨC

HÀ NỘI – 2008

Trang 3

LỜI CAM ĐOAN

Tôi xin cam đoan Luận văn này là công trình nghiên cứu khoa học của riêng tôi, không hề sao chép bất kỳ một

công trình nào

Trang 4

MỤC LỤC

LỜI CAM ĐOAN i

MỤC LỤC ii

DANH MỤC CÁC BẢNG iii

DANH MỤC CÁC HÌNH iv

DANH MỤC CÁC TỪ VIẾT TẮT v

CHƯƠNG 1 ĐẶT VẤN ĐỀ 1

1.1 Giới thiệu 1

1.2 Nội dung nghiên cứu đề tài 3

1.3 Phương pháp nghiên cứu đề tài 4

1.4 Các bước thực hiện đề tài 4

1.5 Kết cấu của Luận văn 4

CHƯƠNG 2 NGÔN NGỮ LẬP TRÌNH HÀM SML# 6

2.1 Giới thiệu 6

2.2 Lớp ngôn ngữ SML (Standard Metalanguage) 10

2.3 Ngôn ngữ SML# 19

CHƯƠNG 3 KỸ THUẬT LẬP TRÌNH TƯƠNG TRANH 25

3.1 Lập trình tương tranh (Concurrent Programming) 25

3.2 Một số vấn đề về tương tranh 28

3.3 Các cách tiếp cận tương tranh trên một số ngôn ngữ lập trình 36

CHƯƠNG 4 XÂY DỰNG CƠ CHẾ LẬP TRÌNH TƯƠNG TRANH 52

4.1 Thiết kế hệ thống biên dịch hỗ trợ lập trình đa luồng trên SML# 52

4.2 Thiết kế hệ thống thực thi hỗ trợ lập trình đa luồng trên SML# 61

CHƯƠNG 5 KẾT LUẬN VÀ HƯỚNG PHÁT TRIỂN 78

5.1 Những kết quả đã đạt được và đóng góp mới của Luận văn 78

5.2 Hướng phát triển tiếp theo của Luận văn 79

DANH MỤC CÁC THUẬT NGỮ 80

TÀI LIỆU THAM KHẢO 82

Trang 5

DANH MỤC CÁC BẢNG

Bảng 4.1 Một số lệnh thực thi trên máy ảo của SML# 57 Bảng 4.2 Các bước biên dịch trong SML# 60

Trang 6

DANH MỤC CÁC HÌNH

Hình 3.1 Các mô hình kết hợp giữa tiến trình và luồng 30

Hình 3.2 Mô hình tiến trình đơn luồng và tiến trình đa luồng 31

Hình 3.3 Wait set 39

Hình 3.4 Các thông điệp trong Mailbox 44

Hình 3.5 Kiến trúc Heap trong Manticore 50

Hình 4.1 Ví dụ minh họa thực thi đa luồng 54

Hình 4.2 Các mô đun chính trong quá trình biên dịch 59

Hình 4.3 Mô hình tổ chức máy ảo trong SML# 62

Hình 4.4 Mô hình tổ chức mới của máy ảo 63

Hình 4.5 Mô hình bộ nhớ hiện tại của SML# 63

Hình 4.6 Mô hình bộ nhớ thực thi đa luồng 64

Hình 4.7 Cài đặt các thanh ghi trong máy ảo của SML# 65

Hình 4.8 Lớp Session 66

Hình 4.9 Các bước khởi tạo Session 67

Hình 4.10 Cấu trúc Executable 67

Hình 4.11 Các bước thực thi máy ảo 68

Hình 4.12 Quá trình khởi tạo máy ảo thực thi đa luồng 69

Hình 4.13 Phương thức khởi tạo các luồng 70

Hình 4.14 Các bước khởi tạo luồng 71

Hình 4.15 Thuật toán GC 74

Hình 4.16 Xử lý ngoại lệ 77

Trang 8

CHƯƠNG 1 ĐẶT VẤN ĐỀ 1.1 Giới thiệu

Trong thế giới thông tin ngày nay, sự phát triển mạnh mẽ của các ứng dụng song song và phân tán kéo theo những nhu cầu cấp thiết về một nền tảng phát triển phần mềm để đảm bảo tính hiệu quả cho ứng dụng Với mục đích phục vụ tốt nhất cho người sử dụng, các phần mềm phải đảm bảo hiệu năng cao nhất cũng như sự hợp lý trong việc sử dụng tài nguyên Công nghệ

mạng hay gần đây là công nghệ chế tạo chíp đa lõi (multicore) có thể được

coi như những hạ tầng cần thiết cho nền tảng này Tuy nhiên, khi nhắc đến một ứng dụng song song hay phân tán, ta không thể không đề cập đến ngôn ngữ lập trình sử dụng để phát triển ứng dụng Ngôn ngữ hỗ trợ lập trình song song và phân tán (gọi tắt là ngôn ngữ lập trình song song) không những phải giúp lập trình viên chuyển tải ý tưởng thiết kế thành các phần mềm mà còn phải đảm bảo được rằng phần mềm đó có thể thực thi hiệu quả trên nền các

hạ tầng tính toán sẽ sử dụng

Một ngôn ngữ lập trình song song tốt phải hướng đến việc cung cấp các cơ chế song song hóa ở nhiều cấp độ khác nhau Thông thường, tính song song được thể hiện ở ba cấp độ, đó là [FF+07]:

- Song song hóa ẩn (implicit parallelism): ở đó, trình biên dịch tự động phân chia công việc và song song hóa chúng bằng cách thực thi trên các luồng tính toán (thread) khác nhau

- Phân luồng ẩn (implicit threading): ở đó, người lập trình cần cung

cấp các thông tin có ích cho việc song song hóa Tuy nhiên, việc phân chia công việc và ánh xạ chúng lên các luồng tính toán được giao phó cho trình biên dịch

- Phân luồng tường minh (explicit threading): ở đó, người lập trình

Trang 9

phải tự phân chia công việc và ánh xạ chúng lên các luồng tính toán trong chương trình

Trên thực tế, phần lớn các ngôn ngữ mới chỉ hỗ trợ ở mức phân luồng

tường minh Các ngôn ngữ lập trình chỉ thị (imperative) như C hay Java khó

có thể cài đặt cơ chế song song hóa tự động do những đặc trưng ngôn ngữ (sự phụ thuộc về thứ tự thực hiện quá chặt chẽ trong cơ chế chỉ thị) Ta hãy phân tích khả năng song song hóa của lớp các ngôn ngữ lập trình hàm

Với cách tiếp cận dựa trên các lời gọi hàm, lập trình hàm là một mô

hình lập trình dạng khai báo (Declarative Programming) được xây dựng dựa

trên cơ sở định giá các biểu thức toán học mà bỏ qua trạng thái và những dữ

liệu có thể thay đổi (mutable data) [HPa89] Việc tính toán một biểu thức

toán học có thể thực hiện bằng cách đánh giá độc lập các biểu thức con sau

đó kết hợp giá trị của chúng Đây là một yếu tố quan trọng cho việc song song hóa tự động Ngoài ra, với việc không sử dụng khái niệm biến nên người lập trình không cần phải quan tâm đến thao tác đọc và ghi nó Trong bối cảnh lập trình tương tranh, điều này giúp người lập trình tránh khỏi những phức tạp trong việc xử lý xung đột khi truy cập tài nguyên Những đặc điểm trên đây cho thấy các ngôn ngữ lập trình hàm tỏ ra rất phù hợp với mô hình lập trình tương tranh Thực tế là, một số ngôn ngữ lập trình hàm như: Erlang và SML/NJ đã bước đầu xây dựng cơ chế lập trình này

Tuy nhiên, thực tiễn cho thấy các ngôn ngữ lập trình hàm không được

áp dụng phổ biến trong phát triển ứng dụng Nếu bỏ qua những yếu tố về thói quen lập trình thì hai trong những nguyên nhân chính của thực trạng này là sự kém hiệu quả (hiệu năng ứng dụng thấp) và khả năng tương tác yếu (chia sẻ thư viện) với các ngôn ngữ khác Dự án xây dựng ngôn ngữ SML# [SP07] ra đời nhằm mục tiêu khắc phục được những nhược điểm này Tuy nhiên, phiên bản hiện tại của SML# chưa hỗ trợ các cơ chế lập trình tương tranh

Trang 10

Xuất phát từ điều đó, Luận văn tham vọng xây dựng một ngôn ngữ lập trình hàm có hỗ trợ song song hóa ở nhiều cấp, đồng thời có tính tương tác mạnh đối với những ngôn ngữ lập trình khác Trong bước tiếp cận đầu tiên, Luận văn tập trung vào việc giải quyết cơ chế lập trình tương tranh cho

SML# ở mức độ hỗ trợ cấp ngôn ngữ (explicit parallelism)

Với mục tiêu nói trên, Luận văn nghiên cứu và đề xuất một hệ thống thực thi đa luồng cho SML# theo mô hình bộ nhớ chia sẻ Mỗi luồng được

thực thi trong một không gian riêng với một hệ thống ngăn xếp (stack) riêng

và bộ thanh ghi (register) riêng Mã thực thi của các luồng được đặt trên một

bộ đệm lệnh (code buffer) chung Các đối tượng phức hợp tạo ra trong các luồng được cấp phát trên một vùng nhớ duy nhất (heap)

1.2 Nội dung nghiên cứu đề tài

Trong khuôn khổ của một luận văn thạc sĩ, đề tài chỉ giới hạn nghiên cứu lập trình tương tranh trong ngôn ngữ lập trình hàm SML# Cụ thể, về nội dung, Luận văn tập trung vào:

- Nghiên cứu lập trình hàm nói chung và xem xét cụ thể với ngôn ngữ lập trình hàm SML#

- Nghiên cứu hệ thống biên dịch và hệ thống thực thi của ngôn ngữ lập trình này

- Nghiên cứu kỹ thuật lập trình tương tranh và cách tiếp cận lập trình tương tranh hiện nay của một số ngôn ngữ hỗ trợ nó

- Đề xuất cơ chế lập trình tương tranh thông qua việc sửa đổi hệ thống biên dịch và hệ thống thực thi của ngôn ngữ lập trình hàm SML#

Trang 11

1.3 Phương pháp nghiên cứu đề tài

Khi nghiên cứu các đặc điểm của ngôn ngữ lập trình và các kỹ thuật lập trình tương tranh, Luận văn tuân thủ một số nguyên tắc cơ bản sau:

Nguyên tắc 1: Giữ vững các đặc trưng vốn có của ngôn ngữ lập trình hàm SML# Bởi lẽ, bất kỳ một sự thay đổi nào cũng có thể dẫn đến việc phải hiệu chỉnh toàn bộ hệ thống

Nguyên tắc 2: Nghiên cứu kết hợp các giải pháp lập trình tương tranh

để đề xuất mô hình và chiến lược cài đặt phù hợp với ngôn ngữ lập trình SML#

Nguyên tắc 3: Quá trình phân tích được triển khai từ tổng quan cho đến

chi tiết từng vấn đề, theo hướng từ trên xuống (Top-down)

1.4 Các bước thực hiện đề tài

Để thực hiện đề tài, quá trình nghiên cứu được triển khai qua từng bước sau:

- Nghiên cứu tổng quan về lập trình hàm, xem xét các đặc trưng của ngôn ngữ SML#

- Nghiên cứu cách tiếp cận lập trình tương tranh trên một số ngôn ngữ có hỗ trợ như: Java, SML/NJ, Erlang, CML,…

- Xem xét phần cài đặt cụ thể của SML# và đề xuất mô hình lập trình tương tranh phù hợp với ngôn ngữ này

1.5 Kết cấu của Luận văn

Luận văn gồm có 5 chương với các nội dung như sau:

C hương 1 Đặt vấn đề

Khái quát mục đích và tính cần thiết của việc song song hóa Đề cập các đặc điểm chung của lập trình hàm Đưa ra nội dung, cách tiếp cận và các

Trang 12

bước thực hiện để giải quyết cơ chế lập trình tương tranh trên ngôn ngữ lập trình hàm SML#

Chương 2 Ngôn ngữ lập trình hàm SML#

Giới thiệu tổng quan về lập trình hàm, tập trung vào lớp ngôn ngữ SML và xem xét chi tiết SML#, qua đó cung cấp cái nhìn rõ ràng hơn về các đặc điểm của ngôn ngữ lập trình này

Chương 3 Kỹ thuật lập trình tương tranh

Giới thiệu kỹ thuật lập trình tương tranh và các vấn đề cơ bản của nó Tiếp đó, xem xét và phân tích các cách tiếp cận lập trình tương tranh trên một

số ngôn ngữ hỗ trợ như: Java, Erlang, CML và Manticore, từ đó rút ra các

đặc điểm và giải pháp của từng cách tiếp cận

Chương 4 Xây dựng cơ chế lập trình tương tranh

Trên cơ sở nghiên cứu kiến trúc hiện tại của ngôn ngữ SML# về hệ thống biên dịch, hệ thống thực thi và đặc trưng của các mô hình lập trình tương tranh đã được đề cập trong Chương 3, Luận văn đề xuất cơ chế lập trình tương tranh phù hợp với ngôn ngữ SML# và chiến lược cài đặt nó trên ngôn ngữ này

Chương 5 Kết luận và hướng phát triển

Tóm tắt những kết quả nghiên cứu đã đạt được, qua đó đề ra hướng phát triển tiếp theo cho Luận văn

Trang 13

CHƯƠNG 2 NGÔN NGỮ LẬP TRÌNH HÀM SML#

Chương này giới thiệu các đặc trưng của ngôn ngữ lập trình hàm, những điểm chính của lớp ngôn ngữ SML và tập trung khảo sát các đặc điểm của ngôn ngữ lập trình hàm SML#

2.1 Giới thiệu

Lập trình hàm (Functional Programming) là cách tiếp cận lập trình dựa

trên các lời gọi hàm Đây là điểm khác biệt so với các ngôn ngữ thuộc lớp lập trình chỉ thị vốn được xây dựng dựa trên các lệnh Lập trình hàm là mô hình lập trình, trong đó, việc tính toán được thực hiện thông qua việc định giá các hàm mà bỏ qua sự thay đổi trạng thái và dữ liệu

Lập trình hàm dựa trên các biểu thức Những biểu thức này thỏa mãn các quy luật toán học trên cơ sở giới hạn của máy (ví dụ như: các thuật toán đối với số thực chỉ cho kết quả gần đúng) Ngữ nghĩa của một biểu thức, đơn giản là kết quả của việc tính toán biểu thức đó, vì thế, các biểu diễn con có thể được định giá một cách độc lập Trong khi đó, lệnh là việc chuyển đổi trạng thái hoặc một thao tác khác phức tạp tương tự Vì vậy, để hiểu một lệnh, ta phải hiểu được đầy đủ ảnh hưởng của nó lên trạng thái của máy

Một biểu diễn ở lập trình hàm đơn giản và dễ hiểu hơn so với một biểu diễn trong các ngôn ngữ lập trình chỉ thị Việc sử dụng các hàm đệ quy có thể mang lại sự rõ ràng hơn về mặt diễn đạt thuật toán, tuy nhiên nó lại làm cho vấn đề cài đặt trở nên kém hiệu quả Thông thường, các biểu diễn trong ngôn

ngữ lập trình chỉ thị (như Pascal) không thể thỏa mãn các hàm toán học Một

bộ tối ưu biên dịch có thể dẫn đến việc tính toán biểu thức: f(z) + u/2 được

chuyển thành u/2 + f(z) Do đó, nó có thể sẽ không cho ra cùng kết quả bởi

hàm f có thể làm thay đổi giá trị của u Điều đó có nghĩa là một biểu diễn

trong Pascal bao gồm cả trạng thái lẫn giá trị

Trang 14

Lập trình hàm và lập trình Logic là những thể hiện của lập trình khai báo (Declarative Programming) Ý tưởng về dạng lập trình này là tạo sự tự

do khi viết chương trình, nghĩa là, người lập trình chỉ cần đặt ra yêu cầu còn máy tính sẽ làm phần còn lại Mục đích thực tế của lập trình khai báo là làm cho chương trình dễ hiểu hơn Tính chính xác của nó được đảm bảo bởi các

lý do toán học mà không cần bận tâm về các vấn đề khác Tuy nhiên, lập trình khai báo vẫn là lập trình, vì vậy, ta vẫn phải viết mã một cách có hiệu quả

Đặc trưng của các ngôn ngữ lập trình hàm là không có biến và cũng không có lệnh gán Trong dạng lập trình này, các hàm được xây dựng dựa trên tư tưởng của các hàm toán học Thông thường, chúng định ra nhiều trường hợp Mỗi trường hợp như vậy lại được định nghĩa độc lập qua việc xác định ra các hàm

Ta hãy xem xét thuật toán để tìm ra ước số chung lớn nhất của hai số

tự nhiên Trong Pascal, ta có thể cài đặt thuật toán này như sau:

function gcd(m,n: integer): integer;

var prevm: integer;

Trang 15

fun gcd (m,n) =

if m = 0 then n

else gcd (n mod m, m);

Rõ ràng, với ngôn ngữ lập trình chỉ thị, mặc dầu mã nguồn (code) được

cài đặt trên một ngôn ngữ cấp cao (như đoạn mã ở trên) nhưng thật khó trong sáng và ngắn gọn Với một ngôn ngữ lập trình như Pascal, ta cũng có thể cài đặt một chương trình đệ quy cho bài toán này Tuy nhiên, cũng cần lưu ý rằng các lời gọi thủ tục mang tính đệ quy hầu như không có hiệu quả

Trong các ngôn ngữ lập trình hàm, những định nghĩa hàm được biên dịch ít nhiều thành cú pháp của ngôn ngữ Toàn bộ chương trình đơn giản là một hàm, mà chính nó được định nghĩa trong mối quan hệ với các hàm khác

Ở đây, tất cả định nghĩa đều bị giới hạn bởi các giá trị Các biến không thực

sự cần thiết cho hình thức lập trình này, bởi vì, chúng sẽ nhận giá trị trong

quá trình liên kết (binding) và kết quả của một hàm ngay lập tức được chuyển

cho một hàm khác như một tham số

Về mặt quản lý bộ nhớ, các ngôn ngữ lập trình hàm thực hiện hoàn toàn tự động Theo đúng chu kỳ, hệ thống sẽ quét bộ nhớ, đánh dấu những phần được truy cập và giải phóng những phần còn lại Thao tác này được gọi

là garbage collection Tuy nhiên, nó có thể làm chậm quá trình xử lý và yêu

cầu nhiều không gian nhớ hơn nhưng cũng mang lại nhiều lợi ích quan trọng Người lập trình được giải phóng khỏi các thao tác quản lý bộ nhớ phức tạp và nhiều rủi ro, do vậy có thể làm việc hiệu quả hơn

Thông thường, các hàm là đệ quy và không có hiệu ứng phụ free) Nghĩa là, chúng chỉ tính toán kết quả mà không cần cập nhật các giá trị

(effect-tương ứng với các biến Điều đó cung cấp cho chúng ta một cách tiếp cận để giải quyết về bản chất việc tính toán Trên thực tế, xuất phát từ lý thuyết tính toán, lập trình hàm tạo ra cầu nối giữa các phương thức tính toán hình thức với các ứng dụng của chúng

Trang 16

Để tăng sức mạnh của các biểu diễn, các hàm phải không bị bó buộc, nghĩa là, chúng có thể nhận bất kỳ loại tham số nào (ngay cả chính các hàm)

và cũng có thể trả về bất kỳ dạng kết quả nào Hầu hết các ngôn ngữ lập trình hàm cho phép việc phân tích các tham số đầu vào bằng việc sử dụng hình

thức khớp mẫu (pattern-matching), như đoạn mã tính toán về số phần tử của

một danh sách sau:

fun length [] = 0

| length (x::xs) = 1 + length xs;

Ngoài ra, một tính năng khác mà các ngôn ngữ lập trình hàm có hỗ trợ

đó là đa hình (polymorphic) Tính đa hình được thể hiện ở cả hàm lẫn kiểu dữ

liệu:

- Đa hình ở kiểu dữ liệu cho phép khai báo một kiểu đơn (chẳng hạn

như danh sách – “list”) để mô tả cho cả danh sách các số nguyên (integer), các xâu (string), v.v Tuy nhiên, người lập trình vẫn được đảm bảo với một danh sách dạng “int list” thì tất cả các thành phần

của nó đều là số nguyên

- Các hàm đa hình cho phép khai báo một hàm đơn (chẳng hạn như

hàm lọc – filter_list) để xử lý trên các danh sách số nguyên, các

xâu, v.v mà không cần phải lặp lại mã

Yêu cầu đầu tiên của lập trình hàm là tính đúng đắn (correctness), thứ

hai là tính rõ ràng (clarity ) và cuối cùng mới là tính hiệu quả (efficient)

[LCP96] Các ngôn ngữ lập trình hàm điển hình như: APL, Erlang, Haskell, Lisp, ML, Oz, F#, Scheme, Standard ML, Lazy ML, CAML, CAML Light

Ngôn ngữ lập trình hàm kém phổ biến hơn các ngôn ngữ lập trình khác

vì nhiều lý do, chẳng hạn như tốc độ thực thi và tính hiệu quả Điều này dẫn đến việc ứng dụng thực tế của nó vẫn còn nhiều hạn chế Các ngôn ngữ lập trình hàm chỉ phù hợp với các ứng dụng lớn, phức tạp Hiện nay, vẫn còn

Trang 17

nhiều vấn đề về lập trình hàm đang được tập trung nghiên cứu Một trong những lĩnh vực quan trọng đó là mở rộng tính năng của lập trình hàm và việc tối ưu chương trình dịch

2.2 Lớp ngôn ngữ SML (Standard Metalanguage)

Tất cả các ngôn ngữ được thiết kế thành công đều vì mục đích đặc biệt nào đó, chẳng hạn như: Lisp cho trí tuệ nhân tạo, Fortran cho tính toán số, Prolog cho xử lý ngôn ngữ tự nhiên Trong khi đó, các ngôn ngữ được thiết

kế cho mục đích chung như ngôn ngữ thuật toán (algorithmic language):

Algol 60 và Algol 68 lại đạt được nhiều thành công về ý tưởng hơn là những công cụ thực hành

Ngôn ngữ ML (Metalanguage) ban đầu được thiết kế để chứng minh

định lý Đây là một lĩnh vực không rộng Trong phạm vi này, ML tập trung cho lập trình chứng minh định lý đặc biệt, đó là logic cho hàm tính toán

(Edinburgh LCF - Logic for Computable Functions) Phần thiết kế của ngôn

ngữ này dựa trên những đặc trưng cần thiết cho nhiệm vụ của nó, cụ thể là:

- Các luật suy diễn và phương thức chứng minh được mô tả thành các

hàm, vì thế ML mang đầy đủ sức mạnh của hàm bậc cao order function)

(higher Các luật suy diễn định nghĩa kiểu trừu tượng với hình thức kiểm tra kiểu mạnh và chấp nhận kiểu đa hình

- Chứng minh có thể được thực hiện theo nhiều nhánh Sự thất bại tại bất kì nhánh nào phải được phát hiện để chuyển sang nhánh khác

- ML được thiết kế an toàn và nó không làm hỏng môi trường trong bất kỳ trường hợp nào

Trình biên dịch ML đầu tiên được xây dựng vào năm 1974 Sau đó,

Trang 18

cộng đồng của ngôn ngữ này phát triển, dẫn đến nhiều lớp ngôn ngữ mới

được ra đời Standard ML (còn gọi là SML hoặc ML) là một ngôn ngữ chung

ra đời do sự phối hợp của cả cộng đồng ML

ML nhanh chóng được chú ý và trở nên phổ biến trong khoảng thời gian ngắn sau đó Nhiều trường đại học giảng dạy ML cho sinh viên như là một ngôn ngữ lập trình đầu tiên ML cung cấp mức độ lập trình cơ bản cho tất cả các sinh viên, mặc dù họ biết về C, Basic, ngôn ngữ máy hoặc chưa biết bất kỳ ngôn ngữ nào Sử dụng ML, sinh viên có thể học phân tích các vấn đề dưới góc độ toán học, bỏ đi thói quen bắt đầu từ những ngôn ngữ cấp thấp Các tính toán có thể được biểu diễn trong một vài dòng

Những người mới làm quen với ngôn ngữ này có những đánh giá đặc biệt về phần kiểm tra kiểu Nó có thể phát hiện các lỗi thông thường và không điều gì làm cho hệ thống đổ vỡ Trên thực tế, các nhà phát triển ứng dụng đã lựa chọn ML để làm ngôn ngữ cài đặt vì ML làm cho việc viết chương trình đơn giản, sáng sủa và đáng tin cậy

Tuy nhiên, điểm quan trọng hơn cả là ML bảo vệ cho người lập trình tránh khỏi những lỗi thường mắc phải Trước khi chương trình được thực hiện, bộ biên dịch kiểm tra sự hợp lệ của tất cả các mô đun và tính toàn vẹn của các dữ liệu được sử dụng Khi thực thi, các thao tác kiểm tra sẽ được tiếp tục thực hiện để đảm bảo tính an toàn, ngay cả một chương trình ML có sai sót Chương trình có thể thực hiện mãi mãi và có thể trả về cho người dùng một thông điệp lỗi nhưng nó không bao giờ bị đổ vỡ

2.2.1 Các đặc điểm của SML

Chương trình hàm hoạt động với các giá trị và không sử dụng trạng thái Công cụ của chúng là những biểu thức chứ không phải là những lệnh Tuy nhiên, người lập trình có thể sử dụng các kỹ thuật trong lập trình chỉ thị cho lập trình hàm để giải quyết các vấn đề cần thiết như lệnh gán, lặp và cấu

Trang 19

trúc mảng

- Suy diễn kiểu tự động

Đối với hầu hết các ngôn ngữ lập trình chỉ thị, việc định kiểu cho các thành phần ngôn ngữ được thực hiện bởi lập trình viên thông qua khai báo

ML giải phóng người sử dụng khỏi động tác này bằng cách cung cấp một hệ thống suy diễn kiểu tự động trong quá trình biên dịch Người lập trình không cần phải đưa ra tất cả các kiểu dữ liệu của biến và các tham số của hàm Tuy nhiên, từ ngữ cảnh, bộ biên dịch có thể tính toán ra kiểu của chúng Điều này làm cho chương trình ngắn gọn và đơn giản hơn trong cách viết

# val x = 1 + 2 x : int

# val f y = x + y f : int -> int

# val r = (x, f, f 1) r : int * (int -> int) * int

- Biểu diễn dữ liệu

Ngoài những kiểu dữ liệu cơ bản như số nguyên (int), xâu ký tự (string ), số thực (real), giá trị logic (bool), ML cung cấp một số cấu trúc dữ

# val r = {ten = “Tran Nhat Hoa”, tuoi = 27,

diachi = {thanhpho = “Ha noi”, quan = “Dong Da”}}

# val thanhpho = #thanhpho (#diachi r)

==> thanhpho = “Ha noi”

• Danh sách:

Trang 20

# val li = [1,2,3,4]

# val ls = [“a”, “b”, “c”, “d”]

Danh sách hỗ trợ cho việc truy cập tuần tự từ trái sang phải Điều này là đủ cho hầu hết các mục đích, thậm chí với cả các thao tác sắp xếp và ma trận Thư viện chuẩn của ML cung cấp một số hàm tiện

ích rất hữu dụng cho thao tác trên danh sách như: map, app, foldr,

• Kiểu dữ liệu tự định nghĩa:

Người lập trình có thể dễ dàng tự định nghĩa các kiểu dữ liệu của riêng mình Ví dụ cây nhị phân có thể được định nghĩa như sau:

datatype 'a tree = Leaf of 'a |Node of 'a * 'a tree * 'a tree val t = Node(1, Leaf 2, Node (3, Leaf 4, Leaf 5))

==> val l = [11,12] : int list

Hàm có thể được định nghĩa lồng nhau và đệ quy

==> val sum = fn : int -> int

Hàm có thể đa hình và có thể áp dụng trên mọi kiểu dữ liệu

Trang 21

# fun twice f x = f (f x)

val twice = fn : ['a.’a -> 'a]

# fun inc x = x + 1

# fun dup s = s ^ s

# val r1 = twice inc 1 ==> 3

# val r2 = twice dup “a” ==> “aaaa”

- Khớp mẫu

Hầu hết các ngôn ngữ lập trình hàm cho phép phân tích tham số của hàm bằng cách sử dụng khớp mẫu Một hàm tính toán số lượng các phần tử của một danh sách có thể được biểu diễn trong ML như sau:

fun leng [] = 0

| length (x::xs) = 1 + length xs;

Có thể thấy rằng số phần tử của danh sách rỗng ([]) là 0 Số phần tử

của một danh sách bao gồm phần tử đầu x nối với danh sách xs sẽ là số phần

tử của danh sách xs này cộng với 1

Các định nghĩa hàm trong ML thông thường xem xét nhiều trường hợp

với các mẫu phức tạp hơn dạng x::xs ở trên Rõ ràng, với các mô tả hàm như

vậy nếu không sử dụng đến khớp mẫu thì sẽ gặp rất nhiều khó khăn Tuy nhiên, bộ biên dịch ML thực hiện việc khớp mẫu một cách tự động ở bên trong, do đó, nó có thể thực thi công việc tốt hơn người lập trình

- Thao tác vào/ra

Về lý thuyết, vào/ra được coi là những thao tác có thể gây ra hiệu ứng

phụ (effect) và có thể là nguồn gốc gây ra lỗi của chương trình Một ngôn ngữ

lập trình hàm thuần túy thường không cho phép những thao tác gây ra hiệu ứng phụ như vậy Tuy nhiên, ML lại hỗ trợ các thao tác vào ra với mục đích tiện dụng cho người lập trình

- Mô đun

Bất kỳ ngôn ngữ ứng dụng nào cũng phải quan tâm tới khả năng mô

Trang 22

đun hóa cho chương trình Standard ML hỗ trợ cho các mô đun (gọi là

structure) và giao tiếp (gọi là signature) Một giao tiếp quyết định thành phần

và kiểu dữ liệu nào của mô đun có thể được nhìn thấy từ bên ngoài Việc

tham chiếu đến các thành phần (hàm, giá trị, kiểu, ) được đóng gói trong một mô đun có thể thông qua tên mô đun và tên của thành phần đó Ví dụ

như hàm dấu của một số thực sẽ là Real.sign (chứ không phải là sign) Các tên hàm có thể đặt trong nhiều mô đun (ví dụ, ta có hàm Int.sign để xác định

dấu cho số nguyên) Ở đây có một hệ thống linh hoạt cho việc khớp giữa mô đun và giao tiếp, đó là: có thể có nhiều giao tiếp khác nhau của cùng một mô đun, và có thể có nhiều cài đặt khác nhau khớp với cùng một giao tiếp ML

cũng hỗ trợ các mô đun bậc hai (funtor), trong đó một mô đun có thể được tạo ra bằng cách áp dụng funtor cho một mô đun khác

2.2.2 Các tính năng của SML

- Tính hiệu quả

Chương trình hàm thông thường mang theo một hệ thống thực thi lớn với một bộ biên dịch bên trong Phần quản lý bộ nhớ tự động có thể yêu cầu một dạng biểu diễn đặc thù của dữ liệu, nó không được tự nhiên giống như trong các ngôn ngữ lập trình chỉ thị vì cần thêm phần lưu trữ và xử lý Người lập trình hàm bị lấy đi hầu hết các cấu trúc dữ liệu hiệu quả, như mảng, xâu

và bit vectors Do đó, một chương trình hàm kém hiệu quả hơn chương trình

C tương ứng, đặc biệt là trong phần yêu cầu lưu trữ

ML phù hợp nhất cho những ứng dụng lớn và phức tạp Với kiểm tra kiểu, tự động phân phát bộ nhớ và những lợi ích khác của lập trình hàm tạo ra

sự khác biệt giữa các chương trình có chúng và những chương trình không

có Tuy nhiên, với ML, hiệu quả lại trở thành vấn đề thứ yếu Nhưng với một ứng dụng được yêu cầu, sự khác biệt này sẽ ít rõ ràng đi Hầu hết các chương trình hàm có khả năng thực thi nhanh như những chương trình thủ tục tương

Trang 23

tự Trong trường hợp xấu nhất, nó có thể chậm hơn năm lần [LCP96] Với những tiến bộ trong kỹ thuật tối ưu hóa của trình biên dịch, khoảng cách về hiệu năng này sẽ càng ngày càng được rút ngắn

- An toàn

Standard ML là một ngôn ngữ lập trình an toàn Các lỗi xung đột kiểu (thường thấy trong ép kiểu ở C) được phát hiện sớm trong giai đoạn biên dịch Hệ thống quản lý bộ nhớ tự động ngăn ngừa khả năng truy cập bộ nhớ trái phép (trước khi cấp phát hay sau khi giải phóng) cũng như các hiện tượng

rò rỉ bộ nhớ (memory leak) Hệ thống khớp giao diện (signature matching)

của ML cũng cho phép ngăn chặn những tham chiếu tới thành phần riêng tư của mô đun Tất cả các tính năng an toàn này đều được đảm bảo bằng một hệ thống chứng minh chặt chẽ

- Quản lý bộ nhớ tự động

Tiến trình đòi lại phần bộ nhớ mà chương trình không sử dụng nữa

(reclaiming memory) được gọi là garbage collection Garbage collection có

thể làm cho người lập trình tự do trong việc quản lý, điều khiển cấp phát bộ

nhớ động và giải phóng những phần bộ nhớ chết (dead location) Vì thế,

người lập trình không phải bận tâm đến việc cần phải giải phóng những phần

bộ nhớ không được sử dụng mà không làm ảnh hưởng đến những phần bộ nhớ khác Việc tự động giải phóng những dữ liệu không sử dụng và phần bộ nhớ chết làm cho chương trình đơn giản, sáng sủa và đáng tin cậy hơn

- Nghiêm ngặt

Trong ML (tương tự như C, Pascal, C++, Java, v.v.), tham số được đánh giá trước khi bắt đầu phần thân của hàm Một ngôn ngữ như vậy được

gọi là nghiêm ngặt hoặc gọi-bởi-giá trị (call-by-value) Một số ngôn ngữ khác

như Haskell lại cho phép đánh giá tham số tại thời điểm chúng được sử dụng

(lazy hay call-by-need) Đánh giá nghiêm ngặt làm cho việc thực thi chương

Trang 24

trình của người lập trình đơn giản hơn

- Kiểm tra kiểu tại thời điểm biên dịch

Với các ngôn ngữ kiểm tra kiểu tại thời điểm biên dịch, người lập trình nhận được nhiều lợi ích không chỉ là sự thực thi nhanh hơn mà còn cả việc có

ít lỗi cần gỡ rối hơn vì nhiều lỗi của người lập trình có thể được phát hiện sớm

- Bắt lỗi

Cơ chế bắt lỗi của ML tương tự như trong C++, Java, Ada, v.v Nó cung cấp các nắm bắt lồng nhau và ước lượng sự cần thiết cho các ngoại lệ

từ các hàm một cách đặc biệt

- Các kiểu không thay đổi giá trị

Trong ML, hầu hết các cấu trúc dữ liệu là bất biến, có nghĩa là chúng không bao giờ thay đổi, được cập nhật hoặc được lưu Điều này đảm bảo thao tác của các thành phần sử dụng dữ liệu trong chương trình là thống nhất

Khi có nhu cầu tạo nên một giá trị mới, người ta thường xây dựng một cấu trúc dữ liệu mới thay vì cập nhật lại cấu trúc cũ Ví dụ sau minh họa cho điều này:

# val x = 1 (1)

# fun f y = x + y

# val x = x + 1 (2)

# val r = f 2 r <- 3

Trong ví dụ trên, biến x ở (1) và biến x ở (2) thực chất là hai biến khác

nhau (có cùng tên nhưng khác tầm nhìn) Do vậy, giá trị r sẽ nhận được là 3 thay vì 4

- Các tham chiếu có thể cập nhật

Ngoài các cấu trúc dữ liệu bất biến, ML còn cung cấp kiểu tham chiếu

Trang 25

cho những giá trị có thể cập nhật Ví dụ sau minh họa tính năng này

# val x = ref 1 (1)

# fun f y = !x + y

# val _ = x := !x + 1 (2)

# val r = f 2 r <- 4

- Các kiểu dữ liệu trừu tượng

ML hỗ trợ che giấu thông tin, vì thế có thể cài đặt loại dữ liệu mà phần

mô tả của nó được giấu đi bởi một giao tiếp

- Các mô đun tham số

Functor là mô đun chương trình ML nhận giao tiếp của mô đun khác

như một tham số Functor có thể được áp dụng cho bất kỳ mô đun nào phù hợp với giao tiếp đó Khả năng này tương tự như mẫu (template) của C++

Nhưng trong ML, functor có thể được hoàn toàn kiểm tra kiểu và được biên

dịch thành mã máy trước khi nó sử dụng các tham số Điều này tỏ ra ưu việt hơn mô đun của chương trình

Sau đây là một số ưu điểm của ngôn ngữ SML [Fu99]:

- Soát lỗi đơn giản

ML là một ngôn ngữ kiểu mạnh Theo đó, tất cả các lỗi về kiểu có thể được phát hiện tại thời điểm biên dịch Trong quá trình kiểm tra, sẽ không có chuyển đổi kiểu nào xảy ra nếu có sự không khớp Với việc quản lý bộ nhớ tự

động và hệ thống kiểu chặt chẽ, Standard ML là một ngôn ngữ an toàn Việc

kiểm tra các lỗi thực thi, đặc biệt là các lỗi ngữ nghĩa (ví dụ như truy cập quá giới hạn của mảng, ép một giá trị số nguyên thành một con trỏ không hợp lệ)

là rất khó khăn Cho nên, sử dụng một ngôn ngữ an toàn sẽ làm cho việc phát triển phần mềm đơn giản và đáng tin cậy hơn

- Bảo trì một cách đơn giản

ML hỗ trợ các kiểu đa hình và có một hệ thống mô đun tiên tiến

Trang 26

Những đặc điểm này cung cấp sự linh hoạt trong việc định nghĩa giao tiếp hàm và làm cho chương trình bảo trì một cách đơn giản hơn Thông thường, các chương trình ML là tin cậy, di động và có khả năng sử dụng lại hơn các chương trình C

- Đơn giản, dễ hiểu

SML là một ngôn ngữ lập trình hàm nhưng không phải là một ngôn

ngữ lập trình hàm thuần túy Ngoài các giá trị không thay đổi (immutable)

của một ngôn ngữ lập trình hàm thông thường làm cho chương trình đơn giản

và dễ lần vết; SML cũng có những đối tượng có thể thay đổi (mutable object), để khi cần thiết, các biến cũng có thể được thực hiện đọc và ghi Với hai tính chất này, SML có thể là một ngôn ngữ lập trình phù hợp với các dự

án thực tế

- Cài đặt hiệu quả

Với các đặc điểm như: đa hình, có các mô đun tham số và thao tác quản lý bộ nhớ tự động, việc biên dịch ML thành mã máy chỉ yêu cầu các kỹ thuật thông thường Hiện nay, có nhiều bộ biên dịch Standard ML sinh mã

máy có hiệu quả như Standard ML of New Jersey và Harlequin ML Works

2.3 Ngôn ngữ SML#

SML# là một ngôn ngữ lập trình mới thuộc lớp ngôn ngữ Standard ML đang được phát triển tại Viện nghiên cứu truyền thông thuộc trường đại học Tohoku phối hợp với liên hợp Sanpu Koubou của Nhật Bản Mục đích thiết

kế của ngôn ngữ này là cung cấp một số mở rộng ở mức độ vừa phải nhưng thiết thực trong việc bảo trì tính tương hợp của Standard ML

Vào năm 1993, Atsushi Ohori mở rộng bộ biên dịch của ngôn ngữ

Standard ML of New Jersey với bản ghi đa hình (record polymorphism) và đặt tên thực nghiệm ban đầu là SML# of Kansai Tên này tượng trưng cho

Trang 27

bản ghi đa hình của SML

Để hỗ trợ đa hình bản ghi, khả năng tương tác mạnh và những tính năng thực tế quan trọng khác, nhóm phát triển đã quyết định xây dựng một ngôn ngữ kiểu SML hỗn tạp và bắt đầu dự án bộ biên dịch SML# tại Viện

khoa học và công nghệ tiên tiến của Nhật (Japan Advanced Institute of Science and Technology – JAIST) Vào tháng 3 năm 2005, dự án này được chuyển cho Viện nghiên cứu truyền thông của đại học Tohoku Dự án SML#

đã được sự hỗ trợ của các Bộ giáo dục và đào tạo, văn hóa, thể dục, khoa học

và công nghệ của Nhật Bản

Ngôn ngữ lập trình SML# hỗ trợ các đặc điểm mới sau:

- Tương tác trực tiếp với C

Một trong những mục tiêu của SML# là đạt được sự tương tác cao với

C và những ngôn ngữ khác Hiện nay, các ngôn ngữ ML – đầy đủ như SML/NJ, Mlton, hay Ocaml chỉ tương tác “yếu” với các ngôn ngữ ngoại lai

do sự khác biệt về biểu diễn dữ liệu Hầu hết các ngôn ngữ này sử dụng cách

biểu diễn đánh dấu (tagged representation) cho các số nguyên, biểu diễn đóng gói (boxed representation) cho số thực và các kiểu dữ liệu phức hợp

Chính vì đặc điểm này, các chương trình ML không thể liên kết trực tiếp với các thư viện của C Việc chuyển giao hoặc nhận giá trị với C không thể thực hiện trực tiếp mà phải qua một quá trình chuyển đổi dữ liệu phức tạp và tốn kém

SML# giải quyết vấn đề này mà không cần sử dụng đến kỹ thuật phân tích toàn bộ chương trình hoặc sử dụng kỹ thuật xem xét kiểu tại thời điểm thực thi Trong SML#, các số nguyên và các số thực dấu phẩy động được biểu diễn một cách tự nhiên giống trong C và các ngôn ngữ lập trình chỉ thị khác Không những thế, kiến trúc heap của SML# không phụ thuộc vào hệ thống kiểu hoặc bộ biên dịch Vì thế, nó có thể dễ dàng trao đổi dữ liệu với

Trang 28

các ngôn ngữ lập trình này Những đặc điểm trên đây làm cho SML# có tính tương tác mạnh

Hiện tại, với SML#, người lập trình có thể dùng các hàm bên ngoài như một hàm thông thường bằng cách liên kết chúng qua thư viện liên kết

động Để làm được điều này, SML# cung cấp thư viện FFI (Foreign Function Interface) với hai hàm: DynamicLink.dlopen và DynamicLink.dlsym Trong

đó:

- dlopen(path) mở tệp tương ứng với thư viện liên kết động được

chỉ ra trong đường dẫn path

- dlsym(ptr, s) tìm kiếm hàm có tên là s trong thư viện được trỏ bởi ptr và trả về con trỏ đến hàm này

Con trỏ hàm được đưa vào không gian tên của SML# bằng biểu thức:

exp _import : (t1, ,tn) -> t

Với exp là con trỏ tới hàm nhận n tham số có các kiểu lần lượt là t1, ,tn và trả về một kết quả thuộc kiểu t Kết quả hàm này được chuyển

thành một hàm SML# với kiểu tương ứng

# val lib = DynamicLink.dlopen “./library.so”

# val f = DynamicLink.dlsym (lib, “f”) : _import (int * int) -> int

Trang 29

val lib = DynamicLink.dlopen “./library.so”

val f = DynamicLink.dlsym (lib, “f”)

: _import (int -> int, int) -> int

- Liên kết tĩnh và động với các hàm của C với sự biên dịch độc lập

- Giao tiếp an toàn với Java thông qua bản ghi đa hình và

- record và set dựa trên giao tiếp với các cơ sở dữ liệu

- B ản ghi đa hình

SML# hỗ trợ bản ghi đa hình (điều này giúp toán tử lựa chọn của SML: #<label> trở thành đa hình đầy đủ) như trong ví dụ sau:

# fun f x = #name x;

val f = fn : ['a, 'b#{name:'a} 'b -> 'a]

# f {name = "Joe", age = 21};

val it = "Joe" : string

trong đó:

[‘a, ‘b#{name:’a}.’b -> ‘a]

xác định f là hàm đa hình hoạt động trên bất kỳ bản ghi nào có chứa một

Trang 30

trường có kiểu ‘a và trả về kết quả thuộc kiểu ‘a

SML# cũng cho phép việc cập nhật đa hình bởi cú pháp:

exp # {label1 = exp1, , labeln = expn}

điều này tạo ra một bản ghi mới từ bản ghi exp bằng cách thay đổi giá trị biểu thức label i thành exp i

Chúng ta thường bắt gặp phép cập nhật đa hình trong nhiều trường hợp, chẳng hạn như ví dụ sau:

# fun f x y = (x, ref y);

val f = fn : ['a 'a -> ['b 'b -> 'a * 'b ref]]

# f 1;

val it = fn : ['a 'a -> int * 'a ref]

Thêm vào đó, tính đa hình bậc nhất làm cho việc dịch kiểu trực tiếp của SML# và mã biên dịch trở nên hiệu quả hơn do đã loại bỏ kiểu mô tả Phần mở rộng này không yêu cầu bất kỳ sự thay đổi nào về cú pháp ngôn ngữ

SML# được thiết kế cẩn thận và là một mở rộng thận trọng của Standard ML Bộ biên dịch SML# có thể biên dịch bất kỳ chương trình nào của Standard ML Ngoài ra, ngôn ngữ này cũng hỗ trợ các thư viện chuẩn

Hiện tại phiên bản phát hành của SML# là 0.20 được tồn tại dưới các

Trang 31

hình thức:

- Gói mã nguồn (có thể được dịch trên: linux, solaris, cygwin),

- toàn bộ gói nhị phân Mac OS Intel/PowerPC,

- và phần cài đặt trên Windows

Trang 32

CHƯƠNG 3 KỸ THUẬT LẬP TRÌNH TƯƠNG TRANH

Trong chương này, Luận văn khái quát về kỹ thuật lập trình tương tranh Ở đây chú trọng các vấn đề cơ bản của kỹ thuật lập trình này Sau đó, xem xét, phân tích các mô hình và cách tiếp cận lập trình tương tranh trên một số ngôn ngữ hỗ trợ như: Java, Erlang, CML và Manticore từ đó rút ra các đặc trưng và giải pháp của từng mô hình

3.1 Lập trình tương tranh (Concurrent Programming)

Các ngôn ngữ lập trình tuần tự và đơn luồng không thực sự thích hợp cho việc mô phỏng nhiều nguồn đầu vào Tuy nhiên, trên thực tế, yêu cầu phần mềm đôi lúc buộc các hoạt động phải được xảy ra cùng một thời điểm Chẳng hạn như: một trình duyệt Web phải đồng thời tiếp nhận thao tác từ giao tiếp với người dùng; đọc, hiển thị các trang và thực thi những chương

trình nhúng bên trong như JavaScript hoặc các ngôn ngữ khác Chính vì sự

không tương thích này làm cho việc thiết kế phần mềm đáp ứng được yêu cầu

đó trở nên khó khăn hơn

Tính đồng thời (Concurrency) là một điểm mạnh cho phép xây dựng

các ứng dụng thực hiện nhiều công việc ở cùng thời điểm Mặc dù vậy, nó cũng làm cho việc viết các chương trình chính xác trở nên khó khăn hơn Các thao tác của một chương trình đồng thời là không định trước nên người lập trình cần phải dự kiến tất cả những trình tự thực thi có thể của các luồng để đảm bảo hoạt động của chương trình là đúng đắn

Theo Richard H Carver và Kuo-Chung Tai thì: “Lập trình tương tranh

là một kỹ thuật lập trình tạo ra các chương trình gồm hai hoặc nhiều hơn một luồng (thread) được thực hiện đồng thời cho một công việc nào đó Một chương trình như vậy được gọi là chương trình đồng thời (Concurrent program)” [CTa06]

Trang 33

Những chương trình dạng này tồn tại ở hai hình thức, đó là: nhiều tiến trình nhưng đơn luồng và một tiến trình nhưng đa luồng [SSe03] Ở đó, hệ điều hành sẽ chịu trách nhiệm quản lý việc sử dụng tài nguyên và cho phép các luồng chia sẻ bộ xử lý Tất cả những chương trình đồng thời đều đưa ra những hoạt động không được định trước Các chương trình này có thể tồn tại

ở hai dạng thông dụng sau [JCM03]:

- Đa lập trình (Multiprogramming) Một bộ xử lý vật lý có thể thực

hiện nhiều tiến trình đồng thời bằng cách xen lẫn các bước của tiến trình này vào các bước của một tiến trình khác Mỗi tiến trình độc lập sẽ được xử lý tuần tự, tuy nhiên các hoạt động của một tiến trình như thế có thể được xảy ra giữa hai bước của một tiến trình khác

- Đa xử lý (Multiprocessing) Hai hay nhiều bộ xử lý có thể chia sẻ

bộ nhớ hoặc được kết nối với nhau qua mạng Các tiến trình của một bộ xử lý này có thể tương tác với các tiến trình được thực thi đồng thời trên bộ xử lý khác

Ngày nay, đối với các ngôn ngữ lập trình hiện đại, như Java và C#, việc sử dụng đa luồng cho lập trình tương tranh đã thể hiện nhiều ưu việt và mang lại nhiều lợi ích quan trọng, đó là [Sun94]:

- Tăng khả năng đáp ứng của ứng dụng

Các hoạt động không phụ thuộc lẫn nhau trong bất kỳ chương trình nào đều có thể được tiến hành trên các luồng khác nhau nhằm làm tăng hiệu năng cho ứng dụng

- Sử dụng bộ xử lý hiệu quả hơn

Việc sử dụng bộ xử lý hiệu quả hơn khi chương trình cho phép nhiều công việc được thực hiện đồng thời với các tốc độ khác nhau Ngoài ra, với việc sử dụng các luồng, nếu tăng thêm số lượng bộ xử lý thì hiệu quả của ứng

Trang 34

dụng cũng được tăng lên một cách trong suốt mà không cần phải thay đổi lại chương trình

- Cải tiến cấu trúc của chương trình

Cấu trúc của chương trình hiệu quả hơn khi có nhiều thành phần thực thi độc lập Các chương trình đa luồng có thể thích nghi với nhiều yêu cầu của người dùng hơn so với các chương trình đơn luồng

- Sử dụng ít tài nguyên hệ thống hơn

Với đa tiến trình, các chương trình có thể sử dụng hai hoặc nhiều tiến

trình để truy cập đến dữ liệu dùng chung thông qua bộ nhớ chia sẻ (shared memory) Tuy nhiên, sự phân tách vốn có giữa các tiến trình đòi hỏi sự cố gắng rất nhiều của người lập trình để tạo ra sự giao tiếp và thực hiện việc đồng bộ hóa các hoạt động giữa chúng

Việc xử lý tương tranh quan trọng xuất phát từ các nguyên nhân chủ yếu sau đây:

- Một là, nó cho phép nhiều công việc được xử lý với các tốc độ khác

nhau Ví dụ như, đa lập trình cho phép một chương trình thực thi công việc của mình trong lúc chương trình khác lại phải chờ kết quả Điều đó làm cho việc sử dụng bộ xử lý trở nên hiệu quả hơn

- Hai là, xử lý tương tranh mang lại một tư tưởng lập trình quan trọng

đó là việc tách biệt giữa các công việc có thể tiến hành một cách độc lập Chẳng hạn như: công việc của thành phần giao tiếp với người dùng và công việc của thành phần xử lý dữ liệu bên trong của ứng dụng

- Ba là, đa xử lý tạo tiền đề cho việc xử lý nguyên thủy trở nên mạnh

mẽ hơn trong việc giải quyết vấn đề tính toán Ngoài ra, nó còn giúp khai phá nhiều vấn đề, ví dụ như việc giải quyết độ tin cậy trong giao tiếp mạng và khả

Trang 35

năng hoạt động của bộ xử lý trong trường hợp các bộ xử lý khác bị hỏng Điều quan trọng là việc quản lý, điều phối hoạt động giữa các thành phần của một chương trình được thực hiện trên cùng một bộ xử lý hoặc trên các bộ xử

3.2 Một số vấn đề về tương tranh

3.2.1 Vấn đề về tiến trình và luồng

Khi thực hiện một chương trình, hệ điều hành tạo ra một tiến trình chứa phần mã và dữ liệu của chương trình đó và quản lý nó cho đến khi

chương trình này kết thúc [CTa06] Các tiến trình người dùng (user process)

được tạo từ những chương trình của người dùng, các tiến trình hệ thống

(system process) được tạo bởi các chương trình của hệ thống Hệ điều hành sẽ chịu trách nhiệm cấp phát không gian địa chỉ với phần ngăn xếp (stack), heap

và thanh ghi lệnh riêng cho chúng Các ngôn ngữ lập trình có thể định ra các tiến trình trên cùng không gian địa chỉ của hệ điều hành, nhưng chúng lại được bảo vệ bởi đặc trưng ngôn ngữ lập trình đó

Một tiến trình người dùng có riêng một không gian địa chỉ logic so với những tiến trình khác và tách biệt với không gian của các tiến trình hệ thống

(được gọi là kernel space) Điều này có nghĩa là hai tiến trình có thể tham chiếu cùng không gian địa chỉ logic, nhưng những địa chỉ này phải được ánh

Trang 36

xạ tới các phần bộ nhớ vật lý khác nhau Vì thế, các tiến trình không chia sẻ

về bộ nhớ, trừ phi chúng có một sự sắp đặt với hệ điều hành

Các hệ điều hành đa xử lý (multiprocessing operating system) cho

phép thực hiện đồng thời nhiều chương trình và chịu trách nhiệm cấp phát tài nguyên cho các tiến trình Những tài nguyên được dùng chung bao gồm: bộ nhớ, thiết bị ngoại vị và CPU Mục đích của hệ điều hành đa xử lý là tối đa hóa hoạt động của CPU bằng việc thực hiện một vài tiến trình tại bất kỳ thời điểm nào

Trong một tiến trình, chương trình thực thi đòi hỏi việc khởi tạo và bảo trì một khối lượng lớn thông tin như [CTa06]:

- Trạng thái của tiến trình (sẵn sàng, thực thi, chờ hoặc dừng);

- Bộ đếm chương trình (program counter) chứa địa chỉ lệnh tiếp theo

cho tiến trình đó;

- Các giá trị thanh ghi CPU;

- Thông tin quản lý bộ nhớ (page table và swap file) mô tả về file và

các yêu cầu input/output (I/O)

Thực thể luồng (thread) là thành phần thực thi tuần tự của một công

việc nào đó Một số công việc là hoàn toàn độc lập nhưng một số công việc khác lại cần phải phối hợp với nhau Ở đây, có một thành phần khác được gọi

là bộ lập lịch (scheduler) có nhiệm vụ lựa chọn công việc nào (hoặc các công

việc nào trên máy tính có nhiều CPU) sẽ được thực hiện

Luồng là đơn vị điều khiển bên trong một tiến trình Mỗi tiến trình có thể có một hoặc nhiều luồng (Hình 3.1) Tiến trình của chương trình bắt đầu

bởi một luồng, gọi là main thread, để thực hiện hàm “main” trong chương trình Với chương trình có nhiều luồng, main thread lại tạo ra các luồng để

Trang 37

thực thi những hàm khác Những luồng này có thể tạo nhiều luồng khác và tiếp tục như vậy Các luồng được tạo bởi sự hỗ trợ của ngôn ngữ lập trình hoặc các hàm được cung cấp bởi giao tiếp lập trình – API

Hình 3.1 Các m ô hình kết hợp giữa tiến trình và luồng

Mỗi luồng có một ngăn xếp và phần copy của các thanh ghi CPU, bao

gồm: con trỏ ngăn xếp (stack pointer) và bộ đếm chương trình (program counter) để cùng mô tả trạng thái thực hiện của luồng đó Tuy nhiên, các luồng trong cùng một tiến trình chia sẻ chung phần dữ liệu, mã, các tài nguyên và không gian địa chỉ của tiến trình đó (Hình 3.2) Ngoài ra, các thông tin trạng thái của tiến trình cũng được chia sẻ bởi các luồng trong chương trình Hệ điều hành sẽ quyết định phân phát CPU cho các tiến trình

Trang 38

Hình 3.2 Mô hình tiến trình đơn luồng và tiến trình đa luồng

3.2.2 Vấn đề thực thi

Mỗi tiến trình chứa một hoặc nhiều luồng Trong một số hệ thống, hệ điều hành lựa chọn một tiến trình để thực thi và tiến trình đó lại lựa chọn một luồng trong nó để thực hiện Một cách khác, các luồng được lập lịch trực tiếp bởi hệ điều hành Tại thời điểm nào đó, một vài luồng có thể chưa sẵn sàng,

ví dụ như: chúng đang chờ yêu cầu input/output (I/O) được hoàn tất Khi đó,

chính sách lập lịch sẽ quyết định cho một luồng sẵn sàng được thực hiện Thông thường, mỗi luồng như vậy nhận được một khoảng thời gian của CPU

(gọi là time slice) Trong trường hợp có nhiều CPU thì nhiều luồng có thể

được thực hiện đồng thời Nếu máy tính chỉ có một CPU, các luồng cần phải được chuyển đổi qua lại và có thể không nhận được khoảng thời gian bằng nhau Vì thế, một số luồng có thể thực hiện nhanh hơn so với các luồng khác

Chính sách lập lịch cũng có thể xem xét độ ưu tiên và loại hình xử lý của luồng để thực hiện sự ưu tiên giữa luồng này so với luồng khác Thao tác

register

stack register

Tiến trình đơn luồng

Chia sẻ chung cho các luồng

Trang 39

chuyển CPU từ một tiến trình (hoặc một luồng) sang một tiến trình (hoặc một

luồng) khác được gọi là chuyển đổi ngữ cảnh (context switch) Trong quá

trình này, cần phải lưu lại trạng thái của tiến trình (luồng) hiện tại và lấy trạng thái của tiến trình (luồng) mới

3.2.3 Vấn đề điều phối hoạt động

Để điều phối hoạt động của các tiến trình, thông thường, hệ điều hành

sử dụng các cơ chế truyền thống như: Lock, Semaphore và Monitor [JCM03]

- Lock là một hình thức chia sẻ biến (shared variable) để xác định tài

nguyên được khóa hay không khóa Khi một tiến trình khóa một tài nguyên thì những tiến trình khác không thể truy cập đến tài nguyên đó Vấn đề quan trọng của cơ chế khóa là thao tác kiểm tra và đặt khóa buộc phải là nguyên tố

- Cấu trúc Semaphore là một tập hợp các tiến trình ở trạng thái chờ đợi

để truy cập đến một tài nguyên chia sẻ Các tiến trình sử dụng semaphore phải chờ (wait) cho đến khi được phép truy cập tài nguyên và phải thông báo (signal) khi việc truy cập đã hoàn tất Cả hai hành động trên đều là các thao tác nguyên tố và có thể được cài đặt bởi hệ điều hành (hoặc phần thực thi của ngôn ngữ lập trình) sử dụng cơ chế khóa

- Thành phần Monitor tập hợp sự đồng bộ hóa các thao tác đối với dữ liệu Nó sẽ ước lượng sự cần thiết cho tiến trình thực hiện hai thao tác wait và signal (ở trên)

Các tác vụ có tính độc lập tương đối với nhau, nhưng chúng có thể tương tác với bộ lập lịch Ví dụ như, nếu tác vụ chắc chắn không còn công việc nào thì có thể yêu cầu bộ lập lịch đặt nó trạng thái ngủ để sử dụng CPU cho những tác vụ khác Một hình thức khác là thay đổi độ ưu tiên của tác vụ Tất cả những hành động trên đều sử dụng luồng để định ra tác vụ đặc thù Vì thế, luồng là một thực thể xác định, được sử dụng khi tác vụ cần tác động với

Trang 40

sẽ rất cần thiết để chặn các hoạt động của luồng khác ảnh hưởng đến luồng đang hoạt động Nếu không có thành phần điều phối, tất cả các thao tác của luồng này có thể hủy bỏ các thao tác của luồng khác và làm cho trạng thái của dữ liệu không còn ý nghĩa đối với chương trình

3.2.4 Vấn đề tương tác

Về việc tương tác giữa các tác vụ, có hai cách tiếp cận phổ biến, đó là:

- Truyền thông điệp

Tất cả các dữ liệu của các tác vụ độc lập với nhau và có một cơ chế đặc biệt cho việc tương tác giữa các tác vụ Ví dụ như: ở một trò chơi, trong đó một tác vụ có thể phát hiện ra dấu vết di chuyển của chuột và từ đó tính toán được hình chữ nhật giữa vị trí cũ và mới của nó Tiếp theo, tác vụ này báo hiệu cho tác vụ vẽ màn hình cập nhật lại vùng này trong lúc con trỏ chuột sẽ được vẽ ở một vị trí khác Do các tác vụ gửi thông điệp qua lại với nhau cho nên hình thức này được gọi là truyền thông điệp (message passing) Khi tác

vụ gửi một thông điệp, thành phần phân phát sao chép tất cả các dữ liệu cần thiết từ bộ nhớ địa phương vào bộ đệm Sau đó, tác vụ nhận có thể sao chép

dữ liệu này vào bộ nhớ địa phương của nó

Cách tiếp cận trên cho thấy việc tương tác của các tác vụ cần thêm một thực thể mới, đó là thành phần nhận nhiệm vụ phân phát các thông điệp Ở đây, vấn đề toàn vẹn dữ liệu cũng được giải quyết qua việc sao chép dữ liệu hai lần Khi dữ liệu được sao chép vào bộ đệm thì không thành phần nào thay

Ngày đăng: 23/02/2021, 15:21

TỪ KHÓA LIÊN QUAN

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

🧩 Sản phẩm bạn có thể quan tâm

w