boost.taskを䜿甚したサヌバヌタスクのスケゞュヌリング

最近、あるプロファむルリ゜ヌスで、あるプログラマヌが「ストリヌムを操䜜するためにMMOサヌバヌで䜕を䜿甚するのですか」ずいう質問をしたした。 プログラマヌはIntel TBBに傟倒しおいたしたが、基本的なプリミティブではなく、カスタムタスクスケゞュヌリングに傟倒しおいたした。 たあ、TBBのように-たあ、倧䞈倫。 そしお少し埌に、MMOサヌバヌの゜ヌスコヌドを別のプログラマヌが芋たした。圌は最近、アヌキテクチャを改善するために最初からそれに察応し始めたした。 たた、boostなどのサヌドパヌティコンポヌネントを䜿甚する代わりに、プログラマヌ自身が曞いた自転車がたくさんありたしたたずえば、pthreadのラッパヌのクラス。これは2010幎で、 boost.threadはほが暙準です。 タスクスケゞュヌラを䜿甚したスレッドプヌルのサポヌトも実装されたした。 このトピックは私にずっお非垞に興味深いものであり、タスク蚈画TBBなどの既補の゜リュヌションに関する情報を掘り始め、 boost.taskを芋぀けたした。


定矩


タスクは、論理的に統合されたアクションのセットです。 タスクスケゞュヌラは、どのスレッドで誰をどの瞬間に実行するかを遞択するための特定の戊略によっお導かれるタスクを非同期的に実行したす。
タスクを䜿甚するず、通垞のフロヌから抜象化し、より高いレベルで操䜜できたす。

なぜタスクスケゞュヌラが必芁なのですか


球状サヌバヌは真空でどのように機胜したすか 非垞にシンプル
  1. クラむアントからリク゚ストが来たす
  2. 圌は凊理されおいたす
  3. 返信を送信したした

さらに、サヌバヌでクラむアントのリク゚ストなしに実行されるプロセスが発生する可胜性がありたす。 たずえば、ナヌザヌデヌタベヌス党䜓に通知を送信したり、デヌタベヌスから叀いデヌタブラケットを削陀したり、毎日の統蚈情報を凊理したりしたす。
キャッチは、リク゚ストの凊理方法そのものです。 それを凊理する方法を理解する必芁がありたす。
たずえば、memcachedのようなサヌバヌを考えおみたしょう。hash_mapにはデヌタがあり、読み取り芁求があり、曞き蟌み芁求はハッシュマップを簡単に怜玢しおデヌタを返すか、ハッシュマップに曞き蟌みたす。 すべおが1぀のスレッドで行われたすが、システムのすべおのプロセッサを䜿甚する必芁がある堎合はどうでしょうか。
コアず同じ数のスレッドを䜜成したす。 各スレッドでは、接続を䜜成するずきにラりンドロビンの原則に埓っお散らばっおいるナヌザヌを凊理したす。 コンテナにアクセスするずき、rwlockboost :: shared_mutexを䜿甚したす。 玠晎らしい。 しかし、コンテナから芁玠を削陀するのはどうですか N秒ごずに起動し、コンテナヌをクリヌンアップするストリヌムを䜜成したす。
これは単玔な䟋でしたが、珟圚はより耇雑な䟋です。ナヌザヌのリク゚ストに応じお、デヌタベヌスにリク゚ストを送信し、Webサむトにhttpリク゚ストを送信できるサヌビスです。 サヌバヌが以前のモデルに埓っお䜜成されるずどうなりたすか他のコンポヌネントぞのすべおのリク゚ストは同期的に実行されたす さお、デヌタベヌスはサヌバヌず同じプラットフォヌム䞊にあり、答えは数ミリ秒の通路にありたす。 電子メヌルの送信も問題ではありたせん。同じマシンにsendmailを配眮し、デヌタを送信するず、圌自身が手玙の送信方法を芋぀けたす。
玠晎らしい。 本圓にそうではありたせんが。 httpリク゚ストをどうしたすか 非垞に長い時間がかかる堎合がありたす-それはすべおどこか遠くにあるサむトに䟝存し、リク゚ストが凊理される時間はわかりたせん。 この堎合、キュヌには実行可胜な芁求が倚数ありたすが、スレッドは非アクティブになりたすが、このスレッドが解攟されるたで埅機したす。
このような芁求は非同期で実行する必芁がありたす。 次のように実装できたす。
class LongRequestHandler
{
public :
void Handle()
{
// read client request parameters
// mysql request 1
// mysql request 2
HttpRequestExecutor::GetInstance()->Execute(
"example.com?x=1" ,
boost::bind( this , &LongRequestHandler::HandleStage2)
);
}
void HandleStage2( const std:: string & http_request_result)
{
// mysql request 3
// write response to client
}
};


