Re-entrant lock และแนวคิดโดยทั่วไปคืออะไร?


93

ฉันมักจะสับสน จะมีใครอธิบายได้ไหมว่าReentrantหมายถึงอะไรในบริบทต่างๆ แล้วทำไมคุณถึงต้องการใช้ reentrant กับ non-reentrant?

พูดว่า pthread (posix) กำลังล็อค primitives พวกเขากลับเข้ามาใหม่หรือไม่? ข้อผิดพลาดใดที่ควรหลีกเลี่ยงเมื่อใช้?

mutex re-entrant หรือไม่

คำตอบ:


161

การล็อกผู้เข้าใหม่

การล็อกการย้อนกลับเป็นวิธีที่กระบวนการสามารถเรียกร้องการล็อกได้หลายครั้งโดยไม่ต้องปิดกั้น มีประโยชน์ในสถานการณ์ที่ไม่สะดวกในการติดตามว่าคุณได้ล็อกแล้วหรือยัง หากล็อคไม่ได้เข้าใหม่คุณสามารถจับล็อคได้จากนั้นปิดกั้นเมื่อคุณไปคว้ามันอีกครั้งเพื่อปิดกั้นกระบวนการของคุณเองอย่างมีประสิทธิภาพ

การย้อนกลับโดยทั่วไปเป็นคุณสมบัติของรหัสที่ไม่มีสถานะกลางที่ไม่สามารถเปลี่ยนแปลงได้ซึ่งอาจเสียหายได้หากมีการเรียกรหัสในขณะที่กำลังดำเนินการ การเรียกดังกล่าวสามารถทำได้โดยเธรดอื่นหรืออาจสร้างซ้ำโดยเส้นทางการดำเนินการที่มาจากภายในโค้ดเอง

หากรหัสอาศัยสถานะที่ใช้ร่วมกันซึ่งสามารถอัปเดตได้ในระหว่างการดำเนินการจะไม่สามารถเข้าร่วมใหม่ได้อย่างน้อยก็ไม่ใช่หากการอัปเดตนั้นสามารถทำลายได้

กรณีการใช้งานสำหรับการล็อกผู้เข้าใหม่

ตัวอย่าง (ค่อนข้างทั่วไปและเป็นรูปเป็นร่าง) ของแอปพลิเคชันสำหรับล็อกผู้เข้าใหม่อาจเป็น:

  • คุณมีการคำนวณบางอย่างที่เกี่ยวข้องกับอัลกอริทึมที่ข้ามกราฟ (อาจมีวงจรอยู่ในนั้น) การข้ามผ่านอาจไปที่โหนดเดียวกันมากกว่าหนึ่งครั้งเนื่องจากรอบหรือเนื่องจากหลายเส้นทางไปยังโหนดเดียวกัน

  • โครงสร้างข้อมูลอาจมีการเข้าถึงพร้อมกันและอาจได้รับการอัปเดตด้วยเหตุผลบางประการโดยอาจใช้เธรดอื่น คุณต้องสามารถล็อคแต่ละโหนดเพื่อจัดการกับความเสียหายของข้อมูลที่อาจเกิดขึ้นเนื่องจากสภาพการแข่งขัน ด้วยเหตุผลบางประการ (อาจเป็นประสิทธิภาพ) คุณไม่ต้องการล็อกโครงสร้างข้อมูลทั้งหมดทั่วโลก

  • การคำนวณของคุณไม่สามารถเก็บข้อมูลที่สมบูรณ์เกี่ยวกับโหนดที่คุณเคยเยี่ยมชมได้หรือคุณกำลังใช้โครงสร้างข้อมูลที่ไม่อนุญาตให้ตอบคำถาม "ฉันเคยมาที่นี่มาก่อน" อย่างรวดเร็ว

    ตัวอย่างของสถานการณ์นี้จะเป็นการนำอัลกอริทึมของ Dijkstra ไปใช้งานโดยใช้ลำดับความสำคัญเป็นลำดับความสำคัญแบบไบนารีฮีปหรือการค้นหาแบบกว้างเป็นอันดับแรกโดยใช้รายการที่เชื่อมโยงอย่างง่ายเป็นคิว ในกรณีเหล่านี้การสแกนคิวสำหรับการแทรกที่มีอยู่คือ O (N) และคุณอาจไม่ต้องการทำซ้ำทุกครั้ง

