Diễn Đàn Tin Học » Tutorial Room » Lập trình » Software Engineering » Craftsman-Truyện dài nhiều tập Craftsman 10 - Iterations Unbound Nếu có một vòng xoay động, bạn không muốn đổi tập họp hiện có, nhất là từ một thread khác. Phải chăng "design patterns" là giải pháp cho bạn?
Những vòng xoay vô giới hạn (iterations unbound)
Nếu có một vòng xoay động, bạn không muốn đổi tập họp hiện có, nhất là từ một
thread khác. Phải chăng "design patterns" là giải pháp cho bạn?
Phần 10.
Robert C. Martin
Mỗi tháng tôi lại dùng điểm tâm một lần ở đài quan sát. Ðây là điều ngoại hạng
cho tay học việc như tôi, tôi khoái ăn dưới vòm trời mở rộng. Trong lúc ăn, tôi
ngẫm nghĩ về chuyện thread treo lủng lẳng được chúng tôi giải quyết ngày hôm
qua. Chúng tôi sửa cái serverThread nhưng lại để trọn
bộ các thread thuộc serviceRunnable treo tòng teng.
Tôi biết thế nào Jerry cũng muốn sửa mấy cái ấy cho sớm.
Ðúng y như vậy, ngay khi tôi bước vào phòng làm việc, Jerry đã mang mấy cái test
case trên màn hình như sau:
|
|
public void testAllServersClosed()
throws Exception {
ss.serve(999, new
WaitThenClose());
Socket s1 = new Socket("localhost",
999);
Thread.sleep(20);
assertEquals(1,WaitThenClose.threadsActive);
ss.close();
assertEquals(0,
WaitThenClose.threadsActive);
} |
|
"Ông phải chắc ăn trọn bộ những cái SocketServers đóng
hết ngay khi trở lại từ bước đóng SockeService," tôi
nói.
"Tao muốn chắc ăn là mình không để cho mấy cái servers đó treo lủng lẵng như
thế," Jerry trả lời.
"Nhưng ông chỉ test nó với một server thôi mà," tôi đáp lại. "Bộ mình không cần
test với nhiều server hay sao?"
"Ðúng thế!" Jerry mỉm cười. "Nhưng hãy làm xong cái test này ngon lành cái đã."
"OK," tôi trả lời. "Tôi biết cách viết WaitThenClose ra
sao rồi."
|
|
class WaitThenClose implements
SocketServer {
public static int
threadsActive = 0;
public void serve(Socket s) {
threadsActive++;
delay();
threadsActive--;
}
private void delay() {
try {
Thread.sleep(100);
} catch (Interrupted Exception e) {
}
} |
|
Jerry gật gù trong lúc mã nguồn của tôi hiện ra trên màn hình; cái
WaitThenClose của tôi đúng y như gã dự tưởng. Tôi biên
dịch mã nguồn này và chạy mấy phần test, chúng hỏng như dự đoán:
1) testAllServersClosed AssertionFailedError:
expected:<0> but was:<1>
Jerry xoa tay và nói, "bây giờ hãy làm cho nó đạt đi." Gã với lấy bàn phím nhưng
tôi cản gã lại. "Tôi nghĩ là tôi có một ý kiến". Thế nên, trong khi Jerry quan
sát, tôi thay đổi đoạn mã như sau:
|
|
private LinkedList serverThreads = new
LinkedList();
public void serve(int port, SocketServer server) throws
Exception {
itsServer = server;
serverSocket = new ServerSocket(port);
serverThread = new Thread(
new Runnable() {
public void run() {
running =
true;
while
(running) {
try
{
Socket s = serverSocket.accept();
Thread serverThread = new Thread(new ServiceRunnable(s));
serverThreads.add(serverThread);
serverThread.start();
}
catch (IOException e) {
}
}
}
}
);
serverThread.start();
}
public void close() throws Exception {
if (running) {
running = false;
serverSocket.close();
serverThread.join();
for (Iterator i =
serverThreads.iterator(); i.hasNext();) {
Thread thread = (Thread)
i.next();
serverThreads.remove(thread);
thread.join();
}
} else {
serverSocket.close();
}
} |
|
Khi mã nguồn được biên dịch, Jerry nhăn nhó. "Vậy được không?" tôi hỏi.
"Hãy xem nào," gã trả lời. "Chạy thử cái test xem sao."
Khi chạy cái test, bị một lỗi khác thường:
1) testOneConnection java.util.ConcurrentModificationException
"Cái gì vậy?" tôi hỏi.
"Mày làm vỡ luật đó, Alphonse," Jerry nói. "Không bao giờ thêm hoặc bớt từ một
cái list trong khi mày có một vòng xoay động."
"Tất nhiên rồi!" tôi nói một cách ngượng ngùng. "Ok, nhưng chuyện này dễ sửa
thôi, bởi vì tôi không cần phải tháo bỏ cái cái thread từ list." Tôi bỏ dòng
remove và chạy đoạn test lại. "À! bây giờ thì nó
chạy."
Jerry gật đầu nhưng nhìn tôi chằm chặp một cách chờ đợi. "Gì hở?" tôi gào lên
sau nửa phút chịu đựng kiểu nhìn của gã. "Mày vẫn đang thay đổi cái list trong
khi vòng xoay ứng động," gã phán.
"Vậy sao?" tôi quả thật bối rối. "Chỉ có một nơi duy nhất cái list được thay
đổi, và đó là nơi thread được thêm vào trong running loop.
Làm sao nó được gọi trong khi vòng xoay động?"
"Có thể được," Jerry nói. "Cú gọi để tiếp nhận có thể ở tình trạng chực trở lại
ngay khi mày đi vào vòng xoay. Khi vòng xoay chặn một cú nối (join), phần tiếp
nhận sẽ trở lại và thêm một thread nữa vào list."
"OK, nhưng mình test chuyện đó được không?" tôi hỏi.
"Mình có thể làm được chuyện đó nhưng chẳng ích gì," Jerry trả lời. "Hoá ra ở
một nơi khác nơi mày sẽ thay đổi cái list trong khi vòng xoay mở ra."
"Có à?"
"Ừa, mày sắp sửa thêm nó vào đó," Jerry mỉm cười. Gã nói tiếp, "Có bao nhiêu
thread trong list đó vậy?"
"Cả lũ... eo ôi!" tôi vỗ trán. "Tôi nên bỏ cái thread ra khỏi list khi nó đã
hoàn thành công tác! không thì, các thread đã hoàn tất sẽ đeo tòng teng trong
list." Tôi vớ lấy bàn phím và thay đổi như sau:
|
|
class ServiceRunnable implements
Runnable {
private Socket itsSocket;
ServiceRunnable(Socket s) {
itsSocket = s;
}
public void run() {
try {
itsServer.serve(itsSocket);
serverThreads.remove(Thread.currentThread());
itsSocket.close();
} catch (IOException e) {
}
}
} |
|
"À há, bây giờ nó lại hỏng tiếp," tôi nói. "Ông nói đúng lắm - vài cái thread
hoàn tất trước khi vòng xoay chấm dứt. Cha chả, vòng xoay "ý kiến" với các cập
nhật liên đới quả là điều thật hay!"
"Ðúng thế," Jerry gật đầu. "Bây giờ để tao chỉ mà cách tao trị nó như thế nào."
|
|
public void close() throws Exception
{
if (running) {
running = false;
serverSocket.close();
serverThread.join();
while (serverThreads.size() >
0) {
Thread t =
(Thread)serverThreads.get(0);
serverThreads.remove(t);
t.join();
}
} else {
serverSocket.close();
}
} |
|
"Rồi!" Jerry nói. "Bây giờ thì mấy cái test hẳn phải đạt."
"Tôi biết rồi," tôi thốt ra. "Thay vì dùng vòng xoay, ông chỉ kéo phần tử thứ
nhất ra khỏi list và tiếp tục lặp lại cho đến khi list trống rỗng."
"Ðúng đó," Jerry trả lời. "Bằng cách đó, không vòng xoay nào mở ra quá lâu. Các
cú nối (joins) có thể mất thời gian, cho nên để vòng xoay mở quá lâu khi các
thread khác thay đổi list là điều không hay."
"Thế, mình xong việc rồi sao?" Jerry lắc đầu. "Không, vẫn còn hiểm nguy," gã
cảnh báo.
"Ý ông thế nào vậy?" tôi ré lên, thất vọng. "Chớ có sự cố gì nữa đây?"
"Alphonse, mỗi khi mày có một container bị nhiều thread thay đổi, rất có cơ hội
hai thread va nhau bên trong container. Một thread có thể thêm một phần tử
trong khi một thread khác lại xoá phần tử khác. Khi trường hợp này xảy ra,
container có thể bị hỏng và những chuyện kỳ quái có thể xảy ra."
"Vậy ý ông là mình nên đồng bộ hoá truy cập đến container?" tôi hỏi.
"Chính xác," Jerry trả lời. "Chúng ta cần nắm chắc không có thread nào khác có
thể truy cập container trong khi nó bị thay đổi."
"Ðơn giản thôi," tôi nói trong khi gom lại đoạn thêm và
hai đoạn bớt với biện thức đồng bộ
(serverThreads) {...}. Tôi chạy mấy cái tests và chúng đạt hết.
"Ðó là một cách," Jerry nói với nụ cười trên mặt, "nhưng nó hơi bị dễ dính lỗi.
Nếu có ai chỉnh sửa mã nguồn và đặt vào một cái add hay
remove, họ phải nhớ đặt phần đồng bộ hoá vào. Nếu họ
quên, những chuyện tồi tệ có thể xảy ra."
Tôi ngẫm nghĩ vấn đề ấy vài phút và xác định gã nói đúng - nếu chúng ta không
cần phải gom các dòng thao túng list bằng biện thức đồng bộ thì có lẽ tốt hơn.
"Thế cách nào tốt hơn vậy?"
"Tao chỉ cho mày xem." Gã lấy bàn phím và tháo bỏ các dòng
synchronized của tôi. Sau đó gã thay đổi thêm một dòng mã nữa -
dòng tạo
LinkedList ngay lúc đầu:
private List serverThreads = Collections.synchronizedList
(new LinkedList());
Jerry biên dịch mã nguồn và chạy trọn bộ các cái test. Mọi sự ổn cả. Sau rồi gã
hỏi, "Mày biết gì về design patterns hả Alphonse? Có bao giờ mày nghe đến
Decorator pattern chưa?"
"Tất nhiên là tôi nghe về chúng rồi, và tôi cũng thấy sách nói về chuyện này
trên giá sách của thiên hạ, nhưng tôi không biết nhiều lắm về chúng."
Jerry nhìn tôi nghiêm khắc nói, "vậy thì đến lúc mà nên bắt đầu học về chúng một
cách nghiêm chỉnh đi. Mày có thể mượn sách của tao và nghiên cứu nó nếu thích.
Ðầu tiên tao muốn mày đọc chương nói về Decorator pattern. Hàm
synchronizedList mình vừa gọi để gói cái
LinkedList trong một Decorator. Mọi cú gọi đến
LinkedList đều được nó đồng bộ hoá cả."
"Nghe đúng là một giải pháp hay," tôi đáp. "Ừa, mà mày cũng phải nhớ đồng bộ hoá
cụ thể những nơi dùng vòng xoay." Jerry cau mày.
"Vậy sao?" Tôi hỏi. "Ý ông vòng xoay không được đồng bộ hoá trong danh sách đồng
bộ sao?"
"TANSTAAFL," gã trả lời.
"Hở?" tôi hỏi, thộn người ra. Không biết có phải gã nói tiếng Clangrish hay gì
đây.
"TANSTAAFL," gã lặp lại theo kiểu khống chế; rồi gã mỉm cười. "There Ain't No
Such Thing As A Free Lunch" (Không hề có cái gọi là buổi ăn trưa miễn phí).
"Tôi biết," tôi mỉm cười trong khi rảo bước về buồng của tôi.
(Còn nữa.....)
|