ฉันอ่านบนpthread.h
; ฟังก์ชันเงื่อนไขที่เกี่ยวข้องกับตัวแปร (เช่นpthread_cond_wait(3)
) ต้องการ mutex เป็นอาร์กิวเมนต์ ทำไม? เท่าที่ผมสามารถบอกได้ว่าผมจะได้รับการสร้าง mutex เพียงเพื่อใช้เป็นข้อโต้แย้งที่? Mutex นั้นควรทำอะไร?
ฉันอ่านบนpthread.h
; ฟังก์ชันเงื่อนไขที่เกี่ยวข้องกับตัวแปร (เช่นpthread_cond_wait(3)
) ต้องการ mutex เป็นอาร์กิวเมนต์ ทำไม? เท่าที่ผมสามารถบอกได้ว่าผมจะได้รับการสร้าง mutex เพียงเพื่อใช้เป็นข้อโต้แย้งที่? Mutex นั้นควรทำอะไร?
คำตอบ:
มันเป็นเพียงวิธีการที่ปรับใช้ตัวแปรเงื่อนไข (หรือเริ่มต้น)
mutex ใช้ในการป้องกันตัวแปรสภาพตัวเอง นั่นเป็นเหตุผลที่คุณต้องล็อคมันก่อนที่จะรอ
การรอจะเป็นการปลดล็อค mutex ทำให้ผู้อื่นสามารถเข้าถึงตัวแปรเงื่อนไขได้ (สำหรับการส่งสัญญาณ) จากนั้นเมื่อตัวแปรเงื่อนไขถูกส่งสัญญาณหรือออกอากาศไปหนึ่งเธรดในรายการที่รอจะถูกปลุกและ mutex จะถูกล็อกอย่างน่าอัศจรรย์อีกครั้งสำหรับเธรดนั้น
โดยทั่วไปคุณจะเห็นการดำเนินการต่อไปนี้พร้อมกับตัวแปรเงื่อนไขซึ่งแสดงวิธีการทำงานของมัน ตัวอย่างต่อไปนี้เป็นเธรดผู้ปฏิบัติงานซึ่งให้งานผ่านสัญญาณไปยังตัวแปรเงื่อนไข
thread:
initialise.
lock mutex.
while thread not told to stop working:
wait on condvar using mutex.
if work is available to be done:
do the work.
unlock mutex.
clean up.
exit thread.
งานเสร็จภายในลูปนี้หากว่ามีบางอย่างพร้อมใช้งานเมื่อการรอส่งคืน เมื่อเธรดถูกตั้งค่าสถานะเพื่อหยุดการทำงาน (โดยปกติเธรดอื่นจะตั้งค่าเงื่อนไขการออกจากนั้นก็เตะตัวแปรเงื่อนไขเพื่อปลุกเธรดนี้ขึ้น) ลูปจะออกจากการทำงาน mutex จะถูกปลดล็อกและเธรดนี้จะออก
รหัสด้านบนเป็นรุ่นสำหรับผู้บริโภครายเดียวเนื่องจาก Mutex ยังคงล็อคอยู่ในขณะที่กำลังทำงาน สำหรับรูปแบบที่หลากหลายผู้บริโภคคุณสามารถใช้เป็นตัวอย่าง :
thread:
initialise.
lock mutex.
while thread not told to stop working:
wait on condvar using mutex.
if work is available to be done:
copy work to thread local storage.
unlock mutex.
do the work.
lock mutex.
unlock mutex.
clean up.
exit thread.
ทำให้ผู้บริโภครายอื่นได้รับงานขณะที่ทำงานอยู่
ตัวแปรเงื่อนไขช่วยลดภาระการโพลให้คุณแทนที่จะอนุญาตให้เธรดอื่นแจ้งให้คุณทราบเมื่อมีบางสิ่งที่จำเป็นต้องเกิดขึ้น เธรดอื่นสามารถบอกได้ว่าเธรดที่ทำงานนั้นมีอยู่ดังนี้:
lock mutex.
flag work as available.
signal condition variable.
unlock mutex.
ส่วนใหญ่ของสิ่งที่มักจะผิดพลาดที่เรียกว่าการเสแสร้งโดยทั่วไปมักจะเป็นเพราะหลายกระทู้มีสัญญาณในการpthread_cond_wait
โทร (ออกอากาศ) พวกเขาจะกลับมาพร้อมกับ mutex ทำงานแล้วรออีกครั้ง
จากนั้นเธรดที่มีสัญญาณที่สองจะออกมาเมื่อไม่มีงานต้องทำ ดังนั้นคุณต้องมีตัวแปรพิเศษที่บ่งบอกว่างานควรจะทำ (นี่คือการป้องกัน mutex โดยเนื้อแท้กับคู่ condvar / mutex ที่นี่ - เธรดอื่น ๆ จำเป็นต้องล็อค mutex ก่อนที่จะเปลี่ยนอย่างไรก็ตาม)
มันเป็นไปได้ในทางเทคนิคสำหรับเธรดที่จะกลับมาจากการรอเงื่อนไขโดยไม่ถูกเตะโดยกระบวนการอื่น (นี่คือการปลุกที่ผิดจริง) แต่ในช่วงเวลาหลายปีที่ฉันทำงานกับ pthreads ทั้งในการพัฒนา / บริการของรหัสและในฐานะผู้ใช้ ของพวกเขาฉันไม่เคยได้รับหนึ่งในเหล่านี้ อาจเป็นเพราะ HP มีการใช้งานที่เหมาะสม :-)
ไม่ว่าในกรณีใดรหัสเดียวกันกับที่จัดการกับกรณีที่ผิดพลาดนั้นก็จัดการกับการปลุกแบบปลอมด้วยเช่นกันเนื่องจากจะไม่มีการตั้งค่าสถานะการทำงานที่พร้อมใช้งานให้
do something
ภายในwhile
วง
ตัวแปรเงื่อนไขค่อนข้าง จำกัด หากคุณสามารถส่งสัญญาณเงื่อนไขได้โดยปกติคุณต้องจัดการข้อมูลบางอย่างที่เกี่ยวข้องกับเงื่อนไขที่ถูกส่งสัญญาณ การส่งสัญญาณ / การปลุกจะต้องทำโดยใช้อะตอมเพื่อให้บรรลุถึงสิ่งนั้นโดยไม่ต้องแนะนำสภาพการแข่งขันหรือซับซ้อนเกินไป
pthreads ยังสามารถให้คุณด้วยเหตุผลทางเทคนิคมากกว่าการปลุกปลอม นั่นหมายความว่าคุณต้องตรวจสอบเพรดิเคตเพื่อให้มั่นใจได้ว่าสภาพสัญญาณจริง - และแยกแยะความแตกต่างจากการตื่นขึ้น การตรวจสอบเงื่อนไขดังกล่าวเกี่ยวกับการรอคอยนั้นจำเป็นต้องได้รับการปกป้องดังนั้นตัวแปรเงื่อนไขจำเป็นต้องมีวิธีการรอ / ปลุกโดยใช้อะตอมมิคในขณะที่ล็อค / ปลดล็อก mutex ที่ป้องกันสภาพนั้น
ลองพิจารณาตัวอย่างง่ายๆที่คุณได้รับแจ้งว่ามีการสร้างข้อมูลบางส่วน อาจมีเธรดอื่นสร้างข้อมูลบางส่วนที่คุณต้องการและตั้งตัวชี้ไปยังข้อมูลนั้น
จินตนาการว่าเธรดผู้ผลิตให้ข้อมูลแก่ผู้บริโภคเธรดอื่นผ่านตัวชี้ 'some_data'
while(1) {
pthread_cond_wait(&cond); //imagine cond_wait did not have a mutex
char *data = some_data;
some_data = NULL;
handle(data);
}
คุณจะได้รับสภาพการแข่งขันมากมายโดยธรรมชาติจะเกิดอะไรขึ้นถ้าอีกเธรดหนึ่งทำงานsome_data = new_data
หลังจากที่คุณตื่นขึ้นมา แต่ก่อนที่คุณจะทำdata = some_data
คุณไม่สามารถสร้าง mutex ของคุณเองเพื่อป้องกันกรณีนี้เช่น
while(1) {
pthread_cond_wait(&cond); //imagine cond_wait did not have a mutex
pthread_mutex_lock(&mutex);
char *data = some_data;
some_data = NULL;
pthread_mutex_unlock(&mutex);
handle(data);
}
จะไม่ทำงาน แต่ก็ยังมีโอกาสที่จะมีสภาพการแข่งขันในระหว่างการตื่นขึ้นมาและการคว้า mutex การวาง mutex ก่อน pthread_cond_wait จะไม่ช่วยคุณเนื่องจากคุณจะถือ mutex ระหว่างรอ - นั่นคือผู้ผลิตจะไม่สามารถคว้า mutex ได้ (หมายเหตุในกรณีนี้คุณสามารถสร้างตัวแปรเงื่อนไขที่สองเพื่อส่งสัญญาณผู้ผลิตที่คุณทำด้วยsome_data
- แม้ว่าสิ่งนี้จะซับซ้อนโดยเฉพาะถ้าคุณต้องการผู้ผลิต / ผู้บริโภคจำนวนมาก)
ดังนั้นคุณต้องมีวิธีในการปล่อย / จับ mutex แบบอะตอมเมื่อรอ / ตื่นจากสภาพ นั่นคือสิ่งที่ตัวแปรเงื่อนไข pthread ทำและนี่คือสิ่งที่คุณต้องการ:
while(1) {
pthread_mutex_lock(&mutex);
while(some_data == NULL) { // predicate to acccount for spurious wakeups,would also
// make it robust if there were several consumers
pthread_cond_wait(&cond,&mutex); //atomically lock/unlock mutex
}
char *data = some_data;
some_data = NULL;
pthread_mutex_unlock(&mutex);
handle(data);
}
(ผู้ผลิตจะต้องใช้ความระมัดระวังเหมือนกันโดยธรรมชาติเสมอปกป้อง 'some_data' ด้วย mutex เดียวกันและทำให้แน่ใจว่าจะไม่เขียนทับ some_data ถ้า some_data อยู่ในขณะนี้! = NULL)
while (some_data != NULL)
จะเป็นห่วงในขณะที่ทำเพื่อรอตัวแปรเงื่อนไขอย่างน้อยหนึ่งครั้ง?
while(some_data != NULL)
จะเป็นwhile(some_data == NULL)
?
ตัวแปรเงื่อนไข POSIX ไม่มีสถานะ ดังนั้นจึงเป็นความรับผิดชอบของคุณในการรักษาสถานะ เนื่องจากสถานะจะสามารถเข้าถึงได้โดยเธรดทั้งสองที่รอและเธรดที่บอกเธรดอื่นให้หยุดรอดังนั้นจึงต้องมีการป้องกันโดย mutex หากคุณคิดว่าคุณสามารถใช้ตัวแปรเงื่อนไขได้โดยไม่ต้องมี mutex คุณจะไม่เข้าใจว่าตัวแปรเงื่อนไขนั้นไร้สถานะ
ตัวแปรเงื่อนไขถูกสร้างขึ้นรอบ ๆ เงื่อนไข เธรดที่รอตัวแปรเงื่อนไขกำลังรอเงื่อนไขบางอย่าง เธรดที่ตัวแปรเงื่อนไขสัญญาณเปลี่ยนเงื่อนไขนั้น ตัวอย่างเช่นเธรดอาจกำลังรอข้อมูลบางอย่างมาถึง เธรดอื่น ๆ อาจสังเกตเห็นว่าข้อมูลมาถึงแล้ว "ข้อมูลมาถึงแล้ว" เป็นเงื่อนไข
นี่คือการใช้คลาสสิกของตัวแปรเงื่อนไขง่าย:
while(1)
{
pthread_mutex_lock(&work_mutex);
while (work_queue_empty()) // wait for work
pthread_cond_wait(&work_cv, &work_mutex);
work = get_work_from_queue(); // get work
pthread_mutex_unlock(&work_mutex);
do_work(work); // do that work
}
ดูว่าเธรดกำลังรองานอยู่อย่างไร งานได้รับการคุ้มครองโดย mutex การรอจะปล่อย mutex เพื่อให้เธรดอื่นสามารถให้เธรดนี้ทำงานได้ นี่คือวิธีที่จะให้สัญญาณ:
void AssignWork(WorkItem work)
{
pthread_mutex_lock(&work_mutex);
add_work_to_queue(work); // put work item on queue
pthread_cond_signal(&work_cv); // wake worker thread
pthread_mutex_unlock(&work_mutex);
}
ขอให้สังเกตว่าคุณต้องการ mutex เพื่อป้องกันคิวงาน ขอให้สังเกตว่าตัวแปรเงื่อนไขตัวเองไม่รู้ว่าทำงานหรือไม่ นั่นคือตัวแปรเงื่อนไขจะต้องเชื่อมโยงกับเงื่อนไขเงื่อนไขนั้นจะต้องได้รับการบำรุงรักษาโดยรหัสของคุณและเนื่องจากมีการแชร์ในหมู่เธรดจึงต้องได้รับการปกป้องโดย mutex
ฟังก์ชันตัวแปรเงื่อนไขบางฟังก์ชันไม่จำเป็นต้องมี mutex: มีเพียงการดำเนินการที่รอคอยเท่านั้น การดำเนินการของสัญญาณและการออกอากาศไม่จำเป็นต้องมี mutex ตัวแปรเงื่อนไขยังไม่ได้เชื่อมโยงกับ mutex เฉพาะอย่างถาวร Mutex ภายนอกไม่ได้ป้องกันตัวแปรเงื่อนไข หากตัวแปรเงื่อนไขมีสถานะภายในเช่นคิวของเธรดที่รออยู่สิ่งนี้จะต้องได้รับการปกป้องโดยการล็อกภายในภายในตัวแปรเงื่อนไข
การดำเนินการรอรวบรวมตัวแปรเงื่อนไขและ mutex ไว้ด้วยกันเนื่องจาก:
ด้วยเหตุผลนี้การดำเนินการรอใช้เป็นอาร์กิวเมนต์ทั้ง mutex และเงื่อนไข: เพื่อให้สามารถจัดการการถ่ายโอนอะตอมของเธรดจากการเป็นเจ้าของ mutex เพื่อรอเพื่อให้เธรดไม่ตกเป็นเหยื่อของสภาพการแข่งขันตื่นขึ้นมาที่หายไปการปลุกหายไปถึงสภาพการแข่งขัน
สภาพการแข่งขัน Wakeup ที่หายไปจะเกิดขึ้นหากเธรดเลิกใช้ mutex แล้วรอวัตถุการซิงโครไนซ์แบบไร้สถานะ แต่ในทางที่ไม่ใช่อะตอม: มีหน้าต่างเวลาเมื่อเธรดไม่มีการล็อกอีกต่อไปและมี ยังไม่ได้เริ่มรอวัตถุ ในระหว่างหน้าต่างนี้เธรดอื่นสามารถเข้ามาทำให้เงื่อนไขที่รอเป็นจริงส่งสัญญาณการซิงโครไนซ์ไร้รัฐแล้วหายไป วัตถุไร้สัญชาติจำไม่ได้ว่าเป็นสัญญาณ (ไร้สัญชาติ) ดังนั้นเธรดดั้งเดิมจะเข้าสู่โหมดสลีปออบเจ็กต์แบบไร้รัฐและไม่ตื่นขึ้นแม้ว่าเงื่อนไขที่ต้องการจะเป็นจริงแล้ว: การปลุกที่หายไป
ฟังก์ชันการรอตัวแปรเงื่อนไขหลีกเลี่ยงการปลุกที่หายไปโดยตรวจสอบให้แน่ใจว่าเธรดการโทรถูกลงทะเบียนเพื่อจับการปลุกก่อนที่จะเลิก mutex สิ่งนี้จะเป็นไปไม่ได้ถ้าฟังก์ชัน condition variable wait ไม่ได้ใช้ mutex เป็นอาร์กิวเมนต์
pthread_cond_broadcast
และpthread_cond_signal
การดำเนินการ (ที่คำถาม SO นี้เกี่ยวกับ) ไม่ได้ใช้ mutex เป็นอาร์กิวเมนต์ เพียงเงื่อนไข ข้อมูลจำเพาะ POSIX เป็นที่นี่ Mutex ถูกกล่าวถึงโดยเฉพาะในสิ่งที่เกิดขึ้นในเธรดที่รอเมื่อพวกเขาตื่นขึ้นมา
ฉันไม่พบคำตอบอื่นให้กระชับและอ่านง่ายเหมือนหน้านี้ โดยปกติโค้ดที่รอจะมีหน้าตาดังนี้:
mutex.lock()
while(!check())
condition.wait()
mutex.unlock()
มีสามเหตุผลในการตัดคำwait()
ใน mutex:
signal()
ก่อนwait()
และเราจะพลาดการปลุกนี้check()
จะขึ้นอยู่กับการดัดแปลงจากเธรดอื่นดังนั้นคุณต้องแยกออกจากกันประเด็นที่สามไม่ได้เป็นปัญหาเสมอไป - บริบททางประวัติศาสตร์เชื่อมโยงจากบทความถึง การสนทนานี้
การแจ้งเตือนแบบปลอมมักถูกกล่าวถึงเกี่ยวกับกลไกนี้ (เช่นเธรดที่รออยู่นั้นถูกปลุกโดยไม่signal()
ถูกเรียก) check()
อย่างไรก็ตามเหตุการณ์ดังกล่าวจะถูกจัดการโดยคล้อง
ตัวแปรเงื่อนไขเกี่ยวข้องกับ mutex เพราะเป็นวิธีเดียวที่สามารถหลีกเลี่ยงการแข่งขันที่ถูกออกแบบมาเพื่อหลีกเลี่ยง
// incorrect usage:
// thread 1:
while (notDone) {
pthread_mutex_lock(&mutex);
bool ready = protectedReadyToRunVariable
pthread_mutex_unlock(&mutex);
if (ready) {
doWork();
} else {
pthread_cond_wait(&cond1); // invalid syntax: this SHOULD have a mutex
}
}
// signalling thread
// thread 2:
prepareToRunThread1();
pthread_mutex_lock(&mutex);
protectedReadyToRuNVariable = true;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond1);
Now, lets look at a particularly nasty interleaving of these operations
pthread_mutex_lock(&mutex);
bool ready = protectedReadyToRunVariable;
pthread_mutex_unlock(&mutex);
pthread_mutex_lock(&mutex);
protectedReadyToRuNVariable = true;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond1);
if (ready) {
pthread_cond_wait(&cond1); // uh o!
ณ จุดนี้ไม่มีเธรดที่จะส่งสัญญาณตัวแปรเงื่อนไขดังนั้น thread1 จะรอตลอดไปแม้ว่าการป้องกัน READToRunVariable ที่มีการป้องกันจะกล่าวว่าพร้อมที่จะไป!
วิธีการเดียวสำหรับสิ่งนี้คือตัวแปรเงื่อนไขเพื่อปล่อย mutex แบบอะตอมในขณะที่เริ่มต้นพร้อมที่จะรอตัวแปรเงื่อนไข นี่คือสาเหตุที่ฟังก์ชัน cond_wait ต้องการ mutex
// correct usage:
// thread 1:
while (notDone) {
pthread_mutex_lock(&mutex);
bool ready = protectedReadyToRunVariable
if (ready) {
pthread_mutex_unlock(&mutex);
doWork();
} else {
pthread_cond_wait(&mutex, &cond1);
}
}
// signalling thread
// thread 2:
prepareToRunThread1();
pthread_mutex_lock(&mutex);
protectedReadyToRuNVariable = true;
pthread_cond_signal(&mutex, &cond1);
pthread_mutex_unlock(&mutex);
mutex ควรถูกล็อคเมื่อคุณโทร pthread_cond_wait
; เมื่อคุณเรียกมันว่าทั้งสองจะปลดล็อค mutex และบล็อกเงื่อนไข เมื่อเงื่อนไขถูกส่งสัญญาณมันจะล็อคอะตอมอีกครั้งและส่งกลับ
สิ่งนี้อนุญาตให้ใช้งานการจัดตารางเวลาที่สามารถคาดการณ์ได้หากต้องการในเธรดที่จะทำการส่งสัญญาณสามารถรอจนกว่า mutex จะถูกปล่อยเพื่อทำการประมวลผลแล้วส่งสัญญาณเงื่อนไข
ฉันทำแบบฝึกหัดในชั้นเรียนถ้าคุณต้องการตัวอย่างจริงของตัวแปรเงื่อนไข:
#include "stdio.h"
#include "stdlib.h"
#include "pthread.h"
#include "unistd.h"
int compteur = 0;
pthread_cond_t varCond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex_compteur;
void attenteSeuil(arg)
{
pthread_mutex_lock(&mutex_compteur);
while(compteur < 10)
{
printf("Compteur : %d<10 so i am waiting...\n", compteur);
pthread_cond_wait(&varCond, &mutex_compteur);
}
printf("I waited nicely and now the compteur = %d\n", compteur);
pthread_mutex_unlock(&mutex_compteur);
pthread_exit(NULL);
}
void incrementCompteur(arg)
{
while(1)
{
pthread_mutex_lock(&mutex_compteur);
if(compteur == 10)
{
printf("Compteur = 10\n");
pthread_cond_signal(&varCond);
pthread_mutex_unlock(&mutex_compteur);
pthread_exit(NULL);
}
else
{
printf("Compteur ++\n");
compteur++;
}
pthread_mutex_unlock(&mutex_compteur);
}
}
int main(int argc, char const *argv[])
{
int i;
pthread_t threads[2];
pthread_mutex_init(&mutex_compteur, NULL);
pthread_create(&threads[0], NULL, incrementCompteur, NULL);
pthread_create(&threads[1], NULL, attenteSeuil, NULL);
pthread_exit(NULL);
}
มันเป็นการตัดสินใจออกแบบที่เฉพาะเจาะจงมากกว่าความต้องการทางความคิด
สำหรับเอกสาร pthreads เหตุผลที่ไม่ได้แยก mutex นั้นคือมีการปรับปรุงประสิทธิภาพที่สำคัญโดยรวมพวกเขาและพวกเขาคาดหวังว่าเนื่องจากเงื่อนไขการแข่งขันทั่วไปหากคุณไม่ได้ใช้ mutex มันเกือบจะทำอยู่แล้ว
https://linux.die.net/man/3/pthread_cond_wait
คุณสมบัติของ Mutexes และตัวแปรเงื่อนไข
มีข้อเสนอแนะว่าการได้มาและการเปิดเผย mutex จะถูกแยกออกจากการรอเงื่อนไข สิ่งนี้ถูกปฏิเสธเนื่องจากเป็นลักษณะที่รวมกันของการดำเนินการซึ่งในความเป็นจริงแล้วจะช่วยให้เกิดการใช้งานแบบเรียลไทม์ การประยุกต์ใช้เหล่านั้นสามารถย้ายเธรดที่มีลำดับความสำคัญสูงระหว่างตัวแปรเงื่อนไขและ mutex ในลักษณะที่โปร่งใสให้กับผู้เรียก สิ่งนี้สามารถป้องกันการสลับบริบทเพิ่มเติมและจัดเตรียมการได้มาซึ่ง mutex ที่กำหนดได้มากขึ้นเมื่อเธรดที่รออยู่ถูกส่งสัญญาณ ดังนั้นปัญหาความเป็นธรรมและลำดับความสำคัญสามารถจัดการได้โดยตรงโดยการกำหนดเวลา นอกจากนี้การดำเนินการรอสภาพปัจจุบันตรงกับแนวปฏิบัติที่มีอยู่
มีผู้ exegeses มากมายเกี่ยวกับเรื่องนั้น แต่ฉันต้องการที่จะเป็นตัวอย่างที่ชัดเจนด้วยตัวอย่างต่อไปนี้
1 void thr_child() {
2 done = 1;
3 pthread_cond_signal(&c);
4 }
5 void thr_parent() {
6 if (done == 0)
7 pthread_cond_wait(&c);
8 }
เกิดอะไรขึ้นกับข้อมูลโค้ด แค่ไตร่ตรองบ้างก่อนจะไปข้างหน้า
ปัญหาเป็นเรื่องที่ลึกซึ้งอย่างแท้จริง หากผู้ปกครองเรียกใช้
thr_parent()
แล้ว vets มูลค่าของdone
มันจะเห็นว่ามันเป็น0
และพยายามที่จะไปนอน แต่ก่อนที่จะมีสายรอให้เข้านอนพ่อแม่จะถูกขัดจังหวะระหว่างบรรทัด 6-7 และลูกวิ่ง เด็กเปลี่ยนตัวแปรสถานะ
done
เป็น1
และสัญญาณ แต่ไม่มีเธรดรออยู่และไม่มีเธรดถูกปลุก เมื่อผู้ปกครองทำงานอีกครั้งมันจะนอนหลับตลอดไปซึ่งไม่น่าเชื่อจริงๆ
เกิดอะไรขึ้นถ้าพวกเขาจะดำเนินการในขณะที่ได้รับล็อคเป็นรายบุคคล?