C++ マルチスレッディング:初心者のガイド

こんにちは、未来のコーディングスーパースターの皆さん!あなたがこの素晴らしいC++マルチスレッディングの世界への旅のガイドとしてここにいて、私はとても楽しみです。プログラミングを教えていることが多年的になり、このトピックが最初は気になるかもしれませんが、実際には习惯すると非常に興味深いものになると安心できます。では、袖を roll upし、飛び込みましょう!

C++ Multithreading

マルチスレッディングとは?

具体的な内容に飛び込む前に、基本的なことから始めましょう。キッチンで複雑な料理を作ることを想像してみてください。一つ一つの工程でやることができます - 野菜を切る、それからパスタを沸騰させ、そしてソースを作る。しかし、これらすべてのタスクを同時に行うほうが効率的ではないでしょうか?それは、私たちのプログラムにマルチスレッディングが行うことと非常に似ています!

マルチスレッディングは、プログラムが複数のタスクを同時に実行することができるようにします。これらのタスクはそれぞれ「スレッド」と呼ばれます。それは、キッチンに複数のクックがいて、それぞれが料理の異なる部分を担当しているようなものです。

では、C++でどのようにこの力を利用するかを探ってみましょう!

スレッドの作成

C++でスレッドを作成するのは、キッチンに新しいシェフを雇うのと同じです。私たちは、このシェフ(スレッド)にどのタスクを実行させるかを教える必要があります。C++では、<thread>ライブラリのstd::threadクラスを使用してこれを行います。

簡単な例を見てみましょう:

#include <iostream>
#include <thread>

void cookPasta() {
std::cout << "Pastaを調理中..." << std::endl;
}

int main() {
std::thread chefOne(cookPasta);
chefOne.join();
return 0;
}

この例では、以下の通りです:

  1. 必要なライブラリをインクルードします:入力/出力には<iostream>、マルチスレッディングには<thread>を使用します。
  2. スレッドが実行する関数cookPasta()を定義します。
  3. main()の中で、chefOneという名前のスレッドを作成し、cookPasta()関数を実行するように指示します。
  4. プログラムが終了する前にスレッドがタスクを完了するのを待つためにjoin()を使用します。

このプログラムを実行すると、"Pastaを調理中..."というメッセージがコンソールに表示されます。おめでとうございます!初めてのスレッドを作成しました!

スレッドの終了

さて、もしシェフがパスタの調理に時間がかかりすぎるとどうでしょうか?プログラミングの世界では、スレッドがタスクを完了する前に終了させる必要がある場合があります。しかし、スレッドを強制的に終了すると、リソースリークや他の問題が発生する可能性があることに注意する必要があります。一般的には、スレッドを自然に終了させるように設計するか、終了信号に反応するようにする方が良いです。

以下は、スレッドが終了信号に反応するように設定する例です:

#include <iostream>
#include <thread>
#include <atomic>

std::atomic<bool> stop_thread(false);

void cookPasta() {
while (!stop_thread) {
std::cout << "まだPastaを調理中..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
std::cout << "Pastaの調理が停止されました!" << std::endl;
}

int main() {
std::thread chefOne(cookPasta);

std::this_thread::sleep_for(std::chrono::seconds(5));
stop_thread = true;

chefOne.join();
return 0;
}

この例では、以下の通りです:

  1. atomic<bool>型の変数stop_threadを使用して、スレッド間で安全に通信します。
  2. cookPasta()関数は今度はこの変数をループで確認します。
  3. main()の中で、スレッドを5秒間実行させた後、stop_threadをtrueに設定します。
  4. スレッドはループを終了させ、自然に終了します。

スレッドに引数を渡す

シェフにもっと具体的な指示をしたい場合はどうでしょうか?C++では、スレッドに引数を渡すことが、関数に引数を渡すのと同じように可能です。どのようにするかを見てみましょう:

#include <iostream>
#include <thread>
#include <string>

void cookDish(std::string dish, int time) {
std::cout << dish << "を" << time << "分間調理中。" << std::endl;
}

int main() {
std::thread chefOne(cookDish, "スパゲティ", 10);
std::thread chefTwo(cookDish, "ピザ", 15);

chefOne.join();
chefTwo.join();

return 0;
}

この例では、以下の通りです:

  1. cookDish()関数は今度は2つのパラメーターを取ります:料理の名前と調理時間。
  2. 別の料理を異なる時間で調理する2つのスレッドを作成します。
  3. これらの引数を直接スレッドを作成する際に渡します。

これは、スレッドが異なるパラメーターで同じようなタスクを行うことができる柔軟性を示しています!

スレッドの結合と分離

最後に、2つの重要な概念について話しましょう:スレッドの結合と分離。

スレッドの結合

すでに先ほどの例でjoin()を見てきました。スレッドにjoin()を呼び出すと、メインプログラムはそのスレッドが完了するまで待つようになります。それは、シェフが料理を完成させるのを待ってからメインコースを提供するようなものです。

スレッドの分離

時に、スレッドをバックグラウンドで実行させて、完了を待たない場合もあります。そんな場合にはdetach()が使えます。分離されたスレッドは、メインプログラムが終了した後もバックグラウンドで実行を続けます。

以下は、両方の概念を示す例です:

#include <iostream>
#include <thread>
#include <chrono>

void slowCook(std::string dish) {
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << dish << "が準備されました!" << std::endl;
}

void quickCook(std::string dish) {
std::cout << dish << "が準備されました!" << std::endl;
}

int main() {
std::thread slowChef(slowCook, "ステーキ");
std::thread quickChef(quickCook, "サラダ");

slowChef.detach();  // スロークシェフをバックグラウンドで動かす
quickChef.join();   // クイックシェフが完了を待つ

std::cout << "メインプログラムが終了します。スロークシェフはまだ働いているかもしれません!" << std::endl;
return 0;
}

この例では、以下の通りです:

  1. 2人のシェフがいます:1人がステーキをゆっくりと調理し、もう1人がサラダを素早く準備します。
  2. スロークシェフのスレッドを分離し、バックグラウンドで動かします。
  3. クイックシェフのスレッドを結合し、サラダが準備されるのを待ちます。
  4. メインプログラムは、ステーキが準備される前に終了する可能性があります。
メソッド 説明 使用ケース
join() スレッドの完了を待つ スレッドの結果を取得してから続ける必要がある場合
detach() スレッドを独立して実行 バックグラウンドで動作するタスク

それでは、これで皆さんがC++マルチスレッディングの第一歩を踏み出したことになります。料理を学ぶように、マルチスレッディングをマスターするためには実践が必要です。これらの概念について実験をしてみてください。そして、すぐにでもコードのキッチンでマスターシェフとして複雑で効率的なプログラムを作ることができるようになる予感です!

幸せなコーディングをお願いします。皆さんのスレッドが常にスムーズに走ることを願っています!

Credits: Image by storyset