ある銀行口座から別の銀行口座に送金するという古典的な問題では、受け入れられている解決策 (私は信じています) は、ミューテックスを各銀行口座に関連付けてから、一方の口座からお金を引き出して他方の口座に入金する前に両方をロックすることです。一見すると、私は次のようにします。
class Account {
public:
void deposit(const Money& amount);
void withdraw(const Money& amount);
void lock() { m.lock(); }
void unlock() { m.unlock(); }
private:
std::mutex m;
};
void transfer(Account& src, Account& dest, const Money& amount)
{
src.lock();
dest.lock();
src.withdraw(amount);
dest.deposit(amount);
dest.unlock();
src.unlock();
}
しかし、手動ロック解除は臭いです。ミューテックスをパブリックにしてからstd::lock_guard
intransfer
で使用することもできますが、パブリック データ メンバーも臭いがします。
の要件std::lock_guard
は、その型がBasicLockableの要件を満たしていることです。これは、呼び出しが有効lock
であることだけです。その要件を満たしているので、直接使用できます。unlock
Account
std::lock_guard
Account
void transfer(Account& src, Account& dest, const Money& amount)
{
std::lock_guard<Account> g1(src);
std::lock_guard<Account> g2(dest);
src.withdraw(amount);
dest.deposit(amount);
}
これは問題ないように思えますが、この種のことをこれまでに見たことがなく、ミューテックスのロックとロック解除を複製することAccount
自体がちょっと臭いようです。
このようなシナリオで保護しているデータにミューテックスを関連付ける最良の方法は何ですか?
更新: 以下のコメントで、std::lock
デッドロックを回避するために を使用できることを指摘しましたが、( および の機能に加えて) 機能の存在に依存していることを見落としていましstd::lock
た。のインターフェースに追加することは、かなりひどいハックのようです。したがって、オブジェクトのミューテックスを に残すには、公開する必要があるようです。これはかなりの悪臭を放っています。try_lock
lock
unlock
try_lock
Account
Account
Account
いくつかの提案された解決策では、クライアントがラッパー クラスを使用してミューテックスをAccount
オブジェクトに静かに関連付けますが、私のコメントで述べたように、これにより、コードのさまざまな部分でさまざまなラッパー オブジェクトを使用しAccount
、それぞれが独自のオブジェクトを作成することが容易になるようです。これは、コードのさまざまな部分がさまざまなミューテックスを使用してロックしようとする可能性があることを意味しますAccount
。良くないね。
他の提案されたソリューションは、一度に 1 つのミューテックスのみをロックすることに依存しています。これにより、複数のミューテックスをロックする必要がなくなりますが、一部のスレッドがシステムの一貫性のないビューを参照できるようになります。本質的に、これは複数のオブジェクトを含む操作のトランザクション セマンティクスを放棄します。
この時点で、パブリック ミューテックスは利用可能なオプションの中で最も悪臭が少ないように見え始めており、それは私が本当に言いたくない結論です。本当に良いものはありませんか?