Windowsの手動リセットイベントをpthreadに移植する際に、pthread条件変数+ pthreadミューテックス+イベントが設定または設定解除されている場合のフラグよりも簡単な解決策はありますか?
7 に答える
pthread は低レベルの構造です。いいえ、これ以上簡単なメカニズムはありません。pthread_cond__*
概念的には自動リセット イベントに似ています。注意してください。pthread_cond_wait
偽のウェイクアップが発生する可能性があるため、状況に関係なく、何らかの外部フラグなしで使用しないでください。
ただし、独自のビルドはそれほど難しくありません。
#include <pthread.h>
#include <stdbool.h>
struct mrevent {
pthread_mutex_t mutex;
pthread_cond_t cond;
bool triggered;
};
void mrevent_init(struct mrevent *ev) {
pthread_mutex_init(&ev->mutex, 0);
pthread_cond_init(&ev->cond, 0);
ev->triggered = false;
}
void mrevent_trigger(struct mrevent *ev) {
pthread_mutex_lock(&ev->mutex);
ev->triggered = true;
pthread_cond_signal(&ev->cond);
pthread_mutex_unlock(&ev->mutex);
}
void mrevent_reset(struct mrevent *ev) {
pthread_mutex_lock(&ev->mutex);
ev->triggered = false;
pthread_mutex_unlock(&ev->mutex);
}
void mrevent_wait(struct mrevent *ev) {
pthread_mutex_lock(&ev->mutex);
while (!ev->triggered)
pthread_cond_wait(&ev->cond, &ev->mutex);
pthread_mutex_unlock(&ev->mutex);
}
の代わりに使用したい別のロックがあることが多いため、これは使用法に合わない場合がありますがev->mutex
、これが通常の使用方法の要点です。
パイプを使用して手動リセット イベントを簡単に実装できます。
イベントはトリガー状態にあります -> パイプから読み取るものがあります
SetEvent -> write()
ResetEvent -> read()
WaitForMultipleObjects -> 読み取り用の poll() (または select())
「SetEvent」操作は、パイプを空でない状態にするためだけに何か (たとえば、任意の値の 1 バイト) を書き込む必要があるため、後続の「待機」操作、つまり、読み取り可能なデータに対する poll() はブロックされません。
「ResetEvent」操作は、パイプが再び空であることを確認するために書き込まれたデータを読み取ります。パイプの読み取り側をノンブロッキングにする必要があるため、リセット (読み取り) を試みても既にリセットされているイベント (空のパイプ) はブロックされません - fcntl(pipe_out, F_SETFL, O_NONBLOCK) ResetEvent の場合、パイプ内にあるバイトと同じ数のバイトを読み取るようにコーディングする必要があります。
char buf[256]; // 256 is arbitrary
while( read(pipe_out, buf, sizeof(buf)) == sizeof(buf));
イベントを待機してもパイプから読み取られないため、「イベント」はリセット操作までトリガー状態のままであることに注意してください。
多くの場合、待機するイベントだけでなく、複数のオブジェクトが必要になるため、パイプアプローチを好みますWaitForMultipleObjects(...)
。また、パイプを使用すると、 WindowsWaitForMultipleObjects
呼び出しを、、、、およびに簡単に置き換えることができます。poll(...)
select
pselect
epoll
Futex
プロセス同期には、 (Fast Userspace Lockingシステムコール)と呼ばれる軽量の方法がありました。futex_fd
futexの1つ以上のファイル記述子を取得する関数がありました。このファイル記述子は、実際のファイル、デバイス、ソケットなどを表す他の多くのファイルとともに、、、、またはに渡される可能性がselect
ありpoll
ますepoll
。残念ながら、カーネルから削除されました。したがって、パイプトリックはこれを行うための唯一の機能のままです。
int pipefd[2];
char buf[256]; // 256 is arbitrary
int r = pipe2(pipefd, O_NONBLOCK);
void setEvent()
{
write(pipefd[1], &buf, 1);
}
void resetEvent() { while( read(pipefd[0], &buf, sizeof(buf)) > 0 ) {;} }
void waitForEvent(int timeoutMS)
{
struct pollfd fds[1];
fds[0].fd = pipefd[0];
fds[0].events = POLLRDNORM;
poll(fds, 1, timeoutMS);
}
// finalize:
close(pipefd[0]);
close(pipefd[1]);
より簡単な解決策はありませんが、次のコードでうまくいきます。
void LinuxEvent::wait()
{
pthread_mutex_lock(&mutex);
int signalValue = signalCounter;
while (!signaled && signalValue == signalCounter)
{
pthread_cond_wait(&condition, &mutex);
}
pthread_mutex_unlock(&mutex);
}
void LinuxEvent::signal()
{
pthread_mutex_lock(&mutex);
signaled = true;
signalCounter++;
pthread_cond_broadcast(&condition);
pthread_mutex_unlock(&mutex);
}
void LinuxEvent::reset()
{
pthread_mutex_lock(&mutex);
signaled = false;
pthread_mutex_unlock(&mutex);
}
signal()を呼び出すと、イベントはシグナル状態になり、待機中のすべてのスレッドが実行されます。その後、イベントはシグナル状態のままになり、wait()を呼び出すすべてのスレッドは待機しません。reset()を呼び出すと、イベントはシグナルなしの状態に戻ります。
signalCounterは、待機中のスレッドをウェイクアップするためにクイックシグナル/リセットを実行する場合に使用します。
高度にマルチスレッド化されたC++コードをWindowsからLinuxに移植するための同様のソリューションを探していましたが、最終的にはオープンソースのMITライセンスのLinuxライブラリ用Win32イベントを作成しました。それはあなたが探しているソリューションであるはずであり、パフォーマンスとリソース消費について徹底的に精査されています。
手動イベントと自動リセットイベント、およびWaitForSingleObject
とWaitForMultipleObject
機能の両方を実装します。
Windows イベントはセマフォに似ていると思います。つまり、自動リセットの場合は、バイナリ セマフォと sem_timedwait() 関数を使用します。
私たち (完全な開示: 私は NeoSmart Technologies で働いています) は、 POSIX でWIN32 の手動および自動リセット イベントを実装し、WaitForSingleObject と WaitForMultipleObjects の両方のクローンを含む、peventsと呼ばれるオープン ソース (MIT ライセンス) ライブラリを作成しました。それ以来、いくつかの採用が見られ (Linux/Mac の Steam で使用されています)、かなりうまく機能します。
個人的には、POSIX マシンでコーディングする場合は POSIX マルチスレッドとシグナリング パラダイムを使用することをお勧めしますが、pevents は必要に応じて別の選択肢を提供します。