std :: unique_lock <std :: mutex> หรือ std :: lock_guard <std :: mutex>?


349

ฉันมีสองกรณีใช้

A. ฉันต้องการซิงโครไนซ์การเข้าถึงโดยสองเธรดกับคิว

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

สำหรับกรณีที่ใช้ AI std::lock_guard<>ดูตัวอย่างการใช้รหัส สำหรับกรณีการใช้งาน BI std::unique_lock<>ดูตัวอย่างการใช้รหัส

อะไรคือความแตกต่างระหว่างทั้งสองกับสิ่งที่ฉันควรใช้ในกรณีที่ใช้?

คำตอบ:


344

std::unique_lockความแตกต่างคือที่คุณสามารถล็อคและปลดล็อค std::lock_guardจะถูกล็อคเพียงครั้งเดียวในการก่อสร้างและปลดล็อคเมื่อถูกทำลาย

ดังนั้นสำหรับการใช้งานกรณี B คุณต้องการstd::unique_lockตัวแปรเงื่อนไขอย่างแน่นอน ในกรณี A มันขึ้นอยู่กับว่าคุณต้องการใส่ตัวป้องกันใหม่หรือไม่

std::unique_lockมีคุณสมบัติอื่น ๆ ที่อนุญาตให้มันเช่น: สร้างโดยไม่ต้องล็อค mutex ทันที แต่เพื่อสร้าง Wrapper RAII (ดูที่นี่ )

std::lock_guardยังให้ wrapper RAII ที่สะดวก แต่ไม่สามารถล็อค mutex หลาย ๆ ชุดได้อย่างปลอดภัย มันสามารถใช้เมื่อคุณต้องการ wrapper สำหรับขอบเขตที่ จำกัด เช่น: ฟังก์ชั่นสมาชิก:

class MyClass{
    std::mutex my_mutex;
    void member_foo() {
        std::lock_guard<mutex_type> lock(this->my_mutex);            
        /*
         block of code which needs mutual exclusion (e.g. open the same 
         file in multiple threads).
        */

        //mutex is automatically released when lock goes out of scope           
};

เพื่อชี้แจงคำถามโดย chmike โดยค่าเริ่มต้นstd::lock_guardและstd::unique_lockเหมือนกัน ดังนั้นในกรณีข้างต้นคุณสามารถแทนที่ด้วยstd::lock_guard std::unique_lockอย่างไรก็ตามstd::unique_lockอาจมีค่าใช้จ่ายตาดเพิ่มเติม

โปรดทราบว่าวันนี้ควรใช้แทนstd::scoped_lockstd::lock_guard


2
ด้วยคำสั่ง std :: unique_lock <std :: mutex> lock (myMutex); mutex จะถูกล็อคโดยนวกรรมิกหรือไม่
chmike

3
@ chmike ใช่มันจะ เพิ่มคำอธิบายบางอย่าง
Stephan Dollberg

10
@ chike ดีฉันคิดว่ามันเป็นคำถามที่มีประสิทธิภาพน้อยกว่าฟังก์ชั่น ถ้าstd::lock_guardเพียงพอสำหรับกรณีของคุณ A คุณควรใช้มัน ไม่เพียง แต่จะหลีกเลี่ยงค่าใช้จ่ายที่ไม่จำเป็น แต่ยังแสดงให้เห็นถึงความตั้งใจของผู้อ่านว่าคุณจะไม่ปลดล็อคยามนี้
เตฟาน Dollberg

5
@chmike: ในทางทฤษฎีใช่ อย่างไรก็ตาม Mutices ไม่ใช่โครงสร้างที่มีน้ำหนักเบาดังนั้นค่าใช้จ่ายเพิ่มเติมของunique_lockมันน่าจะถูกแคระโดยค่าใช้จ่ายในการล็อคและปลดล็อก Mutex จริง ๆ (ถ้าคอมไพเลอร์ไม่ปรับค่าใช้จ่ายให้อยู่เหนือหัวซึ่งอาจเป็นไปได้)
Grizzly

6
So for usecase B you definitely need a std::unique_lock for the condition variable- ใช่แต่เฉพาะในเธรดที่cv.wait()s เพราะวิธีการนั้นปล่อย mutex แบบอะตอม ในเธรดอื่นที่คุณอัปเดตตัวแปรที่ใช้ร่วมกันจากนั้นเรียกcv.notify_one()ใช้เป็นวิธีง่าย ๆlock_guardในการล็อค mutex ในขอบเขต ... เว้นแต่คุณจะทำอะไรที่ซับซ้อนกว่าที่ฉันจินตนาการไม่ได้! เช่นen.cppreference.com/w/cpp/thread/condition_variable - เหมาะสำหรับฉัน :)
underscore_d

115

lock_guardและunique_lockเป็นสิ่งเดียวกัน; lock_guardเป็นรุ่นที่ จำกัด ด้วยอินเทอร์เฟซที่ จำกัด

