6

C++でプログラムを書いています。間隔を置いて何かを行うことを目的としたスレッドがいくつか増えていることに気付きました.3つか4つありました. これらのスレッドを使用する他の場所がサブスクライブできるスケジューラー サービスを作成することでリファクタリングすることにしました。

これを使用するコードはまだありません。書き始める前に、それが可能かどうかを知り、私のデザインについてフィードバックを得たいと思います。私が達成したいことの簡単な説明はこれです:

イベントを追加するには

  1. 発信者はイベントとスケジュールを提供します
  2. スケジュールは、イベントの次の発生を提供します
  3. (イベント、スケジュール) ペアがイベント キューに追加されます
  4. スリープ状態のイベント スレッドに割り込む (つまり、ウェイク アップ)

イベントスレッドのメインループ

  1. イベント キューで次のイベントを取得しようとする
  2. 保留中のイベントがない場合は、そのまま 4 に進みます
  3. 次のイベントが発生する予定の時刻を取得する
  4. 次のイベントまでスリープします (待機イベントがない場合は永久に)
  5. 何らかの理由で睡眠が中断された場合は、1 にループバックします
  6. スリープが正常に完了した場合は、現在のイベントを実行します
  7. キューを更新します (イベントを削除し、繰り返しイベントの場合は再挿入します)
  8. 1 に戻る

私は少し調査を行い、スリープ状態のスレッドに割り込むことができることを知っています。また、イベント キューへの同時アクセスが防止されている限り、危険な動作はないと考えています。スレッドのスリープ解除は可能であると想像します。Java の Thread の sleep() 呼び出しは状況によっては InterruptedException をスローし、オペレーティング システムの基になるスリープ呼び出しに依存しない限り、何らかの方法で可能になるはずです。

質問

誰でも私のアプローチについてコメントできますか? これは、再発明しないほうがよい車輪ですか? 具体的には、次の命令で実行が再開されるように、スリープ状態のスレッドをどのように中断できますか?また、中断されたスレッドからこれを検出することは可能ですか?

ブーストについての注意

ブーストを使用してスケジューラを作成できると思いますが、これは、より良いフレーズがないため、がらくたの負荷であるマシンでコンパイルおよび実行されます。以前にブースト プログラムをコンパイルしたことがありますが、通常、ブーストを取り込む各ファイルのコンパイルには 30 秒以上かかります。この苛立たしい開発障害を回避できるのであれば、是非回避したいです。

補遺 - 作業コード [caf の提案により修正]

これは私が作成したコードです。初歩的なテストが行​​われていますが、さまざまな遅延を伴う単一イベントと繰り返しイベントの両方を適切に処理しています。

イベント スレッドの本文は次のとおりです。

void Scheduler::RunEventLoop()
{
    QueueLock();                   // lock around queue access
    while (threadrunning)
    {
        SleepUntilNextEvent();     // wait for something to happen

        while (!eventqueue.empty() && e.Due())
        {                          // while pending due events exist
            Event e = eventqueue.top();
            eventqueue.pop();

            QueueUnlock();         // unlock
            e.DoEvent();           // perform the event
            QueueLock();           // lock around queue access

            e.Next();              // decrement repeat counter
                                   // reschedule event if necessary
            if (e.ShouldReschedule()) eventqueue.push(e);
        }
    }
    QueueUnlock();                 // unlock
    return;                        // if threadrunning is set to false, exit
}

スリープ機能は次のとおりです。

void Scheduler::SleepUntilNextEvent()
{
    bool empty = eventqueue.empty();  // check if empty

    if (empty)
    {
        pthread_cond_wait(&eventclock, &queuelock); // wait forever if empty
    }
    else
    {
        timespec t =                  // get absolute time of wakeup
            Bottime::GetMillisAsTimespec(eventqueue.top().Countdown() + 
                                         Bottime::GetCurrentTimeMillis());
        pthread_cond_timedwait(&eventclock, &queuelock, &t); // sleep until event
    }
}

最後に、AddEvent:

void Scheduler::AddEvent(Event e)
{
    QueueLock();
    eventqueue.push(e);
    QueueUnlock();
    NotifyEventThread();
}

関連する変数宣言:

bool threadrunning;
priority_queue<Event, vector<Event>, greater<Event> > eventqueue;
pthread_mutex_t queuelock; // QueueLock and QueueUnlock operate on this
pthread_cond_t eventclock;

一般的なイベントの問題に対処するために、それぞれEventに抽象型のオブジェクトへのポインターが含まれており、そのactionサブクラスは overrideaction::DoEventです。このメソッドは内部から呼び出されますEvent::DoEventactionsイベントによって「所有」されます。つまり、イベントのスケジュールを変更する必要がなくなった場合、それらは自動的に削除されます。

4

4 に答える 4

10

