私が取り組んでいるプロジェクトは、複数のスレッドを使用してファイルのコレクションを処理します。各スレッドは、処理するファイルのリストにファイルを追加できるため、スレッドセーフなキュー(考えていたもの)をまとめました。関連する部分は次のとおりです。
// qMutex is a std::mutex intended to guard the queue
// populatedNotifier is a std::condition_variable intended to
// notify waiting threads of a new item in the queue
void FileQueue::enqueue(std::string&& filename)
{
std::lock_guard<std::mutex> lock(qMutex);
q.Push(std::move(filename));
// Notify anyone waiting for additional files that more have arrived
populatedNotifier.notify_one();
}
std::string FileQueue::dequeue(const std::chrono::milliseconds& timeout)
{
std::unique_lock<std::mutex> lock(qMutex);
if (q.empty()) {
if (populatedNotifier.wait_for(lock, timeout) == std::cv_status::no_timeout) {
std::string ret = q.front();
q.pop();
return ret;
}
else {
return std::string();
}
}
else {
std::string ret = q.front();
q.pop();
return ret;
}
}
ただし、時折if (...wait_for(lock, timeout) == std::cv_status::no_timeout) { }
ブロック内でセグメンテーション違反が発生し、gdbの検査では、キューが空であるためセグメンテーション違反が発生していることが示されています。これはどのように可能ですか? wait_for
は通知されたときにのみcv_status::no_timeout
を返すことを理解しており、FileQueue::enqueue
が新しいアイテムをキューにプッシュした後にのみ発生するはずです。
標準によれば、condition_variables
は、イベントが発生していなくても、誤ってウェイクアップすることが許可されています。スプリアスウェイクアップの場合、通知されていなくても、cv_status::no_timeout
を返します(タイムアウトではなく目覚めたため)。これに対する正しい解決策は、当然、手順を実行する前に、実際にウェイクアップが合法であったかどうかをチェックすることです。
詳細は、標準の§30.5.1[thread.condition.condvar]で指定されています。
-関数は、notify_one()の呼び出し、notify_all()の呼び出し、abs_timeで指定された絶対タイムアウト(30.2.4)の期限切れ、または誤って通知されたときにブロックを解除します。
...
戻り値: abs_timeで指定された絶対タイムアウト(30.2.4)が期限切れの場合はcv_status :: timeout、その他の場合はcv_status :: no_timeout。
それを見て、条件変数をチェックするときは、whileループを使用するのが最善です(そのため、ウェイクループが起きてまだ無効でない場合は、再度チェックします)。非同期キューのテンプレートを作成しました。これが役立つことを願っています。
#ifndef SAFE_QUEUE
#define SAFE_QUEUE
#include <queue>
#include <mutex>
#include <condition_variable>
// A threadsafe-queue.
template <class T>
class SafeQueue
{
public:
SafeQueue(void)
: q()
, m()
, c()
{}
~SafeQueue(void)
{}
// Add an element to the queue.
void enqueue(T t)
{
std::lock_guard<std::mutex> lock(m);
q.Push(t);
c.notify_one();
}
// Get the "front"-element.
// If the queue is empty, wait till a element is avaiable.
T dequeue(void)
{
std::unique_lock<std::mutex> lock(m);
while(q.empty())
{
// release lock as long as the wait and reaquire it afterwards.
c.wait(lock);
}
T val = q.front();
q.pop();
return val;
}
private:
std::queue<T> q;
mutable std::mutex m;
std::condition_variable c;
};
#endif
これはおそらくあなたがそれを行うべき方法です:
void Push(std::string&& filename)
{
{
std::lock_guard<std::mutex> lock(qMutex);
q.Push(std::move(filename));
}
populatedNotifier.notify_one();
}
bool try_pop(std::string& filename, std::chrono::milliseconds timeout)
{
std::unique_lock<std::mutex> lock(qMutex);
if(!populatedNotifier.wait_for(lock, timeout, [this] { return !q.empty(); }))
return false;
filename = std::move(q.front());
q.pop();
return true;
}
受け入れられた答えに加えて、正しいマルチプロデューサー/マルチコンシューマーキューを実装するのは難しいと言います(ただし、C++ 11以降は簡単です)
(非常に良い) ロックフリーブーストライブラリ を試してみることをお勧めします。「キュー」構造は、ウェイトフリー/ロックフリーの保証と C++ 11コンパイラが必要 。
ロックフリーライブラリはブーストするのが非常に新しいので、私はこの答えを今追加しています(1.53以来)
デキュー機能を次のように書き直します。
std::string FileQueue::dequeue(const std::chrono::milliseconds& timeout)
{
std::unique_lock<std::mutex> lock(qMutex);
while(q.empty()) {
if (populatedNotifier.wait_for(lock, timeout) == std::cv_status::timeout )
return std::string();
}
std::string ret = q.front();
q.pop();
return ret;
}
それは短く、あなたのように重複したコードはありません。タイムアウトよりも長く待機する可能性があるのは問題のみです。ループの前に開始時間を覚えておく必要があることを防ぐには、タイムアウトを確認し、それに応じて待機時間を調整します。または、待機状態の絶対時間を指定します。
この場合のGLibソリューションもあります。まだ試していませんが、良いソリューションだと思います。 https://developer.gnome.org/glib/2.36/glib-Asynchronous-Queues.html#g-async-queue-new
BlockingCollection は、キュー、スタック、および優先度コンテナのサポートを提供するC++ 11スレッドセーフコレクションクラスです。説明した「空の」キューシナリオを処理します。 「フル」キューと同様に。
Lfqueue、 https://github.com/Taymindis/lfqueue が好きかもしれません。ロックフリーの並行キューです。現在、複数の着信コールからキューを消費するために使用しており、魅力のように機能します。