A lock_guardเสมอล็อคจากการก่อสร้างเพื่อทำลายมัน unique_lockสามารถสร้างขึ้นได้โดยไม่ต้องทันทีล็อคสามารถปลดล็อคที่จุดใด ๆ ในการดำรงอยู่ของตนและสามารถโอนความเป็นเจ้าของล็อคจากที่หนึ่งไปยังอีกเช่น

ดังนั้นคุณจึงมักจะใช้ถ้าคุณต้องการความสามารถของlock_guard ต้องการunique_lockcondition_variableunique_lock


11
A condition_variable needs a unique_lock.- ใช่แต่เฉพาะwait()ด้านไอเอ็นจีตามที่อธิบายไว้ในความคิดเห็นของฉันไปยัง inf
underscore_d

48

ใช้lock_guardจนกว่าคุณจะต้องสามารถที่ตนเองunlockmutex lockในระหว่างโดยไม่ทำลาย

โดยเฉพาะcondition_variableปลดล็อก mutex เมื่อเข้าสู่โหมดwaitสลีป นั่นคือเหตุผลที่lock_guardไม่เพียงพอที่นี่


การส่ง lock_guard ไปยังหนึ่งในวิธีการรอของตัวแปรตามเงื่อนไขจะดีเพราะ mutex จะได้รับการ reacquired เสมอเมื่อการรอสิ้นสุดลงด้วยเหตุผลใดก็ตาม อย่างไรก็ตามมาตรฐานมีเพียงอินเตอร์เฟสสำหรับ unique_lock สิ่งนี้ถือได้ว่าเป็นข้อบกพร่องในมาตรฐาน
Chris Vine

3
@Chris คุณจะยังคงมีการห่อหุ้มในกรณีนี้ วิธีการรอจะต้องสามารถดึง mutex ออกจากlock_guardและปลดล็อกได้ดังนั้นจึงเป็นการทำลายค่าคงที่ในชั้นเรียนของผู้พิทักษ์ชั่วคราว แม้ว่าสิ่งนี้จะเกิดขึ้นกับผู้ใช้ที่มองไม่เห็นฉันจะพิจารณาว่าเหตุผลที่ถูกต้องตามกฎหมายสำหรับการไม่อนุญาตให้ใช้lock_guardในกรณีนี้
ComicSansMS

ถ้าเป็นเช่นนั้นมันจะมองไม่เห็นและไม่สามารถตรวจจับได้ gcc-4.8 ทำ รอ (unique_lock <mutex> &) โทร __gthread_cond_wait (& _ M_cond, __lock.mutex () -> native_handle ()) (ดู libstdc ++ - v3 / src / c ++ 11 / condition_variable.cc) ซึ่งเรียก pthread_cond_wait () (lib) /gthr-posix.h) เดียวกันสามารถทำได้สำหรับ lock_guard (แต่ไม่ใช่เพราะมันไม่ได้อยู่ในมาตรฐานสำหรับ condition_variable)
Chris Vine

4
@Chris จุดนี้lock_guardไม่อนุญาตให้เรียกข้อมูล mutex ที่สำคัญทั้งหมด นี่คือข้อ จำกัด โดยเจตนาที่จะช่วยให้เหตุผลง่ายเกี่ยวกับรหัสที่ใช้เมื่อเทียบกับรหัสที่ใช้lock_guard unique_lockวิธีเดียวที่จะบรรลุสิ่งที่คุณถามคือการตั้งใจห่อหุ้มlock_guardคลาสและเปิดเผยการนำไปใช้กับคลาสอื่น (ในกรณีนี้คือcondition_variable) นี่เป็นราคาที่ยากที่จะจ่ายสำหรับข้อได้เปรียบที่น่าสงสัยของผู้ใช้ตัวแปรเงื่อนไขโดยไม่ต้องจดจำความแตกต่างระหว่างล็อคสองประเภท
ComicSansMS

4
@Chris คุณได้ความคิดที่condition_variable_any.waitจะทำงานร่วมกับlock_guardที่ไหน? มาตรฐานต้องการประเภทการล็อคที่จัดให้เพื่อตอบสนองBasicLockableความต้องการ (§30.5.2) ซึ่งlock_guardไม่เป็นเช่นนั้น เฉพาะ mutex ที่เป็นรากฐานเท่านั้น แต่ด้วยเหตุผลที่ฉันชี้ให้เห็นก่อนหน้านี้อินเทอร์เฟซของlock_guardไม่ได้ให้การเข้าถึง mutex
ComicSansMS

11

มีสิ่งที่ร่วมกันบางอย่างระหว่างมีlock_guardและunique_lockและความแตกต่างบางอย่าง

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

