def yasuharu519(self):

日々の妄想

c++11のthreadを使って非同期実行を試してみる

C++でInstagram APIを使ったクライアントを作ってみる - def yasuharu519(self): という記事では、Instagram API を使って、 Json データを取得し、画像の保存まで行うクライアント作成を行いました。 ただ、ここで書いたコードではすべて1スレッドで動いているため、 一つ一つのダウンロードを行うようになっており、時間がかかっておりました。

C++11 では、thread 関係を簡単に扱うことが可能になっており、今回は、 Instagram クローラをマルチスレッド対応させてみたいと思います。

構成設計

Json の取得を1つのスレッドで行い、画像URLが取得できたら、 他のワーカースレッドを立ち上げて、 画像データのダウンロードからデータの保存まで行うものとします。

本来は CPU が同時に実行できるスレッド数には限りがあるため、 スレッドプールなどを用意し、同時に実行されるスレッド数に 制限をかけるのが一般的ですが、今回は簡単のためそれは考えないこととします。

シングルスレッドでのクローラ

C++でInstagram APIを使ったクライアントを作ってみる - def yasuharu519(self): の記事で使ったプログラムを 少し整理したものを再掲します。

ビルドした結果が以下

Download start: http://scontent-a.cdninstagram.com/hphotos-xaf1/t51.2885-15/e15/10903410_1411717735787367_857711937_n.jpg
Download end: http://scontent-a.cdninstagram.com/hphotos-xaf1/t51.2885-15/e15/10903410_1411717735787367_857711937_n.jpg
... (以下別画像ダウンロードの繰り返し)
$ /usr/bin/time ./a.out

を実行して、コマンド実行にかかった時間を調べてみると、

5.68 real         0.05 user         0.06 sys

のようになりました。

マルチスレッドでのクローラ

マルチスレッド対応にしたものがこちら。

threadクラスのコンストラクタで、関数と引数を渡すだけで、 別スレッドで実行を行ってくれます。 作成したスレッドは、joinを呼ぶなどして、非同期実行が完了したことを保証してから プログラムを終了させないと、別スレッドでの処理が終わる前に プログラム全体が終了してしまいます。

そのため、最後に

for(auto& th : threads)
{
  th.join();
}

としてjoinを呼んでいます。join関数は、threadのメンバ関数ですが、 joinを実行するスレッドが、thのインスタンスが表しているthreadが終了するまで 待つといったものとなっています。

このプログラムを実行した結果

Download start: http://scontent-b.cdninstagram.com/hphotos-xap1/t51.2885-15/e15/926208_638764499531711_1990383021_n.jpg
...(以下 Download start が違う画像に対して呼ばれた結果)
Download done: http://scontent-b.cdninstagram.com/hphotos-xap1/t51.2885-15/e15/928956_1544569365782738_486009445_n.jpg
...(以下 Download done が違う画像に対して呼ばれた結果)

というようになり、非同期的に実行されていることがわかります。

時間計測の結果も

1.64 real         0.05 user         0.06 sys

となり、かかった時間が短くなっていることがわかります。

まとめ

threadを使って簡単に関数の非同期実行を行う方法について書いてみました。 ただし、今回の方法では、返り値がない関数でしか非同期実行できません。 返り値があるような関数を非同期的に実行したい場合は、 future/promise クラスなどを使う必要があります。

これについては、また時間があれば書いてみようかなと思います。 では〜