Paylaşılan_mutex'i artırma örneği (birden çok okuma / bir yazma)?


116

Bazı verileri sık sık okuması gereken çok iş parçacıklı bir uygulamam var ve bazen bu veriler güncelleniyor. Şu anda bir muteks bu verilere güvenli bir şekilde erişmeye devam ediyor, ancak bu pahalı çünkü birden fazla iş parçacığının aynı anda okuyabilmesini ve yalnızca bir güncelleme gerektiğinde bunları kilitlemesini istiyorum (güncelleme iş parçacığı diğer iş parçacıklarının bitmesini bekleyebilir) .

Sanırım boost::shared_mutexyapılması gereken bu, ancak nasıl kullanılacağı konusunda net değilim ve net bir örnek bulamadım.

Başlamak için kullanabileceğim basit bir örnek olan var mı?


1800 BİLGİ'nin örneği doğrudur. Ayrıca şu makaleye bakın: Boost Threads'teki yenilikler .
Assaf Lavie

Yanıtlar:


102

Görünüşe göre böyle bir şey yapacaksın:

boost::shared_mutex _access;
void reader()
{
  // get shared access
  boost::shared_lock<boost::shared_mutex> lock(_access);

  // now we have shared access
}

void writer()
{
  // get upgradable access
  boost::upgrade_lock<boost::shared_mutex> lock(_access);

  // get exclusive access
  boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
  // now we have exclusive access
}

7
Bu, boost'u ilk kullanışım ve ben bir C ++ acemiyim, bu yüzden belki eksik olduğum bir şey var - ama kendi kodumda türü belirtmem gerekiyor, şöyle: boost :: shared_lock <shared_mutex> lock (_Giriş);
Ken Smith

2
Bunu kendim kullanmaya çalışıyorum ama bir hata alıyorum. 'kilitlemeden önce eksik şablon bağımsız değişkenleri. Herhangi bir fikir?
Matt

2
@shaz Bunlar kapsamlı, ancak ihtiyacınız olursa .unlock () ile bunları erkenden serbest bırakabilirsiniz.
mmocny

4
Eksik şablon bağımsız değişkenlerini ekledim.

1
@raaj, upgrade_lock'u alabilirsiniz, ancak benzersiz bir kilide yükseltme, shared_lock serbest bırakılıncaya kadar engellenecektir
1800 BİLGİ

166

1800 BİLGİ aşağı yukarı doğru, ancak düzeltmek istediğim birkaç sorun var.

boost::shared_mutex _access;
void reader()
{
  boost::shared_lock< boost::shared_mutex > lock(_access);
  // do work here, without anyone having exclusive access
}

void conditional_writer()
{
  boost::upgrade_lock< boost::shared_mutex > lock(_access);
  // do work here, without anyone having exclusive access

  if (something) {
    boost::upgrade_to_unique_lock< boost::shared_mutex > uniqueLock(lock);
    // do work here, but now you have exclusive access
  }

  // do more work here, without anyone having exclusive access
}

void unconditional_writer()
{
  boost::unique_lock< boost::shared_mutex > lock(_access);
  // do work here, with exclusive access
}

Ayrıca, bir shared_lock'tan farklı olarak, yalnızca tek bir iş parçacığının, yükseltilmediğinde bile (bununla karşılaştığımda garip olduğunu düşündüğüm) bir yükseltme_ kilidi elde edebileceğini unutmayın. Öyleyse, tüm okuyucularınız koşullu yazarlarsa, başka bir çözüm bulmanız gerekir.


1
Sadece "başka bir çözüm" hakkında yorum yapmak için. Koşullu yazarların olduğu tüm okuyucularım, onlara her zaman bir shared_lock sahip olmak olduğunda ve ayrıcalıkları yazmak için yükseltmem gerektiğinde, okuyucu kilidini .unlock () ve yeni bir unique_lock elde ettim. Bu, uygulamanızın mantığını karmaşıklaştıracak ve artık diğer yazarların durumu ilk okuduğunuz zamandan değiştirmesi için bir fırsat penceresi var.
mmocny