ในสถานการณ์เช่นนี้การติดตามว่าล็อคใดที่คุณได้มานั้นมีราคาแพง สมมติว่าคุณต้องการทำการล็อกที่ระดับโหนดกลไกการล็อกผู้เข้าใหม่จะช่วยลดความจำเป็นในการบอกว่าคุณเคยเยี่ยมชมโหนดมาก่อนหรือไม่ คุณสามารถล็อกโหนดโดยสุ่มสี่สุ่มห้าบางทีอาจปลดล็อกหลังจากที่คุณเปิดโหนดออกจากคิว

mutexes เข้าใหม่

mutex อย่างง่ายไม่สามารถป้อนซ้ำได้เนื่องจากมีเพียงเธรดเดียวเท่านั้นที่สามารถอยู่ในส่วนวิกฤตได้ในเวลาที่กำหนด หากคุณจับ mutex แล้วลองจับอีกครั้ง mutex ธรรมดา ๆ ก็ไม่มีข้อมูลเพียงพอที่จะบอกได้ว่าใครเคยถือมันมาก่อนหน้านี้ ในการทำสิ่งนี้ซ้ำคุณต้องมีกลไกที่แต่ละเธรดมีโทเค็นเพื่อที่คุณจะได้บอกได้ว่าใครคว้า mutex ไปได้ ทำให้กลไก mutex ค่อนข้างแพงกว่าดังนั้นคุณอาจไม่ต้องการทำในทุกสถานการณ์

IIRC API เธรด POSIX เสนอตัวเลือกของ mutexes re-entrant และ non re-entrant


2
แม้ว่าโดยปกติแล้วควรหลีกเลี่ยงสถานการณ์ดังกล่าวเนื่องจากเป็นการยากที่จะหลีกเลี่ยงการหยุดชะงัก ฯลฯ เช่นกัน การทำเกลียวนั้นยากพอสมควรโดยไม่ต้องสงสัยว่าคุณได้ล็อกแล้วหรือยัง
Jon Skeet

+1 ให้พิจารณากรณีที่ล็อคไม่กลับเข้าที่คุณสามารถปิดกั้นตัวเองได้หากคุณไม่ระวัง นอกจากนี้ในภาษา C คุณไม่มีกลไกแบบเดียวกับที่ภาษาอื่น ๆ ทำเพื่อให้แน่ใจว่าการล็อกจะถูกปลดล็อกหลายครั้งตามที่ได้รับมา ซึ่งอาจนำไปสู่ปัญหาใหญ่
user7116

1
นั่นคือสิ่งที่เกิดขึ้นกับฉันเมื่อวานนี้: ฉันไม่ได้นำประเด็นเรื่องการกลับเข้ามาใหม่ในการพิจารณาและลงเอยด้วยการแก้จุดบกพร่องเป็นเวลา 5 ชั่วโมง ...
vehomzzz

@ Jon Skeet - ฉันคิดว่าอาจมีสถานการณ์ (ดูตัวอย่างที่ค่อนข้างสร้างไว้ด้านบน) ที่การติดตามการล็อกไม่สามารถทำได้เนื่องจากประสิทธิภาพหรือการพิจารณาอื่น ๆ
ConcernedOfTunbridgeWells

21

การล็อกผู้เข้าใหม่ช่วยให้คุณสามารถเขียนวิธีMที่ทำให้ล็อกทรัพยากรAแล้วเรียกMซ้ำหรือจากรหัสที่ล็อคไว้Aแล้ว