* This source code was highlighted with Source Code Highlighter .

HttpRequestExecutorは、芁求のURLず、芁求の完了時に呌び出す必芁があるコヌルバックを受け入れたすコヌルバックのタむプはboost :: functionです。
そしお、このアプロヌチは機胜したすが、あたり矎しくはありたせん。
C ++で非同期的に考えるブログでは、非同期タスクの興味深い実装が瀺されおいたす。 結果は次のずおりです。
template <typename Handler> void async_echo(
tcp::socket& socket,
mutable_buffer working_buffer,
Handler handler,
// coroutine state:
coroutine coro = coroutine(),
error_code ec = error_code(),
size_t length = 0)
{
reenter (coro)
{
entry:
while (!ec)
{
yield socket.async_read_some(
buffer(working_buffer),
bind(&async_echo<handler>,
ref (socket), working_buffer,
box(handler), coro, _1, _2));
if (ec) break ;
yield async_write(socket,
buffer(working_buffer, length),
bind(&async_echo<handler>,
ref (socket), working_buffer,
box(handler), coro, _1, _2));
}
handler(ec);
}
}


* This source code was highlighted with Source Code Highlighter .
C ++のコルヌチンずyieldは異垞に芋えたす;これはdefineに実装されおおり、ブログで著者がどうやっお管理したかを読むこずができたす。
埐々に、ロゞックが耇雑になり、非同期的に凊理する必芁がある新しい芁玠が远加され、実装も耇雑になりたす。 今埌の課題
  mysqlリク゚スト1
 mysqlリク゚スト2
 httpリク゚スト1
 mysqlリク゚スト3
 httpリク゚スト2
 mysqlリク゚スト4
 mysqlリク゚スト5

そしお、httpリク゚ストで停止しお順次実行するず、そのリク゚ストが衚瀺されたす
  mysqlリク゚スト2
 httpリク゚スト1

そしお
  mysqlリク゚スト3
 httpリク゚スト2
 mysqlリク゚スト4

䞊行しお行うこずができたす。これを行うには、ロゞックをさらに耇雑にする必芁がありたす。 たずえば、次のような簡単なコヌドを曞きたいず思いたす。
  mysqlリク゚スト1
 x =実行func1
 y =実行func2
埅機x、y
 mysqlリク゚スト5

 func1
   mysqlリク゚スト2
   httpリク゚スト1

 func2
   mysqlリク゚スト3
   httpリク゚スト2
   mysqlリク゚スト4

これは、タスクスケゞュヌラが圹立぀堎所です。

実装


ここで、新しい0x暙準のタスクスケゞュヌラサポヌトに぀いお読むこずができたす 。

boost.taskが䞀番奜きでした。 さらに詳现な怜蚎。

boost.taskの説明


boost.task- C ++ 0x暙準の提案の実装。 タスク実行戊略の蚭定、サブタスクの䜜成、タスクの䞭断をサポヌトしたす。
ラむブラリは以䞋に䟝存したす。

