ใครบางคนช่วยอธิบายด้วยตัวอย่าง (ของรหัส) ความแตกต่างระหว่างการหยุดชะงักและlivelockคืออะไร?
ใครบางคนช่วยอธิบายด้วยตัวอย่าง (ของรหัส) ความแตกต่างระหว่างการหยุดชะงักและlivelockคืออะไร?
คำตอบ:
นำมาจากhttp://en.wikipedia.org/wiki/Deadlock :
ในการคำนวณพร้อมกันDeadlockคือสถานะที่สมาชิกแต่ละคนของกลุ่มแอ็คชันกำลังรอให้สมาชิกคนอื่นปลดล็อค
livelockจะคล้ายกับการหยุดชะงักยกเว้นว่ารัฐของกระบวนการที่เกี่ยวข้องในการ livelock อย่างต่อเนื่องเปลี่ยนเรื่องไปอีกคนหนึ่งที่มีไม่มีความคืบหน้า Livelock เป็นกรณีพิเศษของความอดอยากทรัพยากร คำจำกัดความทั่วไประบุว่ากระบวนการเฉพาะนั้นไม่คืบหน้า
ตัวอย่างของโลกแห่งความเป็นจริงของ livelock เกิดขึ้นเมื่อคนสองคนพบกันในทางเดินแคบ ๆ และแต่ละคนพยายามที่จะสุภาพด้วยการย้ายออกไปเพื่อให้คนอื่นผ่าน แต่พวกเขาก็จบลงด้วยการโยกย้ายจากด้านหนึ่งไปอีกด้านหนึ่ง วิธีเดียวกันในเวลาเดียวกัน
Livelock เป็นความเสี่ยงด้วยอัลกอริทึมบางอย่างที่ตรวจจับและกู้คืนจากการหยุดชะงัก หากมีมากกว่าหนึ่งกระบวนการดำเนินการอัลกอริทึมการตรวจจับการหยุดชะงักสามารถถูกเรียกใช้ซ้ำ ๆ สิ่งนี้สามารถหลีกเลี่ยงได้โดยการทำให้มั่นใจว่ากระบวนการเดียวเท่านั้น (สุ่มเลือกหรือลำดับความสำคัญ) ดำเนินการ
เธรดมักทำหน้าที่ตอบสนองต่อการกระทำของเธรดอื่น หากการดำเนินการของเธรดอื่นเป็นการตอบสนองต่อการดำเนินการของเธรดอื่นดังนั้นอาจส่งผลให้เกิดการหมุนวน
เช่นเดียวกับ deadlock เธรด livelocked ไม่สามารถดำเนินการต่อไปได้ อย่างไรก็ตามเธรดจะไม่ถูกบล็อก - เธรดเหล่านั้นยุ่งเกินไปที่จะตอบสนองซึ่งกันและกันเพื่อให้ทำงานต่อ นี่เปรียบได้กับคนสองคนที่พยายามจะผ่านกันและกันในทางเดิน: อัลฟองส์ขยับไปทางซ้ายเพื่อให้แกสตันผ่านขณะที่แกสตันเดินไปทางขวาของเขาเพื่อให้อัลฟองส์ผ่าน เมื่อเห็นว่าพวกเขายังคงบล็อกกันและกันอัลฟองส์ขยับไปทางขวาขณะที่แกสตันขยับไปทางซ้าย พวกเขายังคงบล็อกซึ่งกันและกันและอื่น ๆ ...
ข้อแตกต่างที่สำคัญระหว่างlivelockและdeadlockคือเธรดจะไม่ถูกบล็อก แต่จะพยายามตอบกลับกันอย่างต่อเนื่อง
ในภาพนี้วงกลม (เธรดหรือกระบวนการ) จะพยายามให้พื้นที่กับอีกอันด้วยการเลื่อนไปทางซ้ายและขวา แต่พวกเขาไม่สามารถเคลื่อนไหวได้อีกต่อไป
เนื้อหาและตัวอย่างทั้งหมดที่นี่มาจาก
ระบบปฏิบัติการ: หลักการภายในและการออกแบบ
William Stallings
8º Edition
การหยุดชะงัก : สถานการณ์ที่กระบวนการสองกระบวนการขึ้นไปไม่สามารถดำเนินการได้เนื่องจากแต่ละกระบวนการกำลังรอให้กระบวนการอื่นทำอะไรบางอย่าง
ตัวอย่างเช่นพิจารณาสองกระบวนการ P1 และ P2 และสองทรัพยากร R1 และ R2 สมมติว่าแต่ละกระบวนการต้องการเข้าถึงทรัพยากรทั้งสองเพื่อดำเนินการส่วนหนึ่งของฟังก์ชัน จากนั้นเป็นไปได้ที่จะมีสถานการณ์ต่อไปนี้: ระบบปฏิบัติการกำหนด R1 ถึง P2 และ R2 ถึง P1 แต่ละกระบวนการกำลังรอหนึ่งในสองทรัพยากร จะไม่ปล่อยทรัพยากรที่มีอยู่แล้วจนกว่าจะได้รับทรัพยากรอื่นและดำเนินการฟังก์ชันที่ต้องใช้ทรัพยากรทั้งสอง กระบวนการทั้งสองถูกหยุดชะงัก
Livelock : สถานการณ์ที่กระบวนการสองกระบวนการขึ้นไปเปลี่ยนสถานะอย่างต่อเนื่องเพื่อตอบสนองต่อการเปลี่ยนแปลงในกระบวนการอื่นโดยไม่ต้องทำงานที่มีประโยชน์ใด ๆ :
ความอดอยาก : สถานการณ์ที่กระบวนการที่เรียกใช้ถูกมองข้ามโดยตัวกำหนดตารางเวลา แม้ว่าจะสามารถดำเนินการต่อไปได้ แต่ก็ไม่เคยถูกเลือก
สมมติว่าสามกระบวนการ (P1, P2, P3) แต่ละกระบวนการต้องมีการเข้าถึงทรัพยากรเป็นระยะ R พิจารณาสถานการณ์ที่ P1 อยู่ในความครอบครองของทรัพยากรและทั้ง P2 และ P3 ล่าช้าเพื่อรอทรัพยากรนั้น เมื่อ P1 ออกจากส่วนที่สำคัญอย่างใดอย่างหนึ่ง P2 หรือ P3 ควรได้รับอนุญาตให้เข้าถึงอาร์สมมติว่าระบบปฏิบัติการให้สิทธิ์การเข้าถึง P3 และ P1 นั้นต้องการการเข้าถึงอีกครั้งก่อนที่ P3 จะเสร็จสมบูรณ์ส่วนที่สำคัญ หากระบบปฏิบัติการให้สิทธิ์การเข้าถึง P1 หลังจาก P3 เสร็จสิ้นแล้วและอีกทางเลือกหนึ่งให้แก่การเข้าถึง P1 และ P3 ดังนั้น P2 อาจถูกปฏิเสธการเข้าถึงทรัพยากรอย่างไม่มีกำหนดแม้ว่าจะไม่มีสถานการณ์การหยุดชะงัก
ภาคผนวก A - หัวข้อที่น่าเชื่อถือ
ตัวอย่างการหยุดชะงัก
หากทั้งสองกระบวนการตั้งค่าสถานะของพวกเขาเป็นจริงก่อนที่จะดำเนินการคำสั่ง while แล้วแต่ละคนจะคิดว่าอีกคนได้เข้าสู่ส่วนที่สำคัญของมันทำให้เกิดการหยุดชะงัก
/* 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;
ตัวอย่าง 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;
[... ] พิจารณาลำดับเหตุการณ์ต่อไปนี้:
ลำดับนี้สามารถขยายได้อย่างไม่มีกำหนดและกระบวนการใดไม่สามารถเข้าสู่ส่วนที่สำคัญ การพูดอย่างเคร่งครัดนี่ไม่ใช่การหยุดชะงักเนื่องจากการเปลี่ยนแปลงใด ๆ ในความเร็วสัมพัทธ์ของกระบวนการทั้งสองจะทำให้วงจรนี้แตกและอนุญาตให้ใครเข้าสู่ส่วนวิกฤต เงื่อนไขนี้จะเรียกว่าเป็นlivelock จำได้ว่าการหยุดชะงักเกิดขึ้นเมื่อชุดของกระบวนการต้องการเข้าสู่ส่วนที่สำคัญ แต่ไม่มีกระบวนการใดที่จะประสบความสำเร็จ ด้วยlivelockมีลำดับการประมวลผลที่เป็นไปได้ แต่ก็เป็นไปได้ที่จะอธิบายลำดับการดำเนินการหนึ่งรายการขึ้นไปโดยที่ไม่มีกระบวนการใดเข้าสู่ส่วนที่สำคัญ
ไม่ใช่เนื้อหาจากหนังสืออีกต่อไป
แล้ว Spinlocks ล่ะ?
Spinlock เป็นเทคนิคในการหลีกเลี่ยงค่าใช้จ่ายของกลไกการล็อค OS โดยทั่วไปแล้วคุณจะทำ:
try
{
lock = beginLock();
doSomething();
}
finally
{
endLock();
}
ปัญหาเริ่มที่จะปรากฏขึ้นเมื่อค่าใช้จ่ายมากขึ้นกว่าbeginLock()
doSomething()
ในแง่ที่ตื่นเต้นมากลองจินตนาการว่าจะเกิดอะไรขึ้นเมื่อbeginLock
ราคา 1 วินาที แต่doSomething
ราคาเพียง 1 มิลลิวินาที
ในกรณีนี้หากคุณรอ 1 มิลลิวินาทีคุณจะหลีกเลี่ยงการถูกขัดขวางเป็นเวลา 1 วินาที
ทำไมถึงbeginLock
ต้องเสียค่าใช้จ่ายมากมาย? หากการล็อคฟรีไม่เสียค่าใช้จ่ายมากนัก (ดูhttps://stackoverflow.com/a/49712993/5397116 ) แต่ถ้าการล็อคไม่ฟรีระบบปฏิบัติการจะ "หยุด" เธรดของคุณตั้งค่ากลไกเพื่อปลุกคุณ เมื่อล็อคเป็นอิสระแล้วปลุกคุณอีกครั้งในอนาคต
ทั้งหมดนี้มีราคาแพงกว่าลูปบางอันที่ตรวจสอบล็อค นั่นคือเหตุผลที่บางครั้งดีกว่าที่จะทำ "spinlock"
ตัวอย่างเช่น:
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;
}
}
หากการนำไปปฏิบัติของคุณไม่ระวังคุณสามารถตกหล่นไปมาได้โดยใช้ CPU ทั้งหมดในกลไกการล็อค
ดูเพิ่มเติมที่:
https://preshing.com/20120226/roll-your-own-lightweight-mutex/
การใช้งานล็อคการหมุนของฉันถูกต้องและเหมาะสมหรือไม่
สรุป :
การหยุดชะงัก : สถานการณ์ที่ไม่มีใครคืบหน้าไม่ทำอะไรเลย (กำลังหลับรอ ฯลฯ ) การใช้ CPU จะต่ำ
Livelock : สถานการณ์ที่ไม่มีใครคืบหน้า แต่ใช้ CPU กับกลไกการล็อคไม่ใช่การคำนวณของคุณ
ความอดอยาก: สถานการณ์ที่ผู้ให้คำปรึกษาคนใดคนหนึ่งไม่เคยได้รับโอกาสเรียกใช้ โดยโชคร้ายบริสุทธิ์หรือโดยบางส่วนของคุณสมบัติของมัน (เช่นลำดับความสำคัญต่ำ);
Spinlock : เทคนิคการหลีกเลี่ยงค่าใช้จ่ายเพื่อรอการปลดล็อคเพื่อให้ได้อิสระ
DEADLOCK Deadlock เป็นเงื่อนไขที่ภารกิจจะรอเงื่อนไขที่ไม่สามารถทำได้ - งานอ้างสิทธิ์ควบคุมทรัพยากรที่ใช้ร่วมกันแบบเอกสิทธิ์ - งานเก็บทรัพยากรในขณะที่รอทรัพยากรอื่น ๆ ที่จะออก - งานไม่สามารถบังคับให้แยกแยะทรัพยากร - การรอแบบวงกลม สภาพมีอยู่
LIVELOCK Livelock เงื่อนไขสามารถเกิดขึ้นได้เมื่อสองงานหรือมากกว่านั้นขึ้นอยู่กับการใช้ทรัพยากรบางอย่างที่ก่อให้เกิดสภาวะการพึ่งพาแบบวงกลมซึ่งงานเหล่านั้นยังคงทำงานต่อไปตลอดกาลดังนั้นการบล็อกภารกิจระดับความสำคัญต่ำกว่าทั้งหมดจากการทำงาน
ตัวอย่างสองตัวอย่างนี้อาจแสดงให้คุณเห็นถึงความแตกต่างระหว่างการหยุดชะงักและการเคลื่อนไหว:
ตัวอย่าง Java สำหรับการหยุดชะงัก:
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");
}
}
}
ตัวอย่างผลลัพธ์:
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 สำหรับ 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
}
}
}
ตัวอย่างผลลัพธ์:
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
...
ทั้งสองตัวอย่างบังคับให้เธรดได้รับการล็อกตามลำดับที่ต่างกัน ในขณะที่ deadlock รอการล็อคอื่น ๆ livelock ไม่รอจริงๆมันพยายามอย่างยิ่งที่จะได้รับการล็อคโดยไม่มีโอกาสได้ล็อค ทุกครั้งที่ใช้ CPU จะสิ้นเปลือง
ลองนึกภาพคุณได้เธรด A และเธรด B พวกเขาทั้งคู่synchronised
อยู่บนวัตถุเดียวกันและในบล็อกนี้มีตัวแปรทั่วโลกที่พวกเขากำลังอัปเดต
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
}
}
}
ดังนั้นเมื่อด้ายเข้ามาในwhile
วงและถือล็อคสิ่งที่มันไม่ได้จะทำอย่างไรและการตั้งค่าไปcommonVar
true
แล้วด้าย B มาในเข้ามาในwhile
วงและตั้งแต่commonVar
เป็นtrue
ตอนนี้ก็คือสามารถที่จะถือล็อค มันไม่เป็นเช่นนั้นดำเนินการsynchronised
บล็อกและชุดกลับไปcommonVar
false
ตอนนี้เธรด A อีกครั้งจะได้รับเป็นหน้าต่าง CPU ใหม่มันกำลังจะออกจากwhile
ลูป แต่เธรด B เพิ่งตั้งค่ากลับเป็นfalse
ดังนั้นรอบจะวนซ้ำอีกครั้ง กระทู้ทำอะไรบางอย่าง (ดังนั้นพวกเขาจะไม่ถูกปิดกั้นในความหมายดั้งเดิม) แต่สำหรับอะไรที่สวยมาก
นอกจากนี้ยังอาจเป็นการดีที่จะกล่าวถึง livelock ที่ไม่จำเป็นต้องปรากฏที่นี่ ฉันสมมติว่าตัวจัดตารางเวลาสนับสนุนเธรดอื่นเมื่อsynchronised
บล็อกเสร็จสิ้นการดำเนินการ เวลาส่วนใหญ่ฉันคิดว่ามันเป็นความคาดหวังที่ยากจะตีและขึ้นอยู่กับหลายสิ่งที่เกิดขึ้นภายใต้ประทุน