ด้วยการล็อกแบบไม่กลับเข้าใหม่คุณจะต้องมี 2 เวอร์ชันคือเวอร์ชันMที่ล็อกและเวอร์ชันที่ไม่มีและตรรกะเพิ่มเติมเพื่อเรียกแบบที่ถูกต้อง


นี่หมายความว่าถ้าฉันมีการโทรแบบเรียกซ้ำซึ่งได้รับการล็อก obj เดียวกันมากกว่าหนึ่งครั้ง - พูดxครั้งโดยเธรดที่กำหนดฉันไม่สามารถแทรกระหว่างการดำเนินการได้โดยไม่ปล่อยการล็อกแบบเรียกซ้ำทั้งหมด (ล็อกเดียวกัน แต่เป็นxจำนวนครั้ง) ถ้าเป็นจริงมันจะทำให้การใช้งานตามลำดับเป็นหลัก ฉันพลาดอะไรไปรึเปล่า?
DevdattaK

นั่นไม่ควรเป็นปัญหาที่แท้จริง เป็นข้อมูลเพิ่มเติมเกี่ยวกับการล็อกแบบละเอียดและเธรดจะไม่ล็อคตัวเอง
Henk Holterman

17

การล็อกกลับเข้าที่อธิบายไว้เป็นอย่างดีในบทช่วยสอนนี้

ตัวอย่างในบทช่วยสอนมีรูปแบบน้อยกว่าคำตอบเกี่ยวกับการข้ามกราฟ การล็อกแบบกลับเข้าที่มีประโยชน์ในกรณีที่ง่ายมาก


3

อะไรและทำไมของmutex แบบเรียกซ้ำไม่ควรเป็นสิ่งที่ซับซ้อนดังที่อธิบายไว้ในคำตอบที่ยอมรับ

ฉันอยากจะเขียนความเข้าใจของฉันหลังจากที่ขุดดูในเน็ต


อันดับแรกคุณควรตระหนักว่าเมื่อพูดถึงmutexแนวคิดแบบมัลติเธรดก็มีส่วนเกี่ยวข้องด้วยเช่นกัน (mutex ใช้สำหรับการซิงโครไนซ์ฉันไม่ต้องการ mutex ถ้าฉันมีเพียง 1 เธรดในโปรแกรมของฉัน)


ประการที่สองคุณควรทราบความแตกต่าง bewteen ที่mutex ปกติและmutex recursive

อ้างจากAPUE :

(mutex แบบเรียกซ้ำคือ a) ประเภท mutex ที่อนุญาตให้เธรดเดียวกันล็อกได้หลายครั้งโดยไม่ต้องปลดล็อกก่อน

ความแตกต่างที่สำคัญคือภายในเธรดเดียวกันการล็อกซ้ำการล็อกซ้ำไม่ทำให้เกิดการชะงักงันและไม่ปิดกั้นเธรด

นี่หมายความว่าการล็อกแบบหักล้างไม่เคยทำให้เกิดการชะงักงันหรือไม่?
ไม่มันยังสามารถทำให้เกิดการหยุดชะงักได้เหมือน mutex ปกติหากคุณล็อกไว้ในเธรดเดียวโดยไม่ได้ปลดล็อกและพยายามล็อกไว้ในเธรดอื่น

มาดูโค้ดกันเป็นหลักฐาน

  1. mutex ปกติพร้อมการหยุดชะงัก
#include <pthread.h>
#include <stdio.h>

pthread_mutex_t lock;


void * func1(void *arg){
    printf("thread1\n");
    pthread_mutex_lock(&lock);
    printf("thread1 hey hey\n");

}


void * func2(void *arg){
    printf("thread2\n");
    pthread_mutex_lock(&lock);
    printf("thread2 hey hey\n");
}

int main(){
    pthread_mutexattr_t lock_attr;
    int error;
//    error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_RECURSIVE);
    error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_DEFAULT);
    if(error){
        perror(NULL);
    }

    pthread_mutex_init(&lock, &lock_attr);

    pthread_t t1, t2;

    pthread_create(&t1, NULL, func1, NULL);
    pthread_create(&t2, NULL, func2, NULL);

    pthread_join(t2, NULL);

}