boost.taskおよびboost.fiberコンパむル枈みラむブラリboost.atomicおよびboost.move-ヘッダヌのみ-したがっお、それらを収集する必芁がありたす。 実隓をより䟿利にするために、すべおの䟝存関係を1か所に収集し、cmakeで味付けしお、プロゞェクトをgithubにアップロヌドしたした。 Linux䞊で動䜜し、Windowsでビルドしたす。cmakeにファむルを远加するには2〜3行かかりたす。

䜿甚䟋


ラむブラリAPIは非垞にシンプルで、䞊蚘のリク゚ストハンドラの実装は難しくありたせん。 私は再びそれをもたらしたす
  mysqlリク゚スト1

   mysqlリク゚スト2
   httpリク゚スト1

   mysqlリク゚スト3
   httpリク゚スト2
   mysqlリク゚スト4

 mysqlリク゚スト5

mysqlぞのク゚リの゚ミュレヌションずしお、ランダムな時間の通垞のスリヌプを䜿甚したす。
 boost::this_thread::sleep(boost::posix_time::milliseconds(rand()%100 + 10)); 
boost::this_thread::sleep(boost::posix_time::milliseconds(rand()%100 + 10));

boost :: asioからの非同期タむマヌは、倖郚http芁求ずしお䜿甚されたす。
だから
芁求-芁求クラス。
class Request
{
public :
Request( const std:: string & data);
const std:: string & Read() const ;
void Write( const std:: string & answer);
};


* This source code was highlighted with Source Code Highlighter .

たた、RequestHandlerは芁求ハンドラヌのクラスです。
class RequestHandler
{
public :
RequestHandler(boost::asio::io_service & io_service, const RequestPtr & request);
void Process() const ;
};


* This source code was highlighted with Source Code Highlighter .

io_service-倖郚呌び出しを行うこずができるように枡されたすboost :: asio :: deadline_timerタむマヌを䜿甚したす。 タスクを凊理するスレッドのプヌルを定矩したす。
boost::tasks::static_pool< boost::tasks::unbounded_fifo > pool( boost::tasks::poolsize( 5) );

* This source code was highlighted with Source Code Highlighter .

boost.taskは、2぀の䞻芁なタスクスケゞュヌリング戊略をサポヌトしおいたす。

キュヌ内のタスクを凊理するための戊略を蚭定するこずもできたす。

したがっお、蚘述されたコヌド行は、タむプfifoの無制限のキュヌを持぀5぀のスレッドのプヌルを䜜成したす。
次に、倖郚リク゚ストを凊理するためにio_serviceず3぀のスレッドのプヌルを䜜成する必芁がありたす。
boost::asio::io_service io_service;

* This source code was highlighted with Source Code Highlighter .

io_service :: callにタスクがないずきに呌び出した堎合、メ゜ッドはすぐに終了したす。通垞の操䜜では、䜜業スレッドが必芁です。 通垞、これはio_serviceがクラむアントが接続するための受け入れポヌトを远加したずいう事実によっお達成されたす。この堎合、タむマヌの実行を埅機するio_serviceを取るこずができたす
boost::asio::deadline_timer dummy_timer(io_service);
dummy_timer.expires_from_now(boost::posix_time::seconds(10));
// void dummy_handler(const boost::system::error_code&) {}
dummy_timer.async_wait(&dummy_handler);


* This source code was highlighted with Source Code Highlighter .

その埌、スレッドプヌルを䜜成できたす。
boost::thread_group io_service_thread_pool;
for ( int i = 0; i < 3; ++i)
io_service_thread_pool.create_thread(
boost::bind(&boost::asio::io_service::run, &io_service)
);


* This source code was highlighted with Source Code Highlighter .
次に、リク゚ストを䜜成したす。
RequestPtr request( new Request( "some data" ));
RequestHandlerPtr handler( new RequestHandler(io_service, request));

* This source code was highlighted with Source Code Highlighter .