lock_guardปรากฏการณ์นี้ผิดหลักการของ lock_guardสามารถสร้างได้เพียงครั้งเดียวและถูกทำลายเพียงครั้งเดียว

ดังนั้นจึงlock_guardไม่สามารถใช้ร่วมกับตัวแปรเงื่อนไขได้ แต่unique_lockสามารถ (เพราะunique_lockสามารถล็อคและปลดล็อคได้หลายครั้ง)


5
he compiler does not allow using a lock_guard in combination with a condition variableนี่เป็นเท็จ แน่นอนมันไม่อนุญาตให้มีและการทำงานที่ดีที่สุดที่มีlock_guardในnotify()ด้านไอเอ็นจี เฉพาะwait()ด้าน int เท่านั้นที่ต้องมีunique_lockเพราะwait()จะต้องปลดล็อคขณะตรวจสอบสภาพ
underscore_d

0

พวกมันไม่เหมือนกัน mutexes lock_guard<muType>เกือบจะเหมือนกันstd::mutexด้วยความแตกต่างที่ว่าอายุการใช้งานจะสิ้นสุดเมื่อสิ้นสุดขอบเขต (D-Tor ที่เรียกว่า) ดังนั้นคำจำกัดความที่ชัดเจนเกี่ยวกับ mutexes ทั้งสองนี้:

lock_guard<muType> มีกลไกสำหรับการเป็นเจ้าของ mutex ในช่วงระยะเวลาของบล็อกที่กำหนดขอบเขต

และ

unique_lock<muType> เป็น wrapper ที่อนุญาตให้ล็อครอการตัดการ จำกัด เวลาในการล็อคการล็อคซ้ำการโอนการเป็นเจ้าของการล็อกและใช้กับตัวแปรเงื่อนไข

นี่คือตัวอย่างการดำเนินการ:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <chrono>

using namespace std::chrono;

class Product{

   public:

       Product(int data):mdata(data){
       }

       virtual~Product(){
       }

       bool isReady(){
       return flag;
       }

       void showData(){

        std::cout<<mdata<<std::endl;
       }

       void read(){

         std::this_thread::sleep_for(milliseconds(2000));

         std::lock_guard<std::mutex> guard(mmutex);

         flag = true;

         std::cout<<"Data is ready"<<std::endl;

         cvar.notify_one();

       }

       void task(){

       std::unique_lock<std::mutex> lock(mmutex);

       cvar.wait(lock, [&, this]() mutable throw() -> bool{ return this->isReady(); });

       mdata+=1;

       }

   protected:

    std::condition_variable cvar;
    std::mutex mmutex;
    int mdata;
    bool flag = false;

};

int main(){

     int a = 0;
     Product product(a);

     std::thread reading(product.read, &product);
     std::thread setting(product.task, &product);

     reading.join();
     setting.join();


     product.showData();
    return 0;
}

ในตัวอย่างนี้ฉันใช้unique_lock<muType>กับcondition variable


-5

ตามที่ได้รับการกล่าวถึงจากผู้อื่น std :: unique_lock ติดตามสถานะการล็อคของ mutex ดังนั้นคุณสามารถเลื่อนการล็อคได้จนกว่าจะสร้างล็อคและปลดล็อคก่อนที่จะทำลายล็อค std :: lock_guard ไม่อนุญาตสิ่งนี้

ดูเหมือนว่าไม่มีเหตุผลใดที่ฟังก์ชัน std :: condition_variable wait ไม่ควรใช้ lock_guard เช่นเดียวกับ unique_lock เพราะเมื่อใดก็ตามที่การรอสิ้นสุดลง (ด้วยเหตุผลใดก็ตาม) Mutex จะได้รับการกู้คืนโดยอัตโนมัติเพื่อไม่ให้เกิดการละเมิดทางความหมาย อย่างไรก็ตามตามมาตรฐานการใช้ std :: lock_guard พร้อมกับตัวแปรเงื่อนไขคุณต้องใช้ std :: condition_variable_any แทน std :: condition_variable

แก้ไข : ลบแล้ว "การใช้อินเตอร์เฟส pthreads std :: condition_variable และ std :: condition_variable_any ควรเหมือนกัน" ในการดูการใช้งานของ gcc:

  • std :: condition_variable :: wait (std :: unique_lock &) เพียงแค่เรียก pthread_cond_wait () บนตัวแปรเงื่อนไข pthread ที่เกี่ยวกับ mutex ที่ถือโดย unique_lock (และสามารถทำเช่นเดียวกันสำหรับ lock_guard แต่ไม่เท่ากันเพราะมาตรฐาน ไม่ได้ให้สิ่งนั้น)
  • std :: condition_variable_any สามารถทำงานกับวัตถุที่ล็อคได้รวมถึงสิ่งที่ไม่ได้ล็อค mutex เลย (มันสามารถทำงานได้แม้จะมีสัญญาณระหว่างกระบวนการ)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.