Cấu trúc của nhà triếtgia i được thể hiện trong Hình 2 Mặc dù giải pháp này đảm bảo rằng không có hai người hàng xóm nào đang ăn cùng một lúc, nhưng nó vẫn phải bị từ chối vì nó có thể
Trang 1Nguyên lý hệ điều hành – IT3070
Bài tập lớn – Báo cáo cá nhân
Trang 21 Người sản xuất – người tiêu thụ (Producer-Consumer)
2 Triết gia ăn tối (Dining Philosophers)
Figure 1: Tình huống triết gia ăn tối (5 triết gia quanh bàn tròn)
Giả sử cho rằng năm nhà triết gia dành cả cuộc đời suy nghĩ và ăn uống Những nhà triết gia chia sẻ một bàn tròn được bao quanh bởi năm chiếc ghế, mỗi chiếc thuộc về một nhà triết gia (Hình 1) Ở giữa bàn là một tô cơm, và trên bàn có năm chiếc đũa đơn (chỉ có một đầu) Khi một nhà triết gia suy nghĩ, cô ta không tương tác với đồng nghiệp của mình Đôi khi, một nhà triết gia đói và cố gắng nhặt lấy hai đôi đũa gần nhất với cô ta (đôi đũa ở giữa cô và hai người hàng xóm bên trái và phải của cô) Một nhà triết gia chỉ có thể nhặt lên một đôi đũa một lần Rõ ràng, cô ta không thể nhặt lên một đôi đũa mà đã ở trong tay của một người hàng xóm Khi một
Trang 3nhà triết gia đói có cả hai đôi đũa cùng một lúc, cô ta ăn mà không buông đôi đũa Khi cô ta đã ăn xong, cô ta đặt xuống cả hai đôi đũa
và bắt đầu suy nghĩ lại
Vấn đề của những nhà triết gia trong việc ăn uống được coi là một vấn đề đồng bộ hóa kinh điển không phải vì nó quan trọng về mặt thực tế hay vì các nhà khoa học máy tính không ưa những nhà triết gia, mà vì đó là một ví dụ của một loại lớn vấn đề kiểm soát đồng thời Nó là một biểu diễn đơn giản về sự cần thiết phải phân phối nhiều tài nguyên giữa nhiều quy trình một cách không gây bế tắc
và gây tình trạng đói
Một giải pháp đơn giản là biểu diễn mỗi que đũa bằng một semaphore Một nhà triết gia cố gắng nắm lấy một que đũa bằng cách thực hiện một hoạt động wait() trên semaphore đó Cô ta giải phóng đôi đũa bằng cách thực hiện một hoạt động signal() trên các semaphore tương ứng
Figure 2: Cấu trúc nhà triết học i
Trang 4 Như vậy, dữ liệu được chia sẻ là semaphore chopstick[5], với tất cả các phần tử của chopstick được khởi tạo là 1 Cấu trúc của nhà triết gia i được thể hiện trong Hình 2
Mặc dù giải pháp này đảm bảo rằng không có hai người hàng xóm nào đang ăn cùng một lúc, nhưng nó vẫn phải bị từ chối vì nó có thể tạo ra tình trạng bế tắc Giả sử rằng tất cả năm nhà triết gia đều đói cùng một lúc và mỗi người nắm lấy que đũa bên trái của mình Tất cả các phần tử của chopstick sẽ giờ đây đều bằng 0 Khi mỗi nhà triết gia cố gắng nắm lấy que đũa bên phải của mình, cô ấy sẽ
bị chờ đợi mãi mãi
Một số giải pháp khả thi để giải quyết vấn đề bế tắc bao gồm: + Cho phép tối đa bốn nhà triết gia ngồi cùng một lúc tại bàn + Cho phép một nhà triết gia chỉ nắm lấy que đũa nếu cả hai que đũa đều khả dụng (để làm điều này, cô ấy phải nắm lấy chúng trong một phần quan trọng)
+ Sử dụng một giải pháp không đối xứng - nghĩa là một nhà triết gia có số thứ tự lẻ nắm lấy que đũa bên trái trước, sau đó lấy que đũa bên phải; trong khi một nhà triết gia có số thứ tự chẵn nắm lấy que đũa bên phải trước và sau đó lấy que đũa bên trái
2.2 Dining-Philosophers Solution Using Monitors
o Tiếp theo, chúng ta minh họa các khái niệm của monitor bằng cách trình bày một giải pháp không gây chết lẻo cho vấn đề của những nhà triết gia Giải pháp này đặt ra hạn chế là một nhà triết gia chỉ có thể nắm lấy que đũa của mình nếu cả hai que đũa đều khả dụng Để mã hóa giải pháp này, chúng ta cần phải phân biệt giữa ba trạng thái mà một nhà triết gia có thể
ở Vì mục đích này, chúng ta giới thiệu cấu trúc dữ liệu sau đây:
enum {THINKING, HUNGRY, EATING} state[5];
o Nhà triết gia i có thể thiết lập biến state[i] = EATING chỉ khi
cả hai người hàng xóm của cô ta không đang ăn:
Trang 5o (state[(i+4) % 5] != EATING) và (state[(i+1) % 5] != EATING)
Trang 6Figure 3: Một giải pháp Monitor cho vấn đề triết gia ăn uống.
Trang 7 Ngoài việc khai báo state[5], sự hiện diện của condition self[5] cũng là cần thiết Điều này cho phép nhà triết gia i tự nguyện trì hoãn khi cô ấy đang đói nhưng không thể có được những que đũa cần thiết
Bây giờ, chúng ta hãy mô tả giải pháp cho vấn đề của những nhà triết gia sử dụng monitor DiningPhilosophers Mỗi nhà triết gia, trước khi bắt đầu ăn, cần gọi phương thức pickup() Hành động này có thể dẫn đến việc tạm dừng quá trình của nhà triết gia Sau khi hoàn thành thành công phương thức pickup(), nhà triết gia có thể bắt đầu ăn Sau đó, nhà triết gia gọi phương thức putdown() Do đó, nhà triết gia i phải gọi các phương thức pickup() và putdown() theo thứ tự như sau:
DiningPhilosophers.pickup(i);
// eat
DiningPhilosophers.putdown(i);
Rõ ràng rằng giải pháp này đảm bảo không có hai nhà triết gia hàng xóm nào đang ăn cùng một lúc và không có tình trạng chết lẻo nào sẽ xảy ra Tuy nhiên, quan trọng lưu ý rằng một nhà triết gia vẫn có thể chết đói
3 Người đọc và biên tập viên (Reader-Writers)
Trong bài toán đọc-viết cơ sở dữ liệu, có nhiều quy trình cùng truy cập vào dữ liệu chung Một số trong số này chỉ muốn đọc cơ sở dữ liệu, trong khi những quy trình khác muốn cập nhật (đọc và ghi) cơ
sở dữ liệu Chúng ta phân biệt giữa hai loại quy trình này bằng cách gọi những quy trình đọc là "readers" và những quy trình viết
là "writers" Hiển nhiên, nếu hai readers truy cập dữ liệu chia sẻ
Trang 8cùng một lúc, không có ảnh hưởng tiêu cực nào xảy ra Tuy nhiên, nếu một writer và một quy trình khác (đọc hoặc viết) truy cập vào
cơ sở dữ liệu cùng một lúc, sự rối loạn có thể xảy ra
Để đảm bảo những khó khăn này không xảy ra, chúng ta yêu cầu rằng writers phải có quyền truy cập độc quyền vào cơ sở dữ liệu chia sẻ khi đang ghi vào cơ sở dữ liệu Vấn đề đồng bộ hóa này được gọi là bài toán đọc-viết (readers–writers problem) Có nhiều biến thể của bài toán này, tất cả đều liên quan đến ưu tiên Bài toán đọc-viết đơn giản nhất, được gọi là bài toán đọc-viết thứ nhất, yêu cầu rằng không có reader nào được giữ đợi trừ khi một writer đã có quyền truy cập vào đối tượng chia sẻ Nói cách khác, không có reader nào nên chờ đợi mà không thể truy cập chỉ vì có một writer đang chờ đợi Bài toán đọc-viết thứ hai yêu cầu rằng, khi một writer sẵn sàng, writer đó phải thực hiện ghi càng sớm càng tốt Nói cách khác, nếu một writer đang chờ để truy cập đối tượng, không có reader mới nào được phép bắt đầu đọc
Một giải pháp cho bài toán đọc-viết thứ nhất có thể dẫn đến tình trạng đói Trong trường hợp đầu tiên, writers có thể bị đói; trong trường hợp thứ hai, readers có thể bị đói Vì lý do này, các biến thể khác của bài toán đã được đề xuất để giải quyết tình trạng đói Dưới đây là một giải pháp cho bài toán đọc-viết thứ nhất
o semaphore rw mutex = 1;
o semaphore mutex = 1;
o int read count = 0;
o Các semaphores mutex và rw mutex được khởi tạo bằng 1; số lần đọc là được khởi tạo bằng 0 Semaphore Mutex rw được dùng chung cho cả quá trình đọc và ghi
Trang 9 Figure 4: Cấu trúc quá trình ghi
Figure 5: Cấu trúc quá trình đọc
Trang 10 Đoạn mã này sử dụng semaphore mutex để đảm bảo sự loại trừ lẫn nhau khi biến đếm đọc (read count) được cập nhật Biến read count theo dõi số lượng quy trình hiện đang đọc đối tượng Semaphore
rw mutex hoạt động như một semaphore loại trừ lẫn nhau cho các writers Nó cũng được sử dụng bởi người đọc đầu tiên hoặc người đọc cuối cùng khi nhập hoặc thoát khỏi phần quan trọng (critical section) Nó không được sử dụng bởi người đọc nào đó khi nhập hoặc thoát trong khi người đọc khác đang ở trong phần quan trọng của mình
Mã cho một quy trình writer được thể hiện trong Hình 1; mã cho một quy trình reader được thể hiện trong Hình 2 Lưu ý rằng, nếu một writer đang ở trong phần quan trọng và có n readers đang chờ, thì một reader được đưa vào hàng đợi trên rw mutex và n - 1 readers được đưa vào hàng đợi trên mutex Cũng lưu ý rằng, khi một writer thực hiện signal(rw mutex), chúng ta có thể tiếp tục thực hiện của độc giả đang chờ hoặc một writer đang chờ duy nhất Lựa chọn này được thực hiện bởi bộ lập lịch (scheduler)
Vấn đề đọc-viết và các giải pháp của nó đã được tổng quát hóa để cung cấp khóa đọc-viết cho một số hệ thống Để có được một khóa đọc-viết, quy trình phải chỉ định chế độ của khóa: là chế độ đọc hay chế độ viết Khi một quy trình chỉ muốn đọc dữ liệu được chia
sẻ, nó yêu cầu khóa đọc-viết ở chế độ đọc Một quy trình muốn sửa đổi dữ liệu được chia sẻ phải yêu cầu khóa ở chế độ viết Nhiều quy trình được phép cùng lúc có được một khóa đọc-viết ở chế độ đọc, nhưng chỉ có một quy trình được phép có được khóa để viết,
vì việc đảm bảo quyền truy cập độc quyền là cần thiết cho những quy trình viết
Trang 11 Khóa đọc-viết (reader-writer locks) thường được sử dụng hiệu quả nhất trong các tình huống sau:
1 Ứng dụng có thể dễ dàng xác định được quy trình nào chỉ đọc dữ liệu chia sẻ và quy trình nào chỉ viết dữ liệu chia sẻ.
Trong môi trường này, khóa đọc-viết cho phép quy trình đọc có thể cùng lúc truy cập vào dữ liệu chia sẻ mà không gặp khó khăn với quy trình viết, và ngược lại Điều này giúp tối ưu hóa hiệu suất và giảm rủi ro xung đột
2 Ứng dụng có số lượng độc giả nhiều hơn so với số lượng người viết.
o Do đối với khóa đọc-viết, thường cần nhiều công đoạn hơn
để thiết lập so với semaphores hoặc khóa loại loại trừ lẫn nhau Tăng sự đồng thời bằng cách cho phép nhiều độc giả cùng lúc truy cập vào dữ liệu chia sẻ có thể bù đắp cho chi phí cần thiết để thiết lập khóa đọc-viết
Sự lựa chọn giữa khóa đọc-viết và các phương tiện đồng bộ hóa khác (như semaphore hoặc khóa loại loại trừ lẫn nhau) thường phụ thuộc vào đặc điểm của ứng dụng cụ thể và mức độ cân bằng giữa việc đọc và ghi dữ liệu chia sẻ Trong những trường hợp nơi có nhiều độc giả và ít người viết, khóa đọc-viết có thể là một giải pháp hiệu quả để tối ưu hóa sự đồng thời và giảm thiểu thời gian chờ đợi của độc giả
4 Người thợ cắt tóc ngủ gật (Sleeping Barber)
Trang 125 Bathroom Problem
6 Đồng bộ theo Barriers
7 Bài toán tạo phân tử H O2