1

なぜ作者はソースコードの以下の部分が競争につながると思うのですか?

著者は言う:

この設計は、キューからアイテムを削除するスレッドが複数ある場合、empty、front、popの呼び出し間の競合状態の影響を受けますが、単一コンシューマーシステム(ここで説明)では、これは問題ではありません。

コードは次のとおりです。

template<typename Data>
class concurrent_queue
{
private:
    std::queue<Data> the_queue;
    mutable boost::mutex the_mutex;
public:
    void push(const Data& data)
    {
        boost::mutex::scoped_lock lock(the_mutex);
        the_queue.push(data);
    }

    bool empty() const
    {
        boost::mutex::scoped_lock lock(the_mutex);
        return the_queue.empty();
    }

    Data& front()
    {
        boost::mutex::scoped_lock lock(the_mutex);
        return the_queue.front();
    }

    Data const& front() const
    {
        boost::mutex::scoped_lock lock(the_mutex);
        return the_queue.front();
    }

    void pop()
    {
        boost::mutex::scoped_lock lock(the_mutex);
        the_queue.pop();
    }
};
4

4 に答える 4

10

電話をかける場合emptyは、要素をポップしても安全かどうかを確認します。スレッド化されたシステムで発生する可能性があるのは、キューが空でないことを確認した後、別のスレッドがすでに最後の要素をポップしている可能性があり、キューが空でないことはもはや安全ではないということです。

thread A:                                 thread B:
if(!queue.empty());                            
                                          if(!queue.empty());

                                          queue.pop();

->it is no longer sure that the queue 
  isn't empty
于 2012-04-13T21:09:06.673 に答える
4

複数のスレッドがキューからデータを「消費」している場合、特に悪い方法で競合状態が発生する可能性があります。次の疑似コードを見てください。

class consumer
{
  void do_work()
  {
      if(!work_.empty())
      {
         type& t = work_.front();
         work_.pop();

         // do some work with t
         t...
      }
  }

  concurrent_queue<type> work_;
};

これは非常に単純に見えますが、複数のconsumerオブジェクトがあり、concurrent_queue. empty()を呼び出した後、 を呼び出す前にコンシューマが中断された場合、pop()潜在的に複数consumerの が同じオブジェクトで動作しようとします。

より適切な実装では、次のように、インターフェイスで公開されている単一の操作で空のチェックとポップを実行します。

class concurrent_queue
{
private:
    std::queue<Data> the_queue;
    mutable boost::mutex the_mutex;
public:
    void push(const Data& data)
    {
        boost::mutex::scoped_lock lock(the_mutex);
        the_queue.push(data);
    }

    bool pop(Data& popped)
    {
        boost::mutex::scoped_lock lock(the_mutex);
        if(!the_queue.empty())
        {
            popped = the_queue.front();
            the_queue.pop();
            return true;
        }

        return false;
    }
};
于 2012-04-13T21:10:26.143 に答える
3

こんなことができるから…

if (!your_concurrent_queue.empty())
    your_concurrent_queue.pop();

...そして、これらの 2 行の「間」で別のスレッドが呼び出されたpop場合、まだ失敗しています。pop

(実際にこれが実際に発生するかどうかは、並行スレッドの実行のタイミングに依存します。本質的にスレッドは「レース」を行い、誰がこのレースに勝つかによって、バグが現れるかどうかが決まります。これは、最新のプリエンプティブ OS では本質的にランダムです。このランダム性競合状態の診断と修復が非常に困難になる可能性があります。)

クライアントがこのような「メタ操作」を行う場合 (目的の効果を達成するための一連の呼び出しがある場合)、メソッド内ロックだけでは競合​​状態から保護することは不可能です。

いずれにせよ、クライアントは独自のロックを実行する必要があるため、パフォーマンス上の理由から、メソッド内ロックを放棄することを検討することもできます。これが明確に文書化されていることを確認して、スレッドセーフに関する約束をしていないことをクライアントが認識できるようにしてください。

于 2012-04-13T21:29:05.920 に答える
2

あなたが混乱しているのは、あなたが投稿したコードには、競合状態を引き起こすものは何もないということだと思います。競合状態は、スレッドが実際にこのコードを呼び出していることが原因で発生します。スレッド1がスレッドが空でないかどうかを確認するとします。その後、そのスレッドは1年間スリープ状態になります。1年後、ウェイクアップしたときに、そのスレッドがキューがまだ空であると想定することはまだ有効ですか?いいえ、その間に、別のスレッドが簡単にやって来て、プッシュと呼ばれる可能性があります。

于 2012-04-13T21:16:19.533 に答える