8
Çizgi olmamalı boost::unique_lock< boost::shared_mutex > lock(lock);okumak boost::unique_lock< boost::shared_mutex > lock( _access ); ?
SteveWilkinson

4
Bu son uyarı çok garip. Bir seferde yalnızca bir iş parçacığı bir upgrade_lock'u tutabiliyorsa, upgrade_lock ile unique_lock arasındaki fark nedir?
Ken Smith

2
@Ken Çok net değildim, ancak upgrade_lock'un yararı, şu anda edinilmiş bazı paylaşılan_ kilitler varsa (en azından benzersiz olana yükseltene kadar) engellememesi. Bununla birlikte, bir upgrade_lock'u denemek ve elde etmek için ikinci iş parçacığı, ilk beklemediğim benzersizliğe yükseltilmemiş olsa bile engellenecektir.
mmocny

6
Bu bilinen bir güçlendirme sorunudur. Yükseltme
Ofek Shilon

47

C ++ 17'den (VS2015) bu yana okuma-yazma kilitleri için standardı kullanabilirsiniz:

#include <shared_mutex>

typedef std::shared_mutex Lock;
typedef std::unique_lock< Lock > WriteLock;
typedef std::shared_lock< Lock > ReadLock;

Lock myLock;


void ReadFunction()
{
    ReadLock r_lock(myLock);
    //Do reader stuff
}

void WriteFunction()
{
     WriteLock w_lock(myLock);
     //Do writer stuff
}

Daha eski sürüm için, aynı sözdizimi ile artırmayı kullanabilirsiniz:

#include <boost/thread/locks.hpp>
#include <boost/thread/shared_mutex.hpp>

typedef boost::shared_mutex Lock;
typedef boost::unique_lock< Lock >  WriteLock;
typedef boost::shared_lock< Lock >  ReadLock;

5
Ben de söyleyebilirim typedef boost::unique_lock< Lock > WriteLock; typedef boost::shared_lock< Lock > ReadLock;.
üzüm

6
Thread.hpp'nin tamamını eklemenize gerek yoktur. Sadece kilitlere ihtiyacınız varsa, kilitleri de dahil edin. Bu dahili bir uygulama değildir. İçeriği minimumda tutun.
Yochai Timmer

5
Kesinlikle en basit uygulama, ancak hem mutekslere hem de kilitlere Kilitler olarak atıfta bulunmak kafa karıştırıcı olduğunu düşünüyorum. Muteks bir mutekstir, kilit ise onu kilitli durumda tutan bir şeydir.
Tim MB

17