あなたが探しているのはpthread_cond_t、オブジェクトpthread_cond_timedwaitpthread_cond_wait関数です。条件変数isThereAnyTaskToDoを作成し、イベント スレッドで待機することができます。新しいイベントが追加されたら、イベント スレッドを で起こすだけですpthread_cond_signal()

于 2012-08-19T07:56:06.387 に答える
3

*NIX プラットフォームと Windows の両方でいくつかの可能性があります。タイマー スレッドは、イベント/条件変数オブジェクトである種の時限待機を使用して待機する必要があります。POSIX プラットフォームでは、pthread_cond_timedwait(). Windows では、必要なタイム デルタを計算しWaitForSingleObject()てイベント ハンドルで使用するか、イベント オブジェクトとCreateTimerQueueTimer()またはを組み合わせて使用​​するかを選択できますCreateWaitableTimer()。Boost には、これを POSIX ライクなプリミティブで移植可能に実装するために使用できるいくつかの同期プリミティブもあります。

アップデート:

POSIX にもいくつかのタイマー機能があります。create_timer()

于 2012-08-19T08:01:28.090 に答える
3

私はグレッグウィルクスに同意します-pthread_cond_timedwait()あなたが求めている動作を実装するために使用することができます. イベント スレッドのメイン ループを簡素化できることを追加したかっただけです。

  1. イベント キューで次のイベントを取得しようとする
  2. 保留中のイベントがない場合は、そのまま 4 に進みます
  3. 次のイベントが発生する予定の時刻を取得する
  4. pthread_cond_timedwait()次のイベントまで (またはpthread_cond_wait()スケジュールされたイベントがない場合は)条件変数で待機します。
  5. イベント キューで次のイベントの取得を試みる
  6. まだ期限切れのイベントがない場合は、4 に戻ります。
  7. キューを更新します (イベントを削除し、繰り返しイベントの場合は再挿入します)
  8. 5に戻る

そのため、目が覚めた理由は気にしません。目が覚めるたびに、現在の時刻を確認し、有効期限が切れたイベントを実行してから、待機に戻ります。ほとんどの場合、新しいイベントが追加されたときに、期限切れになったイベントがないことがわかります。もちろん、待機時間を再計算するだけです。

期限切れになる次のイベントが常に先頭に来るように、キューをプライオリティ キューとして実装することをお勧めします。

于 2012-08-19T09:19:52.777 に答える
1

現在のソリューションには競合状態が含まれています。たとえば、次のようになります。

QueueLock();                      // lock around queue access
bool empty = eventqueue.empty();  // check if empty
QueueUnlock();                    // unlock

pthread_mutex_lock(&eventmutex);  // lock event mutex (for condition)
if (empty)
{
    pthread_cond_wait(&eventclock, &eventmutex); // wait forever if empty
}

QueueUnlock()キューが最初は空であるが、別のスレッドがこれと競合し、その間に新しい値をプッシュするとどうなるかを考えてみてくださいpthread_mutex_lock(&eventmutex)。新しいイベントのウェイクアップが失われます。キューロックを保持せずSleepUntilNextEvent()にアクセスすることにも注意してください。eventqueue.top()

渡されるpthread_cond_wait()ミューテックスは、シグナルが関連する共有状態を保護するミューテックスであると想定されます。この場合、「共有状態」はキュー自体であるため、キューを保護するミューテックスを 1 つだけ使用することでこれらの問題を解決できます。

void Scheduler::RunEventLoop()
{

    pthread_mutex_lock(&queuemutex);
    while (threadrunning)
    {
        while (!eventqueue.empty() && e.Due())
        {                          // while pending due events exist
            Event e = eventqueue.top();
            eventqueue.pop();

            pthread_mutex_unlock(&queuemutex);
            e.DoEvent();           // perform the event
            e.Next();              // decrement repeat counter
            pthread_mutex_lock(&queuemutex);
                                   // reschedule event if necessary
            if (e.ShouldReschedule()) eventqueue.push(e);
        }

        SleepUntilNextEvent();     // wait for something to happen
    }
    pthread_mutex_unlock(&queuemutex);

    return;                        // if threadrunning is set to false, exit
}

/* Note: Called with queuemutex held */
void Scheduler::SleepUntilNextEvent()
{
    if (eventqueue.empty())
    {
        pthread_cond_wait(&eventclock, &queuemutex); // wait forever if empty
    }
    else
    {
        timespec t =                  // get absolute time of wakeup
            Bottime::GetMillisAsTimespec(eventqueue.top().Countdown() + 
                                         Bottime::GetCurrentTimeMillis());
        pthread_cond_timedwait(&eventclock, &queuemutex, &t); // sleep until event
    }
}

pthread_cond_wait()待機中にミューテックスを解放することに注意してくださいpthread_cond_timedwait()(ミューテックスが解放され、ミューテックスがシグナル状態になっていることに関してアトミックに待機が開始されます)。そのため、スケジューラは、スリープ中にミューテックスを保持しません。

于 2012-08-22T03:15:15.083 に答える