เอาต์พุต:

thread1
thread1 hey hey
thread2

ตัวอย่างการหยุดชะงักทั่วไปไม่มีปัญหา

  1. mutex แบบเรียกซ้ำพร้อมการหยุดชะงัก

เพียงแค่ยกเลิกการ
error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_RECURSIVE);
แสดงความคิดเห็นในบรรทัดนี้และแสดงความคิดเห็นอีกบรรทัด

เอาต์พุต:

thread1
thread1 hey hey
thread2

ใช่ mutex แบบเรียกซ้ำอาจทำให้เกิดการหยุดชะงักได้เช่นกัน

  1. mutex ปกติล็อคในเธรดเดียวกัน
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

pthread_mutex_t lock;


void func3(){
    printf("func3\n");
    pthread_mutex_lock(&lock);
    printf("func3 hey hey\n");
}

void * func1(void *arg){
    printf("thread1\n");
    pthread_mutex_lock(&lock);
    func3();
    printf("thread1 hey hey\n");

}


void * func2(void *arg){
    printf("thread2\n");
    pthread_mutex_lock(&lock);
    printf("thread2 hey hey\n");
}

int main(){
    pthread_mutexattr_t lock_attr;
    int error;
//    error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_RECURSIVE);
    error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_DEFAULT);
    if(error){
        perror(NULL);
    }

    pthread_mutex_init(&lock, &lock_attr);

    pthread_t t1, t2;

    pthread_create(&t1, NULL, func1, NULL);
    sleep(2); 
    pthread_create(&t2, NULL, func2, NULL);

    pthread_join(t2, NULL);

}

เอาต์พุต:

thread1
func3
thread2

การหยุดชะงักในthread t1, ในfunc3.
(ฉันใช้sleep(2)เพื่อให้ง่ายขึ้นเพื่อดูว่าการชะงักงันเกิดจากการล็อคใหม่func3)

  1. mutex แบบเรียกซ้ำล็อคซ้ำในเธรดเดียวกัน

อีกครั้งยกเลิกการใส่ข้อคิดเห็นบรรทัด mutex แบบเรียกซ้ำและแสดงความคิดเห็นในบรรทัดอื่น

เอาต์พุต:

thread1
func3
func3 hey hey
thread1 hey hey
thread2

การหยุดชะงักในthread t2, ในfunc2. ดู? func3เสร็จสิ้นและออกการล็อคใหม่ไม่ได้ปิดกั้นเธรดหรือนำไปสู่การหยุดชะงัก


คำถามสุดท้ายทำไมเราถึงต้องการ?

สำหรับฟังก์ชันเรียกซ้ำ (เรียกในโปรแกรมมัลติเธรดและคุณต้องการปกป้องทรัพยากร / ข้อมูลบางส่วน)

เช่นคุณมีโปรแกรมมัลติเธรดและเรียกใช้ฟังก์ชันวนซ้ำในเธรด A คุณมีข้อมูลบางอย่างที่ต้องการป้องกันในฟังก์ชันเรียกซ้ำนั้นดังนั้นคุณจึงใช้กลไก mutex การดำเนินการของฟังก์ชันนั้นเป็นลำดับในเธรด A ดังนั้นคุณจะต้องล็อก mutex ซ้ำในการเรียกซ้ำ ใช้ mutex ปกติทำให้เกิดการชะงักงัน และมีการคิดค้นmutexเพื่อแก้ปัญหานี้

ดูตัวอย่างจากคำตอบที่ยอมรับ เมื่อใดควรใช้การเรียกซ้ำ mutex .

Wikipedia อธิบาย mutex แบบเรียกซ้ำได้เป็นอย่างดี คุ้มค่าสำหรับการอ่าน Wikipedia: Reentrant_mutex

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.