shared_mutexのブーストの例(複数の読み取り/ 1つの書き込み)?


116

いくつかのデータを頻繁に読み取らなければならないマルチスレッドアプリがあり、そのデータが更新されることもあります。現在、ミューテックスはそのデータへのアクセスを安全に保ちますが、複数のスレッドが同時に読み取れるようにし、更新が必要な場合にのみロックアウトする必要があるため、コストがかかります(更新スレッドは他のスレッドが完了するのを待つことができます) 。

これはboost::shared_mutex本来あるべきことだと思いますが、使い方がよく分からず、具体的な例もわかりません。

開始するために使用できる簡単な例はありますか?


1800 INFORMATIONの例は正しいです。:また、この記事を参照してくださいブーストスレッドの新機能を
Assaf Lavie

回答:


102

あなたはこのようなことをするように見えます:

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
これはブーストを初めて使用するので、C ++の初心者なので、何か足りないものがあるかもしれません-しかし、私のコードでは、次のようにタイプを指定する必要がありました:boost :: shared_lock <shared_mutex> lock (_アクセス);
ケン・スミス

2
これを自分で使用しようとしていますが、エラーが発生します。「ロック」の前にテンプレート引数がありません。何か案は?
マット

2
@shazこれらはスコープ付きですが、必要に応じて.unlock()で早期に解放できます。
mmocny 2011年

4
不足しているテンプレート引数を追加しました。

1
@raajはupgrade_lockを取得できますが、一意のロックにアップグレードすると、shared_lockが解放されるまでブロックされます
1800情報

166

1800 INFORMATIONは多かれ少なかれ正しいですが、私が修正したかったいくつかの問題があります。

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
}

また、shared_lockとは異なり、upgrade_lockを一度に取得できるのは、upgrade_lockがアップグレードされていない場合でも(単一のスレッドのみです)。したがって、すべてのリーダーが条件付きライターである場合、別の解決策を見つける必要があります。


1
「別の解決策」についてコメントするだけです。条件付きライターを使用するすべてのリーダーが私がしたことは、常にshared_lockを取得し、特権を書き込むようにアップグレードする必要がある場合、リーダーロックを.unlock()して、新しいunique_lockを取得することです。これにより、アプリケーションのロジックが複雑になり、他のライターが最初に読んだときから状態を変更できるようになります。
mmocny 2011年

8
boost::unique_lock< boost::shared_mutex > lock(lock);boost::unique_lock< boost::shared_mutex > lock( _accessを 読み取るべきではありません);か?
SteveWilkinson、2011

4
その最後の警告は非常に奇妙です。一度に1つのスレッドのみがupgrade_lockを保持できる場合、upgrade_lockとunique_lockの違いは何ですか?
ケン・スミス

2
@ケン私は明確ではありませんでしたが、upgrade_lockの利点は、獲得されたshared_lockが現在ある場合にブロックされないことです(少なくとも、uniqueにアップグレードするまでは)。ただし、最初のスレッドがユニークにアップグレードされていなくても、upgrade_lockを取得しようとする2番目のスレッドはブロックされます。
mmocny 2011

6
これはブーストの既知の問題です。ブースト1.50ベータで解決されたようです:svn.boost.org/trac/boost/ticket/5516
Ofek

47

C ++ 17(VS2015)以降、読み取り/書き込みロックの標準を使用できます。

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

古いバージョンでは、同じ構文でboostを使用できます。

#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
私も言いtypedef boost::unique_lock< Lock > WriteLock; typedef boost::shared_lock< Lock > ReadLock;ます。
2012年

6
thread.hpp全体を含める必要はありません。ロックのみが必要な場合は、ロックを含めます。これは内部実装ではありません。インクルードを最小限に抑えます。
Yochai Timmer 14

5
間違いなく最も単純な実装ですが、ミューテックスとロックの両方をロックと呼ぶのは紛らわしいと思います。ミューテックスはミューテックスであり、ロックはロックされた状態でそれを維持するものです。
Tim MB

17

いくつかの実証的な情報を追加するために、アップグレード可能なロックの問題全体を調査しており、boost shared_mutex(複数の読み取り/ 1つの書き込み)の例を教えてください。アップグレードされていなくても1つのスレッドのみがupgrade_lockを持つことができるという重要な情報を追加することは良い回答です。最初に共有ロックを解放しないと、共有ロックから一意のロックにアップグレードできないことは重要です。(これは他の場所で議論されましたが、最も興味深いスレッドはここhttp://thread.gmane.org/gmane.comp.lib.boost.devel/214394です

ただし、ロックへのアップグレードを待機しているスレッド(つまり、すべてのリーダーが共有ロックを解放するのを待つ必要がある)と、同じものを待機しているライターロック(unique_lock)との重要な(文書化されていない)違いを見つけました。

  1. shared_mutexのunique_lockを待機しているスレッドは、新しいリーダーの受信をブロックし、ライターの要求を待機する必要があります。これは、読者がライターを飢えさせないことを保証します(ただし、ライターが読者を餓死させる可能性があると私は信じています)。

  2. upgradeable_lockのアップグレードを待機しているスレッドは、他のスレッドが共有ロックを取得できるようにするため、リーダーが非常に頻繁である場合、このスレッドは不足する可能性があります。

これは考慮すべき重要な問題であり、おそらく文書化する必要があります。


3
Terekhov algorithmことを保証し1.、作家が読者に餓死することはできません。参照してくださいこれを。しかし2.本当です。upgrade_lockは公平性を保証しません。参照してくださいこれを
JonasVautherin 2014

2

リーダーの数と同じ数のセマフォを使用します。各リーダーがセマフォを1つカウントして読み取るようにします。これにより、すべてのユーザーが同時に読み取ることができます。次に、書き込みを行う前に、ライターにすべてのセマフォカウントを取得させます。これにより、ライターはすべての読み取りが完了するまで待機し、書き込み中は読み取りをブロックします。


(1)ライターにカウントを任意の量だけアトミックにデクリメントするにはどうすればよいですか?(2)ライターがカウントを何らかの方法でゼロにデクリメントする場合、書き込み前に、すでに実行中のリーダーが完了するのをどのように待機しますか?
Ofek Shilon

悪い考え:2人のライターが同時にアクセスしようとすると、デッドロックが発生する可能性があります。
Caduchon 2016年

2

ジム・モリスの素晴らしい反応で、私はこれに偶然出会い、理解するのにしばらく時間がかかりました。これは、unique_lockブースト(バージョン1.54)の「リクエスト」を送信した後、すべてのshared_lockリクエストをブロックすることを示す簡単なコードです。これは、書き込み優先度または優先度なしが必要な場合に、unique_lockとupgradeable_lockのどちらかを選択できるように思えるため、非常に興味深いものです。

また、(1)ジムモリスの投稿ではこれと矛盾しているようです: Boost shared_lock。優先的に読みますか?

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

[ stackoverflow.com/questions/12082405/…のコードが機能しているのに、なぜ上記のコードがデッドロックするのか理解できません。
dale1209 2013年

1
(2)は(1)がロックを解放するのを待っているため、実際には(3)ではなく(2)でデッドロックします。覚えておいてください:一意のロックを取得するには、既存のすべての共有ロックが完了するまで待つ必要があります。
JonasVautherin 2014

@JonesV、(2)がすべての共有ロックが完了するのを待っても、(1)を取得したスレッドとは別のスレッドであるため、デッドロックにはなりません。行(3)が存在しない場合、プログラムはデッドロックなしで終了します。
SagiLow 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.