C++ 0xがセマフォなしで来るのは本当ですか?セマフォの使用に関するStack Overflowには、すでにいくつかの質問があります。私は常に(posixセマフォ)を使用して、スレッドが別のスレッドで何らかのイベントを待つようにします。
void thread0(...)
{
doSomething0();
event1.wait();
...
}
void thread1(...)
{
doSomething1();
event1.post();
...
}
ミューテックスでそれを行う場合:
void thread0(...)
{
doSomething0();
event1.lock(); event1.unlock();
...
}
void thread1(...)
{
event1.lock();
doSomethingth1();
event1.unlock();
...
}
問題:く、最初にthread1がmutexをロックすることは保証されません(同じスレッドがmutexをロックおよびロック解除する必要があるため、thread0およびthread1が開始する前にevent1をロックすることもできません)。
ブーストにはセマフォもありませんので、上記を達成する最も簡単な方法は何ですか?
Mutexと条件変数から簡単に構築できます:
#include <mutex>
#include <condition_variable>
class semaphore
{
private:
std::mutex mutex_;
std::condition_variable condition_;
unsigned long count_ = 0; // Initialized as locked.
public:
void notify() {
std::lock_guard<decltype(mutex_)> lock(mutex_);
++count_;
condition_.notify_one();
}
void wait() {
std::unique_lock<decltype(mutex_)> lock(mutex_);
while(!count_) // Handle spurious wake-ups.
condition_.wait(lock);
--count_;
}
bool try_wait() {
std::lock_guard<decltype(mutex_)> lock(mutex_);
if(count_) {
--count_;
return true;
}
return false;
}
};
Maxim Yegorushkin's answer に基づいて、C++ 11スタイルで例を作成しようとしました。
#include <mutex>
#include <condition_variable>
class Semaphore {
public:
Semaphore (int count_ = 0)
: count(count_) {}
inline void notify()
{
std::unique_lock<std::mutex> lock(mtx);
count++;
cv.notify_one();
}
inline void wait()
{
std::unique_lock<std::mutex> lock(mtx);
while(count == 0){
cv.wait(lock);
}
count--;
}
private:
std::mutex mtx;
std::condition_variable cv;
int count;
};
できる限り標準のスタイルで、できる限り堅牢で汎用的なC++ 11セマフォを作成することにしました(using semaphore = ...
に注意してください。通常は、semaphore
という名前を使用します。通常はbasic_string
ではなくstring
を使用します):
template <typename Mutex, typename CondVar>
class basic_semaphore {
public:
using native_handle_type = typename CondVar::native_handle_type;
explicit basic_semaphore(size_t count = 0);
basic_semaphore(const basic_semaphore&) = delete;
basic_semaphore(basic_semaphore&&) = delete;
basic_semaphore& operator=(const basic_semaphore&) = delete;
basic_semaphore& operator=(basic_semaphore&&) = delete;
void notify();
void wait();
bool try_wait();
template<class Rep, class Period>
bool wait_for(const std::chrono::duration<Rep, Period>& d);
template<class Clock, class Duration>
bool wait_until(const std::chrono::time_point<Clock, Duration>& t);
native_handle_type native_handle();
private:
Mutex mMutex;
CondVar mCv;
size_t mCount;
};
using semaphore = basic_semaphore<std::mutex, std::condition_variable>;
template <typename Mutex, typename CondVar>
basic_semaphore<Mutex, CondVar>::basic_semaphore(size_t count)
: mCount{count}
{}
template <typename Mutex, typename CondVar>
void basic_semaphore<Mutex, CondVar>::notify() {
std::lock_guard<Mutex> lock{mMutex};
++mCount;
mCv.notify_one();
}
template <typename Mutex, typename CondVar>
void basic_semaphore<Mutex, CondVar>::wait() {
std::unique_lock<Mutex> lock{mMutex};
mCv.wait(lock, [&]{ return mCount > 0; });
--mCount;
}
template <typename Mutex, typename CondVar>
bool basic_semaphore<Mutex, CondVar>::try_wait() {
std::lock_guard<Mutex> lock{mMutex};
if (mCount > 0) {
--mCount;
return true;
}
return false;
}
template <typename Mutex, typename CondVar>
template<class Rep, class Period>
bool basic_semaphore<Mutex, CondVar>::wait_for(const std::chrono::duration<Rep, Period>& d) {
std::unique_lock<Mutex> lock{mMutex};
auto finished = mCv.wait_for(lock, d, [&]{ return mCount > 0; });
if (finished)
--mCount;
return finished;
}
template <typename Mutex, typename CondVar>
template<class Clock, class Duration>
bool basic_semaphore<Mutex, CondVar>::wait_until(const std::chrono::time_point<Clock, Duration>& t) {
std::unique_lock<Mutex> lock{mMutex};
auto finished = mCv.wait_until(lock, t, [&]{ return mCount > 0; });
if (finished)
--mCount;
return finished;
}
template <typename Mutex, typename CondVar>
typename basic_semaphore<Mutex, CondVar>::native_handle_type basic_semaphore<Mutex, CondVar>::native_handle() {
return mCv.native_handle();
}
pOSIXセマフォに従って、私は追加します
class semaphore
{
...
bool trywait()
{
boost::mutex::scoped_lock lock(mutex_);
if(count_)
{
--count_;
return true;
}
else
{
return false;
}
}
};
そして、より基本的な演算子を使用してつなぎ合わせたバージョンを常にコピーして貼り付けるのではなく、便利な抽象化レベルで同期メカニズムを使用することを好みます。
cpp11-on-multicore をチェックアウトすることもできます-ポータブルで最適なセマフォの実装があります。
リポジトリには、c ++ 11スレッドを補完する他のスレッド機能も含まれています。
Mutexおよび条件変数を使用できます。ミューテックスを使用して排他的にアクセスし、続行するか、もう一方の端を待つ必要があるかを確認します。待つ必要がある場合は、条件で待機します。他のスレッドが続行できると判断すると、条件を通知します。
Boost :: threadライブラリーには短い 例 があり、これはおそらくコピーするだけです(C++ 0xとboostスレッドライブラリは非常に似ています)。
スレッドで有用なRAIIセマフォラッパーにもなります。
class ScopedSemaphore
{
public:
explicit ScopedSemaphore(Semaphore& sem) : m_Semaphore(sem) { m_Semaphore.Wait(); }
ScopedSemaphore(const ScopedSemaphore&) = delete;
~ScopedSemaphore() { m_Semaphore.Notify(); }
ScopedSemaphore& operator=(const ScopedSemaphore&) = delete;
private:
Semaphore& m_Semaphore;
};
マルチスレッドアプリでの使用例:
boost::ptr_vector<std::thread> threads;
Semaphore semaphore;
for (...)
{
...
auto t = new std::thread([..., &semaphore]
{
ScopedSemaphore scopedSemaphore(semaphore);
...
}
);
threads.Push_back(t);
}
for (auto& t : threads)
t.join();
リストが長い、shared_ptrとweak_ptrが必要な仕事をしてくれました。私の問題は、ホストの内部データとやり取りしたいクライアントがいくつかいたことです。通常、ホストはそれ自体でデータを更新しますが、クライアントが要求した場合、ホストがホストデータにアクセスするクライアントがなくなるまで更新を停止する必要があります。同時に、クライアントは排他的アクセスを要求できるため、他のクライアントもホストもそのホストデータを変更できません。
これをどうやってやったのか、構造体を作成しました:
struct UpdateLock
{
typedef std::shared_ptr< UpdateLock > ptr;
};
各クライアントには、次のようなメンバーがあります。
UpdateLock::ptr m_myLock;
次に、ホストには、排他性のためのweak_ptrメンバーと、非排他的ロックのためのweak_ptrsのリストがあります。
std::weak_ptr< UpdateLock > m_exclusiveLock;
std::list< std::weak_ptr< UpdateLock > > m_locks;
ロックを有効にする機能と、ホストがロックされているかどうかを確認する別の機能があります。
UpdateLock::ptr LockUpdate( bool exclusive );
bool IsUpdateLocked( bool exclusive ) const;
LockUpdate、IsUpdateLocked、および定期的にホストの更新ルーチンでロックをテストします。ロックのテストは、weak_ptrの期限が切れているかどうかを確認し、m_locksリストから期限切れを削除するだけです(ホストの更新中にのみ行います)。リストが空かどうかを確認できます。同時に、クライアントがハングしているshared_ptrをクライアントがリセットすると、自動的にロックが解除されます。これは、クライアントが自動的に破棄されたときにも発生します。
全体的な効果は、クライアントが排他性を必要とすることはほとんどないため(通常は追加と削除のみに予約されている)、ほとんどの場合、LockUpdate(false)、つまり非排他的な要求は(!m_exclusiveLock)である限り成功します。また、排他性の要求であるLockUpdate(true)は、(!m_exclusiveLock)と(m_locks.empty())の両方の場合にのみ成功します。
排他的ロックと非排他的ロックを緩和するためにキューを追加できますが、これまで衝突は発生していません。そのため、ソリューションが追加されるまで待ちます(ほとんどの場合、実際のテスト条件があります)。
これまでのところ、これは私のニーズに適しています。これを拡張する必要性、および拡張された使用で発生する可能性のあるいくつかの問題を想像できますが、これは実装が迅速で、カスタムコードはほとんど必要ありませんでした。