std :: unique_lock <std :: mutex> veya std :: lock_guard <std :: mutex>?


349

İki kullanım durumum var.

A. İki iş parçacığı tarafından bir kuyruğa erişimi eşitlemek istiyorum.

B. İki kuyruğun erişimini bir kuyruğa eşitlemek ve bir koşul değişkeni kullanmak istiyorum, çünkü iş parçacıklarından biri diğer iş parçacığı tarafından kuyruğa kaydedilecek içerikte bekler.

AI kullanımı örneği için kod örneğine bakın std::lock_guard<>. Kullanım senaryosu BI için kod örneğine bakınız std::unique_lock<>.

İkisi arasındaki fark nedir ve hangi kullanım durumunda hangisini kullanmalıyım?

Yanıtlar:


344

Aradaki fark, kilitleyip kilidini açabilmenizdir std::unique_lock. std::lock_guardinşaatta sadece bir kez kilitlenir ve yıkım sırasında kilidi açılır.

Bu nedenle, kullanım durumu B std::unique_lockiçin koşul değişkeni için kesinlikle bir a ihtiyacınız vardır . A durumunda, korumayı yeniden kilitlemeniz gerekip gerekmediğine bağlıdır.

std::unique_lockörneğin, muteksi hemen kilitlemeden, ancak RAII sargısını oluşturmak için inşa edilmesine izin veren başka özelliklere de sahiptir ( buraya bakın ).

std::lock_guardayrıca uygun bir RAII sarmalayıcı sağlar, ancak birden fazla muteksi güvenli bir şekilde kilitleyemez. Sınırlı bir kapsam için bir sargıya ihtiyacınız olduğunda kullanılabilir, örneğin: bir üye işlevi:

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           
};

Bir soruyu chmike ile açıklığa kavuşturmak için, varsayılan olarak std::lock_guardve std::unique_lockaynıdır. Yani yukarıdaki durumda, std::lock_guardile değiştirebilirsiniz std::unique_lock. Ancak, std::unique_lockbiraz daha fazla yük olabilir.

Bu günlerde std::scoped_lockyerine kullanmak gerektiğini unutmayın std::lock_guard.


2
Std :: unique_lock <std :: mutex> lock (myMutex) komutuyla; muteks kurucu tarafından kilitlenecek mi?
chmike

3
@chmike Evet, olacak. Biraz açıklama eklendi.
Stephan Dollberg

10
@chmike Bence bu işlevsellikten daha az bir verimlilik meselesi. Eğer std::lock_guardsizin vakası için yeterlidir, o zaman bunu kullanmalıdır. Sadece gereksiz ek yükü önlemekle kalmaz, aynı zamanda okuyucuya bu koruyucunun kilidini asla açmayacağınızı da gösterir.
Stephan Dollberg

5
@chmike: Teorik olarak evet. Ancak Mutices tam olarak hafif yapılar değildir, bu nedenle ek yükünün unique_lockmuteksi gerçekten kilitleme ve kilidini açma maliyeti ile cüce olması muhtemeldir (derleyici bu ek yükü optimize etmemişse, bu mümkün olabilir).
Grizzly

6
So for usecase B you definitely need a std::unique_lock for the condition variable- evet ama sadece o iş parçacığında cv.wait(), çünkü bu yöntem atomik olarak muteksi serbest bırakır. Paylaşılan değişkenleri güncelleyip aradığınız diğer iş parçacığında , mutex kapsamını kilitlemek için cv.notify_one()basit bir lock_guardyeterlilik ... hayal edemediğim daha ayrıntılı bir şey yapmadıkça! örn. en.cppreference.com/w/cpp/thread/condition_variable - benim için çalışıyor :)
underscore_d

115

lock_guardve unique_lockhemen hemen aynı şeydir; lock_guardsınırlı arayüzü olan kısıtlı bir versiyonudur.

A lock_guard, yapısından yıkımına kadar her zaman bir kilit tutar. A unique_lockhemen kilitlenmeden oluşturulabilir, var olduğu herhangi bir noktada kilidini açabilir ve kilidin sahipliğini bir örnekten diğerine aktarabilir.

Yani lock_guardyeteneklerine ihtiyacınız olmadığı sürece her zaman kullanırsınız unique_lock. A condition_variableihtiyaçları a unique_lock.


11
A condition_variable needs a unique_lock.- evet ama sadece wait()ing tarafında, inf yorumum ayrıntılı olarak.
underscore_d

48

lock_guardEğer unlockaradaki muteksi yok etmeden elle yapabilmeniz gerekmedikçe kullanın lock.

Özellikle, condition_variableçağrılarda uyurken muteksinin kilidini açar wait. Bu yüzden a lock_guardburada yeterli değildir.


Koşullu değişkenin bekleme yöntemlerinden birine lock_guard iletmek iyi olur, çünkü bekleme süresi ne zaman olursa olsun, mutex her zaman yeniden gerekir. Ancak standart sadece unique_lock için bir arayüz sağlar. Bu standartta bir eksiklik olarak görülebilir.
Chris Vine

3
@Chris Bu durumda hala kapsüllemeyi kesiyordunuz. Wait yönteminin muteksi çıkarması lock_guardve kilidini açması , böylece geçici olarak muhafızların sınıf değişmezini kırması gerekir. Bu kullanıcı için görünmez olsa da, bu durumda kullanılmasına izin vermemek için meşru bir neden olduğunu düşünürdüm lock_guard.
ComicSansMS