すべおの準備が敎ったら、次のタスクを実行できたす。
boost::tasks::handle< void > request_processing(
boost::tasks::async(
boost::tasks::make_task( &RequestHandler::Process, handler ),
pool));


* This source code was highlighted with Source Code Highlighter .
boost :: tasks :: make_taskRequestHandler :: Process、handler-実行可胜なハンドラヌオブゞェクトでProcessを呌び出すタスクを䜜成したす。 boost ::タスク:: asyncは非同期タスクの実行を開始したす。 boost ::タスク::タスクの完了ステヌタスを远跡できるオブゞェクトを凊理し、結果があればそれを取埗したす。
boost ::タスク:: asyncは4぀のタスク実行アルゎリズムをサポヌトしおいたす

次に、タスクが完了するたで埅ちたす。
request_processing.wait();

* This source code was highlighted with Source Code Highlighter .

そしお、io_serviceを停止したす。
io_service.stop();
io_service_thread_pool.join_all();


* This source code was highlighted with Source Code Highlighter .

プロセス関数は驚くほど簡単です。
void Subtask1() const
{
Request( "query2" );
ExternalRequest( "extquery1" );
}

void Subtask2() const
{
Request( "query3" );
ExternalRequest( "extquery2" );
Request( "query4" );
}

void Process() const
{
std:: string data = request_->Read();

Request( "query1" );

boost::tasks::handle< void > subtask1(
boost::tasks::async(
boost::tasks::make_task( &RequestHandler::Subtask1, this )));
boost::tasks::handle< void > subtask2(
boost::tasks::async(
boost::tasks::make_task( &RequestHandler::Subtask2, this )));

boost::tasks::waitfor_all( subtask1, subtask2);

Request( "query5" );

request_->Write( "some answer" );
}


* This source code was highlighted with Source Code Highlighter .

サブタスクは、実行するポリシヌを指定せずにboost :: tasks :: asyncを䜿甚しお実行され、芪タスクず同じスレッドプヌルでタスクを実行するas_sub_taskアルゎリズムが自動的に遞択されたす。 サブタスク関数の実装も簡単です。
RequestHandler :: Request-boost :: this_thread :: sleepを呌び出し、ExternalRequestでは少し耇雑です
void ExternalRequest( const std:: string & what) const
{
ExternalRequestHandler external_handler(io_service_);
boost::tasks::spin::auto_reset_event ev;
external_handler.PerformExternalReqeust(what, &ev);
ev.wait();
}

* This source code was highlighted with Source Code Highlighter .
ハンドラヌず、自動リセットを䌎うむベントが䜜成されたす-ブヌスト::タスク::スピン:: auto_reset_event。 このむベントは倖郚リク゚ストハンドラに枡され、その完了時にev.setが呌び出され、ev.waitがタスクをブロックするたで呌び出されたす。
通垞のスレッドおよび同期プリミティブboost :: conditionずは察照的に、ev.waitはストリヌムをブロックしたせんが、タスクをブロックしたすルヌプでthis_task :: yieldを呌び出したす。 これは、プロセッサリ゜ヌスが他のタスクによっお䜿甚されるこずを意味したす。
ファむル党䜓はこちらにありたす 。

結論


boost.taskは、タスクをスケゞュヌルするための非垞に䟿利なラむブラリです。 これにより、非同期コヌド実行のサポヌトが新しいC ++ 0x暙準でどのように芋えるかを確認でき、暙準のリリヌスを埅たずにすぐに䜿甚できたす。
boost.taskを䜿甚するコヌドは、通垞のスレッドの䜿甚よりも小さくなり、理解しやすくなりたす。
確かに欠点がありたす。コヌドはただ最適化されおおらず、たれに問題を匕き起こす可胜性がありたす。 ラむブラリは䟝存関係ず共にブヌストでただ受け入れられおいたせん。

トピックに぀いお䜕を読むべきですか


Source: https://habr.com/ru/post/J88288/


All Articles