Daha fazla deneysel bilgi eklemek için, yükseltilebilir kilitlerin tüm sorununu araştırıyorum ve paylaşılan_mutex'i artırmak için Örnek (birden fazla okuma / bir yazma)? sadece bir iş parçacığının yükseltilmemiş olsa bile bir upgrade_lock'a sahip olabileceğine dair önemli bilgileri ekleyen iyi bir cevaptır, bu önemlidir, çünkü önce paylaşılan kilidi serbest bırakmadan paylaşılan bir kilitten benzersiz bir kilide yükseltemezsiniz. (Bu başka bir yerde tartışılmıştır, ancak en ilginç konu burada http://thread.gmane.org/gmane.comp.lib.boost.devel/214394 )

Bununla birlikte, bir kilide yükseltmeyi bekleyen bir iş parçacığı (yani, tüm okuyucuların paylaşılan kilidi serbest bırakmasını beklemesi gerekir) ile aynı şeyi bekleyen bir yazıcı kilidi (yani bir benzersiz kilit) arasında önemli (belgelenmemiş) bir fark buldum.

  1. Shared_mutex üzerinde bir unique_lock bekleyen iş parçacığı, gelen yeni okuyucuları bloke eder, yazarların talebini beklemeleri gerekir. Bu, okuyucuların yazarları aç bırakmamasını sağlar (ancak yazarların okuyucuları aç bırakabileceğine inanıyorum).

  2. Upgradeable_lock'un yükseltilmesini bekleyen iş parçacığı, diğer iş parçacıklarının paylaşılan bir kilit almasına izin verir, bu nedenle okuyucular çok sıksa bu iş parçacığı aç bırakılabilir.

Bu, dikkate alınması gereken önemli bir konudur ve muhtemelen belgelenmelidir.


3
Terekhov algorithmİçinde olmasını sağlar 1., yazar okuyucuları açlıktan olamaz. Bunu gör . Ama 2.doğrudur. Bir upgrade_lock adaleti garanti etmez. Bunu gör .
JonasVautherin

2

Okuyucu sayısına eşit bir sayıya sahip bir semafor kullanın. Her okuyucunun, okumak için semaforun bir sayısını almasına izin verin, böylece hepsi aynı anda okuyabilir. Sonra yazarın yazmadan önce TÜM semafor sayılarını almasına izin verin. Bu, yazarın tüm okumaların bitmesini beklemesine ve ardından yazarken okumaları engellemesine neden olur.


(1) Bir yazarın sayıyı atomik olarak keyfi bir miktarda azaltmasını nasıl sağlarsınız ? (2) Yazar sayacı bir şekilde sıfıra düşürürse, zaten çalışan okuyucuların yazmadan önce bitirmesini nasıl bekler?
Ofek Shilon

Kötü fikir: İki yazar aynı anda erişmeye çalışırsa, bir kilitlenme yaşayabilirsiniz.
Caduchon

2

Jim Morris'in harika cevabı, bununla karşılaştım ve anlamam biraz zaman aldı. Unique_lock boost (sürüm 1.54) için bir "istek" gönderdikten sonra tüm shared_lock isteklerini engellediğini gösteren bazı basit kod. Bu bana çok ilginç geliyor çünkü unique_lock ve upgradeable_lock arasında seçim yapmak, yazma önceliği istiyorsak veya hiç önceliğimiz yoksa.

Ayrıca Jim Morris'in gönderisindeki (1) şununla çelişiyor gibi görünüyor: Boost shared_lock. Tercih edilen okundu mu?

#include <iostream>
#include <boost/thread.hpp>

using namespace std;

typedef boost::shared_mutex Lock;
typedef boost::unique_lock< Lock > UniqueLock;
typedef boost::shared_lock< Lock > SharedLock;

Lock tempLock;

void main2() {
    cout << "10" << endl;
    UniqueLock lock2(tempLock); // (2) queue for a unique lock
    cout << "11" << endl;
    boost::this_thread::sleep(boost::posix_time::seconds(1));
    lock2.unlock();
}

void main() {
    cout << "1" << endl;
    SharedLock lock1(tempLock); // (1) aquire a shared lock
    cout << "2" << endl;
    boost::thread tempThread(main2);
    cout << "3" << endl;
    boost::this_thread::sleep(boost::posix_time::seconds(3));
    cout << "4" << endl;
    SharedLock lock3(tempLock); // (3) try getting antoher shared lock, deadlock here
    cout << "5" << endl;
    lock1.unlock();
    lock3.unlock();
}

Aslında, [ stackoverflow.com/questions/12082405/… ' deki kod çalışırken yukarıdaki kodun neden kilitlendiğini anlamakta güçlük çekiyorum .
dale1209

1
Aslında (3) 'te değil (2)' de kilitleniyor, çünkü (2) (1) 'in kilidini açmasını bekliyor. Unutmayın: benzersiz bir kilit elde etmek için mevcut tüm paylaşılan kilitlerin bitmesini beklemeniz gerekir.
JonasVautherin

@JonesV, (2) tüm paylaşılan kilitlerin bitmesini beklese bile, bu bir kilitlenme olmaz çünkü bu, edinilen (1) 'den farklı bir iş parçacığıdır, eğer (3) satırı yoksa, program kilitlenme olmadan bitirin.
SagiLow
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.