Eğer öyleyse, görünmez ve tespit edilemez. gcc-4.8 bunu yapar. bekleyin (unique_lock <mutex> &) __gthread_cond_wait (& _ M_cond, __lock.mutex () -> native_handle ()) çağrılarına bakın (bkz. libstdc ++ - v3 / src / c ++ 11 / condition_variable.cc) (bkz. libstdc ++ - v3 / src / c ++ 11 / condition_variable.cc) (bkz. libgdcc /gthr-posix.h). Aynı şey lock_guard için de yapılabilir (ancak condition_variable için standartta olmadığı için değil).
Chris Vine

4
@Chris Buradaki nokta, lock_guardaltta yatan muteksin alınmasına hiç izin vermiyor. Bu, a kullanan lock_guardkodun aksine kullanılan kod hakkında daha basit bir akıl yürütmeye izin vermek için kasıtlı bir sınırlamadır unique_lock. Sorduğunuza ulaşmanın tek yolu, lock_guardsınıfın kapsüllenmesini kasıtlı olarak kırmak ve uygulanmasını farklı bir sınıfa (bu örnekte condition_variable) maruz bırakmaktır . Bu, iki kilit tipi arasındaki farkı hatırlamak zorunda olmayan bir koşul değişkeninin kullanıcının şüpheli avantajını ödemek için zor bir fiyattır.
ComicSansMS

4
@Chris Bir condition_variable_any.waitile çalışacak fikri nereden buldunuz lock_guard? Standart karşılamak için sağlanan Kilit türünü gerektirir BasicLockablegereksinimi (§30.5.2), lock_guardyapmaz. Sadece altta yatan muteks yapar, ancak daha önce belirttiğim nedenlerden dolayı, arayüzü lock_guardmutekse erişim sağlamaz.
ComicSansMS

11

Arasında bazı ortak şeyler vardır lock_guardve unique_lockve bazı farklılıklara.

Ancak, sorulan soru bağlamında, derleyici lock_guardbir koşul değişkeni ile birlikte kullanılmasına izin vermez , çünkü bir iş parçacığı çağrıları bir koşul değişkenini beklediğinde, muteks otomatik olarak açılır ve diğer iş parçacığı / iş parçacıkları bildirildiğinde ve geçerli iş parçacığı çağrılır (beklemeden çıkar), kilit yeniden kazanılır.

Bu fenomen prensibine aykırıdır lock_guard. lock_guardsadece bir kez inşa edilebilir ve sadece bir kez yok edilebilir.

Bu nedenle lock_guardbir koşul değişkeni ile birlikte kullanılamaz, ancak a unique_lock( unique_lockbirkaç kez kilitlenip açılabilir) olabilir.


5
he compiler does not allow using a lock_guard in combination with a condition variableBu yanlış. Kesinlikle gelmez izin ve mükemmel çalışma lock_guardüzerinde notify()ing tarafı. Sadece wait()int tarafı a gerektirir unique_lock, çünkü wait()durumu kontrol ederken kilidi serbest bırakmalıdır.
underscore_d

0

Gerçekten aynı muteksler değiller lock_guard<muType>, neredeyse aynıdırlar, std::mutexyaşamın kapsamın sonunda bittiği bir farkla (D-tor denir), bu iki muteks hakkında net bir tanım:

lock_guard<muType> bir kapsam bloğunun süresi boyunca muteks sahibi olmak için bir mekanizmaya sahiptir.

Ve

unique_lock<muType> ertelenmiş kilitleme, kilitleme için zaman kısıtlı girişimler, yinelemeli kilitleme, kilit sahipliğinin aktarılması ve koşul değişkenleriyle kullanıma izin veren bir sargıdır.

Örnek bir uygulama:

#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;
}

Bu örnekte, i kullanılan unique_lock<muType>ilecondition variable


-5

Başkaları tarafından belirtildiği gibi, std :: unique_lock muteksin kilitli durumunu izler, böylece kilidin oluşturulmasından sonraya kadar kilitlemeyi erteleyebilir ve kilidin imha edilmesinden önce kilidini açabilirsiniz. std :: lock_guard buna izin vermiyor.

Std :: condition_variable wait işlevlerinin neden bir lock_guard yanı sıra unique_lock almaması için hiçbir neden yoktur, çünkü bir bekleme sona erdiğinde (hangi nedenle olursa olsun) muteks otomatik olarak yeniden alınır, böylece herhangi bir anlamsal ihlale neden olmaz. Ancak standarda göre, koşul değişkeni ile std :: lock_guard kullanmak için std :: condition_variable yerine std :: condition_variable_any kullanmanız gerekir.

Düzenleme : silindi "pthreads arabirimini kullanarak std :: condition_variable ve std :: condition_variable_any aynı olmalı". Gcc'nin uygulanmasına bakıldığında:

  • std :: condition_variable :: wait (std :: unique_lock &), unique_lock tarafından tutulan muteks ile ilgili olarak altta yatan pthread koşulu değişkeninde pthread_cond_wait () 'i çağırır (ve böylece lock_guard için aynı şeyi yapabilir, ancak standart bunu sağlamaz)
  • std :: condition_variable_any mutex kilidi olmayan biri de dahil olmak üzere herhangi bir kilitlenebilir nesne ile çalışabilir (bu nedenle süreçler arası semaforla bile çalışabilir)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.