C++のカスタムsleep、条件変数sleepインスタンス
11215 ワード
sleepの役割は言うまでもなく、ほとんどの言語で似たような関数が提供されており、呼び出すのも簡単です.sleepの役割は、プログラムをいくつかの時間待つことにほかならないが、このような目的を達成するためには、実際には多くの方法があり、最も簡単なのは往々にして最も乱暴である.以下のコードを例に挙げて説明する(注:本明細書で述べたプログラムコンパイル実行環境はLinuxである).
このコードは簡単なサービスプログラムを記述しており、サービスの処理ロジック、すなわちProc関数の内容を簡略化するために、ここではある文を周期的に印刷するだけで、周期的な目的を達成するためにsleepで実現し、5秒おきに印刷します.main関数ではSIGINT信号をキャプチャし、プログラムが端末で起動した後、ctr+cを入力するとプログラムに割り込み信号を送信し、一般的にプログラムは終了しますが、ここではこの信号をキャプチャし、私たち自身の論理で処理します.つまりserverのStop関数を呼び出します.コンパイルコマンドの実行
次に、端末に./testはプログラムを実行して、この時プログラムは5秒ごとにスクリーンの上で1本の文を印刷して、ctl+cを押して、あなたはプログラムがすぐに退出していないことを発見して、しばらく待ってやっと退出して、その原因を究明して、ctl+cを押して中断信号を出す時、プログラムは自分の論理を捉えて実行して、つまりserverのStop関数を呼び出して、タグビットrun_を実行しますfalseに設定され、Proc関数はrun_を検出します.falseの場合はループを終了し、プログラムは終了し、しかし、Procはちょうどsleepのステップに実行される可能性があります.sleepはプログラムを一時停止します.割り込み信号を捉えているので、終了するのではなく、時間が満たされるまで停止し続けます.このsleepは明らかに優雅ではありません.次に、迅速に終了できる2つの方法を紹介します.
カスタムsleep
システムが提供するsleepを呼び出すとき、関数の内部で他のことをすることはできません.これに基づいて、sleepで終了変数を検出できれば、すぐに終了できるのではないでしょうか.そう、そうです.カスタムsleepを通じて、タイムスライスをより小さなスライスに分割し、1つのスライスごとに検出します.これにより、プログラムの終了遅延時間をこのより小さなセグメントに縮小することができ、カスタムsleepは以下のようになります.
このsleepの2番目のパラメータはポインタタイプでなければなりません.このsleepの2番目のパラメータは、入力された値だけでなく、リアルタイム値を検出する必要があるため、対応する関数呼び出しも少し修正しなければなりません.完全なコードは以下の通りです.
g++test 2をコンパイルする.cpp-o test,実行./test、プログラムが起動したらctl+cを押して、プログラムがすぐに終了したかどうかを見ます.
実はこのような脱退はすぐに脱退するのではなく、sleepの待ち時間をより小さなタイムスライスに分けたもので、前例は0.1秒、つまりctr+cを押した後、プログラムは実際には0~0.1秒遅れて脱退するが、この時間は短く、すぐに脱退するように見える.
条件変数でsleepを実現する
大まかな考え方は,ループ時に1つの条件変数を待機し,タイムアウト時間を設定し,この時間内に他のスレッドが条件変数をトリガした場合,待機は直ちに終了し,そうでなければ設定時間まで待つことで,条件変数の制御によりsleepを実現し,必要に応じて直ちに終了することができる.
条件変数は往々にして反発ロックと組み合わせて使用され、反発ロックの論理は簡単で、1つのスレッドが反発ロックを取得した場合、他のスレッドは取得できません.つまり、2つのスレッドがpthread_に同時に実行された場合mutex_lock文は、1つのスレッドのみが実行され、別のスレッドがpthread_を呼び出すまでブロックされます.mutex_unlockは実行を続行します.したがって、マルチスレッドが同じメモリ領域にアクセスするときに反発ロックを使用し、複数のスレッドが同時にメモリ領域を変更しないようにします.本例で用いた関数は以下のようにいくつかあり,反発ロック相関関数は
以上の関数機能は、それぞれ初期化、ロック、ロック解除、破棄です.条件変数相関関数は
以上の関数機能は,初期化,タイムアウト待機条件変数,トリガ条件変数,破棄である.ここではpthread_を説明する必要がありますcond_timedwaitとpthread_cond_Signal関数
この関数は呼び出されるとブロックされます.つまりsleepのような役割ですが、2つの場合に呼び出されます.1、条件変数condがトリガーされた場合.2、システム時間がabstimeに達したとき、ここは絶対時間であり、相対時間ではないことに注意してください.それがsleepより優れているのは第一点です.また、mutexというパラメータもあります.この関数を実行すると、関数の入り口でmutexをロックし、出口でmutexをロック解除するのと同じ効果があります.マルチスレッドがこの関数を呼び出すと、このように理解できます.
pthread_cond_Signalはパラメータcondが1つしかありません.簡単に機能します.condを待つスレッドをトリガーします.注意してください.一度に1つしかトリガーされません.condを待つすべての県城をトリガーするにはpthread_を使用する必要があります.cond_broadcast関数は、パラメータも使い方も同じです.
以上の背景知識があれば、sleepをより優雅に実現することができ、主にProc関数とStop関数に注目し、完全なコードは以下の通りである.
とtest 2.cpp同様に、コンパイル後に実行し、プログラムは5秒おきに画面に1行の出力を印刷し、ctr+cを入力すると、プログラムはすぐに終了します.
/* filename: test.cpp */
#include
#include
#include
#include
class TestServer
{
public:
TestServer() : run_(true) {};
~TestServer(){};
void Start()
{
pthread_create(&thread_, NULL, ThreadProc, (void*)this);
}
void Stop()
{
run_ = false;
}
void Wait()
{
pthread_join(thread_, NULL);
}
void Proc()
{
int count = 0;
while (run_)
{
printf("sleep count:%d
", ++count);
sleep(5);
}
}
private:
bool run_;
pthread_t thread_;
static void* ThreadProc(void* arg)
{
TestServer* me = static_cast(arg);
me->Proc();
return NULL;
}
};
TestServer g_server;
void StopService()
{
g_server.Stop();
}
void StartService()
{
g_server.Start();
g_server.Wait();
}
void SignalHandler(int sig)
{
switch(sig)
{
case SIGINT:
StopService();
default:
break;
}
}
int main(int argc, char* argv[])
{
signal(SIGINT, SignalHandler);
StartService();
return 0;
}
このコードは簡単なサービスプログラムを記述しており、サービスの処理ロジック、すなわちProc関数の内容を簡略化するために、ここではある文を周期的に印刷するだけで、周期的な目的を達成するためにsleepで実現し、5秒おきに印刷します.main関数ではSIGINT信号をキャプチャし、プログラムが端末で起動した後、ctr+cを入力するとプログラムに割り込み信号を送信し、一般的にプログラムは終了しますが、ここではこの信号をキャプチャし、私たち自身の論理で処理します.つまりserverのStop関数を呼び出します.コンパイルコマンドの実行
$ g++ test.cpp -o test -lpthread
次に、端末に./testはプログラムを実行して、この時プログラムは5秒ごとにスクリーンの上で1本の文を印刷して、ctl+cを押して、あなたはプログラムがすぐに退出していないことを発見して、しばらく待ってやっと退出して、その原因を究明して、ctl+cを押して中断信号を出す時、プログラムは自分の論理を捉えて実行して、つまりserverのStop関数を呼び出して、タグビットrun_を実行しますfalseに設定され、Proc関数はrun_を検出します.falseの場合はループを終了し、プログラムは終了し、しかし、Procはちょうどsleepのステップに実行される可能性があります.sleepはプログラムを一時停止します.割り込み信号を捉えているので、終了するのではなく、時間が満たされるまで停止し続けます.このsleepは明らかに優雅ではありません.次に、迅速に終了できる2つの方法を紹介します.
カスタムsleep
システムが提供するsleepを呼び出すとき、関数の内部で他のことをすることはできません.これに基づいて、sleepで終了変数を検出できれば、すぐに終了できるのではないでしょうか.そう、そうです.カスタムsleepを通じて、タイムスライスをより小さなスライスに分割し、1つのスライスごとに検出します.これにより、プログラムの終了遅延時間をこのより小さなセグメントに縮小することができ、カスタムsleepは以下のようになります.
void sleep(int seconds, const bool* run)
{
int count = seconds * 10;
while (*run && count > 0)
{
--count;
usleep(100000);
}
}
このsleepの2番目のパラメータはポインタタイプでなければなりません.このsleepの2番目のパラメータは、入力された値だけでなく、リアルタイム値を検出する必要があるため、対応する関数呼び出しも少し修正しなければなりません.完全なコードは以下の通りです.
/* filename: test2.cpp */
#include
#include
#include
#include
class TestServer
{
public:
TestServer() : run_(true) {};
~TestServer(){};
void Start()
{
pthread_create(&thread_, NULL, ThreadProc, (void*)this);
}
void Stop()
{
run_ = false;
}
void Wait()
{
pthread_join(thread_, NULL);
}
void Proc()
{
int count = 0;
while (run_)
{
printf("sleep count:%d
", ++count);
sleep(5, &run_);
}
}
private:
bool run_;
pthread_t thread_;
void sleep(int seconds, const bool* run)
{
int count = seconds * 10;
while (*run && count > 0)
{
--count;
usleep(100000);
}
}
static void* ThreadProc(void* arg)
{
TestServer* me = static_cast(arg);
me->Proc();
return NULL;
}
};
TestServer g_server;
void StopService()
{
g_server.Stop();
}
void StartService()
{
g_server.Start();
g_server.Wait();
}
void SignalHandler(int sig)
{
switch(sig)
{
case SIGINT:
StopService();
default:
break;
}
}
int main(int argc, char* argv[])
{
signal(SIGINT, SignalHandler);
StartService();
return 0;
}
g++test 2をコンパイルする.cpp-o test,実行./test、プログラムが起動したらctl+cを押して、プログラムがすぐに終了したかどうかを見ます.
実はこのような脱退はすぐに脱退するのではなく、sleepの待ち時間をより小さなタイムスライスに分けたもので、前例は0.1秒、つまりctr+cを押した後、プログラムは実際には0~0.1秒遅れて脱退するが、この時間は短く、すぐに脱退するように見える.
条件変数でsleepを実現する
大まかな考え方は,ループ時に1つの条件変数を待機し,タイムアウト時間を設定し,この時間内に他のスレッドが条件変数をトリガした場合,待機は直ちに終了し,そうでなければ設定時間まで待つことで,条件変数の制御によりsleepを実現し,必要に応じて直ちに終了することができる.
条件変数は往々にして反発ロックと組み合わせて使用され、反発ロックの論理は簡単で、1つのスレッドが反発ロックを取得した場合、他のスレッドは取得できません.つまり、2つのスレッドがpthread_に同時に実行された場合mutex_lock文は、1つのスレッドのみが実行され、別のスレッドがpthread_を呼び出すまでブロックされます.mutex_unlockは実行を続行します.したがって、マルチスレッドが同じメモリ領域にアクセスするときに反発ロックを使用し、複数のスレッドが同時にメモリ領域を変更しないようにします.本例で用いた関数は以下のようにいくつかあり,反発ロック相関関数は
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
以上の関数機能は、それぞれ初期化、ロック、ロック解除、破棄です.条件変数相関関数は
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_destroy(pthread_cond_t *cond);
以上の関数機能は,初期化,タイムアウト待機条件変数,トリガ条件変数,破棄である.ここではpthread_を説明する必要がありますcond_timedwaitとpthread_cond_Signal関数
pthread_cond_timedwait
この関数は呼び出されるとブロックされます.つまりsleepのような役割ですが、2つの場合に呼び出されます.1、条件変数condがトリガーされた場合.2、システム時間がabstimeに達したとき、ここは絶対時間であり、相対時間ではないことに注意してください.それがsleepより優れているのは第一点です.また、mutexというパラメータもあります.この関数を実行すると、関数の入り口でmutexをロックし、出口でmutexをロック解除するのと同じ効果があります.マルチスレッドがこの関数を呼び出すと、このように理解できます.
pthread_cond_Signalはパラメータcondが1つしかありません.簡単に機能します.condを待つスレッドをトリガーします.注意してください.一度に1つしかトリガーされません.condを待つすべての県城をトリガーするにはpthread_を使用する必要があります.cond_broadcast関数は、パラメータも使い方も同じです.
以上の背景知識があれば、sleepをより優雅に実現することができ、主にProc関数とStop関数に注目し、完全なコードは以下の通りである.
/* filename: test3.cpp */
#include
#include
#include
#include
#include
class TestServer
{
public:
TestServer() : run_(true)
{
pthread_mutex_init(&mutex_, NULL);
pthread_cond_init(&cond_, NULL);
};
~TestServer()
{
pthread_mutex_destroy(&mutex_);
pthread_cond_destroy(&cond_);
};
void Start()
{
pthread_create(&thread_, NULL, ThreadProc, (void*)this);
}
void Stop()
{
run_ = false;
pthread_mutex_lock(&mutex_);
pthread_cond_signal(&cond_);
pthread_mutex_unlock(&mutex_);
}
void Wait()
{
pthread_join(thread_, NULL);
}
void Proc()
{
pthread_mutex_lock(&mutex_);
struct timeval now;
int count = 0;
while (run_)
{
printf("sleep count:%d
", ++count);
gettimeofday(&now, NULL);
struct timespec outtime;
outtime.tv_sec = now.tv_sec + 5;
outtime.tv_nsec = now.tv_usec * 1000;
pthread_cond_timedwait(&cond_, &mutex_, &outtime);
}
pthread_mutex_unlock(&mutex_);
}
private:
bool run_;
pthread_t thread_;
pthread_mutex_t mutex_;
pthread_cond_t cond_;
static void* ThreadProc(void* arg)
{
TestServer* me = static_cast(arg);
me->Proc();
return NULL;
}
};
TestServer g_server;
void StopService()
{
g_server.Stop();
}
void StartService()
{
g_server.Start();
g_server.Wait();
}
void SignalHandler(int sig)
{
switch(sig)
{
case SIGINT:
StopService();
default:
break;
}
}
int main(int argc, char* argv[])
{
signal(SIGINT, SignalHandler);
StartService();
return 0;
}
とtest 2.cpp同様に、コンパイル後に実行し、プログラムは5秒おきに画面に1行の出力を印刷し、ctr+cを入力すると、プログラムはすぐに終了します.