Câu trả lời:
Lấy từ http://en.wikipedia.org/wiki/Deadlock :
Trong điện toán đồng thời, bế tắc là trạng thái trong đó mỗi thành viên của một nhóm hành động, đang chờ một thành viên khác phát hành khóa
Một livelock tương tự như một bế tắc, ngoại trừ việc các trạng thái của các quá trình liên quan đến livelock liên tục thay đổi liên quan đến nhau, không tiến triển. Livelock là một trường hợp đặc biệt của nạn đói tài nguyên; định nghĩa chung chỉ nói rằng một quá trình cụ thể không tiến triển.
Một ví dụ thực tế về sự sống động xảy ra khi hai người gặp nhau trong một hành lang hẹp và mỗi người cố gắng lịch sự bằng cách di chuyển sang một bên để vượt qua người kia, nhưng cuối cùng họ vẫn lắc lư từ bên này sang bên kia mà không tiến triển gì vì cả hai liên tục di chuyển cùng một cách cùng một lúc.
Livelock là một rủi ro với một số thuật toán phát hiện và phục hồi từ bế tắc. Nếu có nhiều quá trình thực hiện hành động, thuật toán phát hiện khóa chết có thể được kích hoạt nhiều lần. Điều này có thể tránh được bằng cách đảm bảo rằng chỉ một quá trình (được chọn ngẫu nhiên hoặc ưu tiên) thực hiện hành động.
Một chủ đề thường hành động để đáp ứng với hành động của một chủ đề khác. Nếu hành động của luồng khác cũng là phản hồi đối với hành động của luồng khác, thì có thể kết quả là livelock.
Như với sự bế tắc, các chủ đề được nối lại không thể tiến bộ hơn nữa . Tuy nhiên, các chủ đề không bị chặn - đơn giản là chúng quá bận rộn để trả lời cho nhau để tiếp tục công việc . Điều này có thể so sánh với hai người cố gắng vượt qua nhau trong một hành lang: Alphonse di chuyển sang trái để Gaston vượt qua, trong khi Gaston di chuyển sang phải để cho Alphonse vượt qua. Thấy rằng họ vẫn đang chặn nhau, Alphonse di chuyển sang phải, trong khi Gaston di chuyển sang trái. Họ vẫn đang chặn nhau, và cứ thế ...
Sự khác biệt chính giữa livelock và bế tắc là các luồng sẽ không bị chặn, thay vào đó chúng sẽ cố gắng phản hồi với nhau liên tục.
Trong hình ảnh này, cả hai vòng tròn (chủ đề hoặc quy trình) sẽ cố gắng cung cấp không gian cho người khác bằng cách di chuyển sang trái và phải. Nhưng họ không thể di chuyển thêm nữa.
Tất cả nội dung và ví dụ ở đây là từ
Hệ điều hành: Nguyên tắc thiết kế và nội bộ
William Stallings
Phiên bản 8º
Bế tắc : Một tình huống trong đó hai hoặc nhiều quá trình không thể tiến hành vì mỗi quá trình đang chờ một người khác làm một việc gì đó.
Ví dụ, hãy xem xét hai quy trình, P1 và P2 và hai tài nguyên, R1 và R2. Giả sử rằng mỗi quá trình cần truy cập vào cả hai tài nguyên để thực hiện một phần chức năng của nó. Sau đó, có thể có tình huống sau: HĐH gán R1 cho P2 và R2 cho P1. Mỗi quá trình đang chờ một trong hai tài nguyên. Cả hai sẽ không giải phóng tài nguyên mà nó đã sở hữu cho đến khi nó có được tài nguyên khác và thực hiện chức năng yêu cầu cả hai tài nguyên. Hai quá trình bị bế tắc
Livelock : Một tình huống trong đó hai hoặc nhiều quá trình liên tục thay đổi trạng thái để đáp ứng với những thay đổi trong (các) quy trình khác mà không thực hiện bất kỳ công việc hữu ích nào:
Đói : Một tình huống trong đó một quá trình có thể chạy được bỏ qua vô thời hạn bởi bộ lập lịch; mặc dù nó có thể tiến hành, nó không bao giờ được chọn.
Giả sử rằng ba quy trình (P1, P2, P3) đều yêu cầu quyền truy cập định kỳ vào tài nguyên R. Hãy xem xét tình huống mà P1 đang sở hữu tài nguyên và cả P2 và P3 đều bị trì hoãn, chờ tài nguyên đó. Khi P1 thoát khỏi phần quan trọng của nó, P2 hoặc P3 sẽ được phép truy cập vào R. Giả sử rằng HĐH cấp quyền truy cập cho P3 và P1 lại yêu cầu quyền truy cập trước khi P3 hoàn thành phần quan trọng của nó. Nếu HĐH cấp quyền truy cập vào P1 sau khi P3 kết thúc và sau đó luân phiên cấp quyền truy cập vào P1 và P3, thì P2 có thể bị từ chối truy cập vào tài nguyên vô thời hạn, mặc dù không có tình huống bế tắc.
PHỤ LỤC A - CHỦ ĐỀ TRONG Ý TƯỞNG
Ví dụ bế tắc
Nếu cả hai quá trình đều đặt cờ của chúng thành true trước khi thực thi câu lệnh while, thì mỗi cái sẽ nghĩ rằng cái kia đã vào phần quan trọng của nó, gây ra bế tắc.
/* PROCESS 0 */
flag[0] = true; // <- get lock 0
while (flag[1]) // <- is lock 1 free?
/* do nothing */; // <- no? so I wait 1 second, for example
// and test again.
// on more sophisticated setups we can ask
// to be woken when lock 1 is freed
/* critical section*/; // <- do what we need (this will never happen)
flag[0] = false; // <- releasing our lock
/* PROCESS 1 */
flag[1] = true;
while (flag[0])
/* do nothing */;
/* critical section*/;
flag[1] = false;
Ví dụ về Livelock
/* PROCESS 0 */
flag[0] = true; // <- get lock 0
while (flag[1]){
flag[0] = false; // <- instead of sleeping, we do useless work
// needed by the lock mechanism
/*delay */; // <- wait for a second
flag[0] = true; // <- and restart useless work again.
}
/*critical section*/; // <- do what we need (this will never happen)
flag[0] = false;
/* PROCESS 1 */
flag[1] = true;
while (flag[0]) {
flag[1] = false;
/*delay */;
flag[1] = true;
}
/* critical section*/;
flag[1] = false;
[...] Xem xét chuỗi sự kiện sau:
Trình tự này có thể được kéo dài vô tận và không quá trình nào có thể vào phần quan trọng của nó. Nói đúng ra, đây không phải là bế tắc , bởi vì bất kỳ sự thay đổi nào về tốc độ tương đối của hai quá trình sẽ phá vỡ chu trình này và cho phép một người bước vào phần quan trọng. Điều kiện này được gọi là livelock . Hãy nhớ lại rằng bế tắc xảy ra khi một tập hợp các quy trình muốn vào các phần quan trọng của chúng nhưng không có quá trình nào có thể thành công. Với livelock , có thể có các chuỗi thực thi thành công, nhưng cũng có thể mô tả một hoặc nhiều chuỗi thực thi trong đó không có quá trình nào đi vào phần quan trọng của nó.
Không còn nội dung từ cuốn sách nữa.
Và những gì về spinlocks?
Spinlock là một kỹ thuật để tránh chi phí của cơ chế khóa hệ điều hành. Thông thường bạn sẽ làm:
try
{
lock = beginLock();
doSomething();
}
finally
{
endLock();
}
Một vấn đề bắt đầu xuất hiện khi beginLock()
chi phí nhiều hơn doSomething()
. Nói một cách rất hào hứng, hãy tưởng tượng điều gì xảy ra khi beginLock
chi phí 1 giây, nhưng doSomething
chỉ tốn 1 mili giây.
Trong trường hợp này nếu bạn đợi 1 mili giây, bạn sẽ tránh bị cản trở trong 1 giây.
Tại sao beginLock
chi phí rất nhiều? Nếu khóa miễn phí thì không tốn nhiều tiền (xem https://stackoverflow.com/a/49712993/5397116 ), nhưng nếu khóa không miễn phí, HĐH sẽ "đóng băng" luồng của bạn, thiết lập cơ chế để đánh thức bạn khi khóa được giải phóng, và sau đó đánh thức bạn một lần nữa trong tương lai.
Tất cả điều này là đắt hơn nhiều so với một số vòng kiểm tra khóa. Đó là lý do tại sao đôi khi tốt hơn để làm một "spinlock".
Ví dụ:
void beginSpinLock(lock)
{
if(lock) loopFor(1 milliseconds);
else
{
lock = true;
return;
}
if(lock) loopFor(2 milliseconds);
else
{
lock = true;
return;
}
// important is that the part above never
// cause the thread to sleep.
// It is "burning" the time slice of this thread.
// Hopefully for good.
// some implementations fallback to OS lock mechanism
// after a few tries
if(lock) return beginLock(lock);
else
{
lock = true;
return;
}
}
Nếu việc triển khai của bạn không cẩn thận, bạn có thể rơi vào livelock, dành tất cả CPU cho cơ chế khóa.
Cũng thấy:
https://preshing.com/20120226/roll-your-own-lightgra-mutex/ Việc
triển khai khóa xoay của tôi có đúng và tối ưu không?
Tóm tắt :
Bế tắc : tình huống không ai tiến bộ, không làm gì cả (ngủ, chờ đợi, v.v.). Việc sử dụng CPU sẽ thấp;
Livelock : tình huống không có ai tiến triển, nhưng CPU được dành cho cơ chế khóa chứ không phải cho tính toán của bạn;
Đói: tình huống mà một viện trưởng không bao giờ có cơ hội để chạy; bởi sự xui xẻo thuần túy hoặc bởi một số tài sản của nó (ví dụ mức độ ưu tiên thấp);
Spinlock : kỹ thuật tránh chi phí chờ khóa được giải phóng.
CHẾT Bế tắc là một điều kiện trong đó một nhiệm vụ chờ đợi vô thời hạn đối với các điều kiện không bao giờ có thể thỏa mãn - nhiệm vụ yêu cầu quyền kiểm soát độc quyền đối với tài nguyên được chia sẻ - nhiệm vụ giữ tài nguyên trong khi chờ các tài nguyên khác được giải phóng - các nhiệm vụ không thể buộc phải hủy tài nguyên - chờ đợi thông tư điều kiện tồn tại
LIVELOCK Điều kiện Livelock có thể phát sinh khi hai hoặc nhiều tác vụ phụ thuộc và sử dụng một số tài nguyên gây ra tình trạng phụ thuộc vòng tròn trong đó các tác vụ đó tiếp tục chạy mãi mãi, do đó chặn tất cả các tác vụ mức độ ưu tiên thấp hơn chạy (các tác vụ ưu tiên thấp hơn này gặp phải tình trạng gọi là chết đói)
Có thể hai ví dụ này minh họa cho bạn sự khác biệt giữa bế tắc và livelock:
Ví dụ Java cho một bế tắc:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class DeadlockSample {
private static final Lock lock1 = new ReentrantLock(true);
private static final Lock lock2 = new ReentrantLock(true);
public static void main(String[] args) {
Thread threadA = new Thread(DeadlockSample::doA,"Thread A");
Thread threadB = new Thread(DeadlockSample::doB,"Thread B");
threadA.start();
threadB.start();
}
public static void doA() {
System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
lock1.lock();
System.out.println(Thread.currentThread().getName() + " : holds lock 1");
try {
System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
lock2.lock();
System.out.println(Thread.currentThread().getName() + " : holds lock 2");
try {
System.out.println(Thread.currentThread().getName() + " : critical section of doA()");
} finally {
lock2.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
}
} finally {
lock1.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
}
}
public static void doB() {
System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
lock2.lock();
System.out.println(Thread.currentThread().getName() + " : holds lock 2");
try {
System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
lock1.lock();
System.out.println(Thread.currentThread().getName() + " : holds lock 1");
try {
System.out.println(Thread.currentThread().getName() + " : critical section of doB()");
} finally {
lock1.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
}
} finally {
lock2.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
}
}
}
Đầu ra mẫu:
Thread A : waits for lock 1
Thread B : waits for lock 2
Thread A : holds lock 1
Thread B : holds lock 2
Thread B : waits for lock 1
Thread A : waits for lock 2
Java-Ví dụ cho một livelock:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LivelockSample {
private static final Lock lock1 = new ReentrantLock(true);
private static final Lock lock2 = new ReentrantLock(true);
public static void main(String[] args) {
Thread threadA = new Thread(LivelockSample::doA, "Thread A");
Thread threadB = new Thread(LivelockSample::doB, "Thread B");
threadA.start();
threadB.start();
}
public static void doA() {
try {
while (!lock1.tryLock()) {
System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
Thread.sleep(100);
}
System.out.println(Thread.currentThread().getName() + " : holds lock 1");
try {
while (!lock2.tryLock()) {
System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
Thread.sleep(100);
}
System.out.println(Thread.currentThread().getName() + " : holds lock 2");
try {
System.out.println(Thread.currentThread().getName() + " : critical section of doA()");
} finally {
lock2.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
}
} finally {
lock1.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
}
} catch (InterruptedException e) {
// can be ignored here for this sample
}
}
public static void doB() {
try {
while (!lock2.tryLock()) {
System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
Thread.sleep(100);
}
System.out.println(Thread.currentThread().getName() + " : holds lock 2");
try {
while (!lock1.tryLock()) {
System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
Thread.sleep(100);
}
System.out.println(Thread.currentThread().getName() + " : holds lock 1");
try {
System.out.println(Thread.currentThread().getName() + " : critical section of doB()");
} finally {
lock1.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
}
} finally {
lock2.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
}
} catch (InterruptedException e) {
// can be ignored here for this sample
}
}
}
Đầu ra mẫu:
Thread B : holds lock 2
Thread A : holds lock 1
Thread A : waits for lock 2
Thread B : waits for lock 1
Thread B : waits for lock 1
Thread A : waits for lock 2
Thread A : waits for lock 2
Thread B : waits for lock 1
Thread B : waits for lock 1
Thread A : waits for lock 2
Thread A : waits for lock 2
Thread B : waits for lock 1
...
Cả hai ví dụ đều buộc các luồng xử lý các khóa theo các thứ tự khác nhau. Trong khi bế tắc chờ đợi khóa khác, thì livelock không thực sự chờ đợi - nó tuyệt vọng cố gắng để có được khóa mà không có cơ hội lấy được khóa. Mỗi lần thử đều tiêu tốn chu kỳ CPU.
Hãy tưởng tượng bạn đã xử lý chủ đề A và chủ đề B. Cả hai đều synchronised
trên cùng một đối tượng và bên trong khối này có một biến toàn cục mà cả hai đang cập nhật;
static boolean commonVar = false;
Object lock = new Object;
...
void threadAMethod(){
...
while(commonVar == false){
synchornized(lock){
...
commonVar = true
}
}
}
void threadBMethod(){
...
while(commonVar == true){
synchornized(lock){
...
commonVar = false
}
}
}
Vì vậy, khi thread đi vào trong while
vòng lặp và giữ khóa, nó những gì nó đã làm và thiết lập commonVar
để true
. Sau đó, sợi B đến, đi vào trong while
vòng lặp và vì commonVar
là true
bây giờ, nó là có thể giữ khóa. Nó làm như vậy, thực thi synchronised
khối và thiết lập commonVar
lại false
. Bây giờ, sợi A một lần nữa nhận được cửa sổ CPU mới của nó, nó là về để thoát khỏi while
vòng lặp nhưng thread B vừa đặt nó trở lại false
, do đó lặp đi lặp lại chu kỳ trên một lần nữa. Chủ đề làm một cái gì đó (vì vậy chúng không bị chặn theo nghĩa truyền thống) nhưng không có gì nhiều.
Có lẽ cũng rất hay khi đề cập đến việc livelock không nhất thiết phải xuất hiện ở đây. Tôi giả sử rằng trình lập lịch biểu ủng hộ luồng khác sau khi synchronised
khối kết thúc thực thi. Hầu hết thời gian, tôi nghĩ rằng đó là một kỳ vọng khó đạt được và phụ thuộc vào nhiều điều xảy ra dưới mui xe.