GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • ホーム
  • プログラミング
  • 画像処理
  • 機械学習
  • 仕事効率化
  • プロフィール
  • 年齢計算ツール
  • お問い合せ
  • C++

This view

  • Original page
  • New address

GeezerVison is showing cpp-learning.com — 【Cpp-Taskflow】C++で並列処理・マルチタスク -入門から実践まで-|はやぶさの技術ノート

Cpp-Taskflow.jpg
記事内に商品プロモーションを含む場合があります

こんにちは。

現役エンジニアの”はやぶさ”@Cpp_Learningです。

C++でソフトウェア開発をしています。C++でプログラミングをしていると…

【やりたいこと】

  • 並列処理で高速化したい
  • 安全にマルチタスクを実現したい

と思うことがあります。

いくつか方法はありますが、”Cpp-Taskflow”を使えば、”スマート”に【やりたいこと】を実現できそうだったので、勉強してみました。

勉強した内容を自分なりに消化吸収して…

はやぶさ
はやぶさ
『入門から実践まで』・『基礎から応用まで』しっかり学べる記事に仕上げました!

微力ではありますが、現役エンジニアの”はやぶさ”が皆様の勉強をサポートさせて頂きます。本記事をきっかけに、情報処理技術をもっと学びたい!という人が増えると嬉しいです(*・ω・)ノ♪

なお、本記事の【文章構成】は以下の通りなので、ソースコードだけ見せて!という人は、目次から『Cpp-Taskflowで並列処理(マルチタスク)-基礎編-』に飛んで後編から読み進めて下さいな。

【文章構成】

  • 前編:マルチタスク・排他制御などの情報処理技術の説明
  • 後編:マルチタスクプログラミングの実践 -基礎から応用まで-
スポンサーリンク

Contents

  • 1 タスク(プロセス)とは
  • 2 シングルタスクとマルチタスク
    • 2.1 シングルタスクとは
    • 2.2 マルチタスクとは
  • 3 マルチタスクと排他制御
  • 4 Cpp-Taskflowで並列処理(マルチタスク)-基礎編-
    • 4.1 Cpp-Taskflowのシンプルな使い方
    • 4.2 Step 1: Create a Task
    • 4.3 Step 2: Define Task Dependencies
    • 4.4 Step 3: Execute the Tasks
  • 5 Cpp-Taskflowで並列処理(マルチタスク)-Debug編-
    • 5.1 Debug a Taskflow Graph
    • 5.2 Name
    • 5.3 Dump the Present Taskflow Graph
    • 5.4 Graphviz Onlineで図を描画する方法
  • 6 Cpp-Taskflowで並列処理(マルチタスク)-応用編-
    • 6.1 【課題】四則演算のマルチタスク
    • 6.2 マルチタスクプログラム❶ -sample1.cpp-
    • 6.3 マルチタスクプログラム❷ -sample2.cpp-
    • 6.4 マルチタスクプログラム➌ -sample3.cpp-
  • 7 ビルドと動作確認
    • 7.1 前準備
    • 7.2 ビルド&実行
    • 7.3 makefile
  • 8 まとめ
  • 9 おまけ -メッセージとおすすめの書籍-
    • 9.1 メッセージ
    • 9.2 おすすめの書籍

タスク(プロセス)とは

ユーザーがコンピュータにやらせたい処理のことをジョブと呼びます。

例えば、「画像ファイルを読み込む ⇒ 画像を加工 ⇒ 別ファイルに保存」というプログラムがあったとします。この一連の処理を1つのジョブと考えることができます。

ジョブはジョブステップという単位に分割されます。今回の場合、3つのジョブステップに分割されます。

【ジョブステップ】

  1. 画像ファイルを読み込む(ジョブステップ❶)
  2. 画像ファイルを加工(ジョブステップ❷)
  3. 加工後の画像ファイルを保存(ジョブステップ➌)

さらに、ジョブをタスクという単位に分割し、CPUがタスク単位で処理を行います。

Cpp_Taskflow

※タスクのことをプロセスと呼ぶことがあります。

  • ジョブはタスクという単位で分割されます
  • CPUはタスク単位で処理を行います
  • タスクのことをプロセスと呼ぶことがあります

シングルタスクとマルチタスク

シングルタスクとマルチタスクについて説明します。

シングルタスクとは

ジョブを分割せず、1つのタスクで処理するとします。

シングルタスク

この場合、”Task A”はCPU(などのハードウェア資源)を占有して利用することができます。

シングルタスク

このように1つのタスクがハードウェア資源を占有して利用する処理をシングルタスクと呼びます。

マルチタスクとは

ジョブを3つのタスクに分割したとします。

マルチタスク

この場合、”Task A”・”Task B”・”Task C”はCPU(などのハードウェア資源)を共有して利用することになります。

マルチタスク

このように複数のタスクがハードウェア資源を共有して利用する処理をマルチタスクと呼びます。

【シングルタスク】

1つのタスクがCPUなどのハードウェア資源を占有して利用する処理

【マルチタスク】

複数タスクがCPUなどのハードウェア資源を共有して利用する処理

スポンサーリンク

マルチタスクと排他制御

マルチタスクの場合、タスク同士がハードウェア資源を取り合い、適切な処理ができないときがあります。

このような競合を防ぐために排他制御をする必要があります。

排他制御例えば、”Task A”が共有リソースにアクセスするとき、他のタスクをロックしてアクセス競合を防ぐように制御します。

  • マルチタスクでは、資源の取り合いが原因で不具合が発生するときがあります
  • 各タスクの排他制御により、共有リソースのアクセス競合を防ぎます

ここまでが『前編:マルチタスク・排他制御などの情報処理技術』の説明でした。

続いて『後編:マルチタスクプログラミングの実践 -基礎から応用まで-』について説明します。

(前編 完)

Cpp-Taskflowで並列処理(マルチタスク)-基礎編-

排他制御の実装方法は、いくつかありますが、”Cpp-Taskflow”を使うとスマートに実装することができます。

Cpp-Taskflowのシンプルな使い方

”Cpp-Taskflow|GitHub”は”ヘッダーオンリーなライブラリ”なので、taskflow.hppをインクルードするだけで使うことができます。

Cpp-Taskflowのシンプルな使い方(simple.cpp)は以下の通りです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Cpp-Taskflow is header-only
#include "../taskflow/taskflow.hpp"
 
int main(){
 
    tf::Taskflow tf;
 
    auto [A, B, C, D] = tf.emplace(
        [] () { std::cout << "Task A" << std::endl; },    //  task dependency graph
        [] () { std::cout << "Task B" << std::endl; },    //
        [] () { std::cout << "Task C" << std::endl; },    //          +---+
        [] () { std::cout << "Task D" << std::endl; }     //    +---->| B |-----+
    );                                                    //    |     +---+     |
                                                          //  +---+           +-v-+
    A.precede(B);  // A runs before B                     //  | A |           | D |
    A.precede(C);  // A runs before C                     //  +---+           +-^-+
    B.precede(D);  // B runs before D                     //    |     +---+     |
    C.precede(D);  // C runs before D                     //    +---->| C |-----+
                                                          //          +---+
    tf.wait_for_all();  // block until finish
 
    return 0;
}

順番に解説していきます。

Step 1: Create a Task

最初にタスク生成用のオブジェクトを生成します。

1
2
3
 
tf::Taskflow tf;
 

次に”Task A”を生成します。

1
2
3
 
tf::Task A = tf.emplace([](){ "タスクAの処理" });
 

例えば、”Task A”と表示する処理の場合は以下の通りです。

1
2
3
 
tf::Task A = tf.emplace([](){ std::cout << "Task A" << std::endl; });
 

タスクの生成と処理を別々に書くこともできます。

1
2
3
4
 
tf::Task A = tf.emplace([](){});
A.work([] () { std::cout << "Task A" << std::endl; });
 

タスクが複数でも単数のとき同様に生成することができます。

1
2
3
4
5
6
 
tf::Task A = tf.emplace([](){ std::cout << "Task A" << std::endl; });
tf::Task B = tf.emplace([](){ std::cout << "Task B" << std::endl; });
tf::Task C = tf.emplace([](){ std::cout << "Task C" << std::endl; });
tf::Task D = tf.emplace([](){ std::cout << "Task D" << std::endl; });
 

以下のようにタスクをまとめて生成することもできます。

1
2
3
4
5
6
7
8
 
auto [A, B, C, D] = tf.emplace(
    [] () { std::cout << "Task A" << std::endl; },
    [] () { std::cout << "Task B" << std::endl; },
    [] () { std::cout << "Task C" << std::endl; },
    [] () { std::cout << "Task D" << std::endl; }
);
 

簡単にタスク(プロセス)を生成できます

Step 2: Define Task Dependencies

生成したタスクをどの順番で処理するかを定義します。

Cpp-Taskflow

”Task A” ⇒ ”Task B”の順番に処理する書き方は2通りあります。

【Precede(1 on 1)】

1
2
3
 
A.precede(B); // A runs before B.
 

【Gather(1 on 1)】

1
2
3
 
B.gather(A); // B runs after A
 

1対1じゃなくても定義できます。

【Precede(1 on N)】

Cpp-Taskflow

1
2
3
4
5
 
// make A run before B, C, D, and E
// B, C, D, and E run in parallel
A.precede(B, C, D, E);
 

※”Task B”, ”Task C”, ”Task D”, ”Task E”は並列処理されます

【Gather(N on 1)】

Cpp-Taskflow

1
2
3
4
5
 
// B, C, D, and E run in parallel
// A runs after B, C, D, and E complete
A.gather(B, C, D, E);
 

※”Task B”, ”Task C”, ”Task D”, ”Task E”は並列処理されます

簡単にマルチタスクの並列処理が実装できます

Step 3: Execute the Tasks

全タスクの処理が完了するまで待機します。

1
2
3
 
tf.wait_for_all(); // block until finish
 

以上が”Cpp-Taskflow”の基本的な使い方です。

簡単にタスク管理(排他制御による各タスクのやり取り)ができます

Cpp-Taskflowで並列処理(マルチタスク)-Debug編-

並列処理のデバッグには苦労しますが、”Cpp-Taskflow”なら簡単にデバッグできます。

Debug a Taskflow Graph

名前を付けた各タスクのフロー(並列処理するタイミングなど)を可視化することができます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// the only include you need
#include "../taskflow/taskflow.hpp"
 
int main(){
 
tf::Taskflow tf;
 
tf::Task A = tf.emplace([] () {}).name("A");
tf::Task B = tf.emplace([] () {}).name("B");
tf::Task C = tf.emplace([] () {}).name("C");
tf::Task D = tf.emplace([] () {}).name("D");
tf::Task E = tf.emplace([] () {}).name("E");
 
A.precede(B, C, E);
C.precede(D);
B.precede(D, E);
 
tf.dump(std::cout);
 
return 0;
}

または

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// This example demonstrates how to use 'dump' method to inspect a taskflow graph.
#include "../taskflow/taskflow.hpp"
 
int main(){
 
    tf::Taskflow tf;
 
    auto [A, B, C, D, E] = tf.emplace(
        [] () {},
        [] () {},
        [] () {},
        [] () {},
        [] () {}
    );
 
    A.precede(B, C, E);
    C.precede(D);
    B.precede(D, E);
 
    A.name("A");
    B.name("B");
    C.name("C");
    D.name("D");
    E.name("E");
 
    tf.dump(std::cout);
 
    return 0;
}

順番に解説していきます。

Name

各タスクに人間が理解しやすい名前を付けることができます。

1
2
3
 
tf::Task A = tf.emplace([] () {}).name("my name is A");
 

または

1
2
3
4
 
tf::Task A = tf.emplace([] () {});
A.name("my name is A");”
 

tf::Task Aはコンピュータ用の定義です。人間用の分かりやすい名前は”name”使って付けることができます

Dump the Present Taskflow Graph

GraphVizでタスクフロー図を描画するためのコードを生成します。

1
2
3
 
tf.dump(std::cout);
 

今回の場合、以下のコードが生成されます。

digraph Taskflow {
“A” -> “B”
“A” -> “C”
“A” -> “E”
“B” -> “D”
“B” -> “E”
“C” -> “D”
}

動作環境次第では、以下のようなコードを生成するかもしれません。

digraph Taskflow {
p0x7fc91c7e0020[label=”A”];
p0x7fc91c7e0020 -> p0x7fc91c7e0138;
p0x7fc91c7e0020 -> p0x7fc91c7e0250;
p0x7fc91c7e0020 -> p0x7fc91c7e0480;
p0x7fc91c7e0138[label=”B”];
p0x7fc91c7e0138 -> p0x7fc91c7e0368;
p0x7fc91c7e0138 -> p0x7fc91c7e0480;
p0x7fc91c7e0250[label=”C”];
p0x7fc91c7e0250 -> p0x7fc91c7e0368;
p0x7fc91c7e0368[label=”D”];
p0x7fc91c7e0480[label=”E”];
}

PCにインストール済みのGraphVizを使って、生成したコードから図を描画することができます。

タスクフロー図

※Windows版のGraphVizで可視化しました

このように簡単にタスクフローを可視化できるため、「各タスクの不具合」や「各タスクの処理タイミングによる不具合」を早期発見 ⇒ 修正するデバッグが捗ります。

簡単にマルチタスクのフローを可視化できるため、不具合の早期発見 ⇒ 修正するデバッグが捗ります

Graphviz Onlineで図を描画する方法

GraphVizをインストールできない人がいるかも(?)しれないので、本記事では”Graphviz Online”を使って図を描画する方法を説明します。

下のボタンから”Graphviz Online”を試すことができます。

Graphviz Online

ボタンを押すとブラウザに、エディタ(左側の黒い画面)とリアルタイムプレビュー(右側の白い画面)が表示されるので、エディタ(左側の黒い画面)に先ほど生成したコードをコピペすれば、図が描画されます。

GraphvizOnline

※ブラウザにはChromeを使いました

このようにオンライン(環境構築不要)でマルチタスクのフローを可視化できます。

”Graphviz Online”を使えば、環境構築不要でフロー図の描画ができます

Cpp-Taskflowで並列処理(マルチタスク)-応用編-

基礎編では「Task 〇」と表示するだけの簡単なマルチタスクを実践しました。

以降からは、簡単な演算をマルチタスクで実践します。

【課題】四則演算のマルチタスク

a = 6, b = 8, c = 0, d = 3, e = 9, f = 0, g = 0で以下の演算を行います。

g = a × b ÷ (d + e)
g = 6 × 8 ÷ (3 + 9)
g = 48 ÷ 12
g = 4

※c = a × b, f = d + e  

タスクフローは以下の通りです。

C++で並列処理・マルチタスク

cとfの演算は並列処理することで高速化できます。

ただし、cとfの演算が完了した後にgの演算を行わないと、正しい解が得られません。

つまり、各タスクの管理(演算タイミング)が重要になってきます。

この課題を”Cpp-Taskflow”でスマートに解決します!

マルチタスクプログラムを実装するときは以下のことを検討します。

  1. どんな処理をタスクにするか?
  2. 並列処理が可能なタスクはどれか?
  3. 各タスクの管理(演算タイミング)は正しいか?

マルチタスクプログラム❶ -sample1.cpp-

基礎編で学んだことを活かして、最初に作成したソースコード(sample1.cpp)が以下です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include "../taskflow/taskflow.hpp"
 
int main(){
tf::Taskflow tf;
 
int a, b, c;
int d, e, f;
int g;
 
// Create A~D task
auto [A, B, C, D] = tf.emplace(
[&a, &b, &c, &d, &e, &f, &g] () {
std::cout << "==== Task A ====" << std::endl;
a = 6, b = 8, c = 0;
d = 3, e = 9, f = 0;
g = 0;
std::cout << "Init Param" << std::endl;
std::cout << "a = " << a << std::endl;
std::cout << "b = " << b << std::endl;
std::cout << "d = " << d << std::endl;
std::cout << "e = " << e << std::endl;
},
[&a, &b, &c] () {
std::cout << "==== Task B ====" << std::endl;
c = a * b;
std::cout << "c = a * b" << std::endl;
std::cout << "c = " << c << std::endl;
},
[&d, &e, &f] () {
std::cout << "==== Task C ====" << std::endl;
f = d + e;
std::cout << "f = d * e" << std::endl;
std::cout << "f = " << f << std::endl;
},
[&c, &f, &g] () {
std::cout << "==== Task D ====" << std::endl;
g = c / f;
std::cout << "g = c / f" << std::endl;
std::cout << "g = " << g << std::endl;
}
);
 
// Precede
A.precede(B); // B runs after A
A.precede(C); // C runs after A
B.precede(D); // D runs after B
C.precede(D); // D runs after C
 
// debug
A.name("Init a~g");
B.name("c = a * b");
C.name("f = d + e");
D.name("g = c / f");
tf.dump(std::cout);
 
// block until finished
tf.wait_for_all();
 
// work of main()
std::cout << "==== main ====" << std::endl;
std::cout << "g - 2 = " << g - 2 << std::endl;
 
return 0;
}

各タスクが引数をもっている以外は、基礎編で学んだ内容ですね。

基礎が大事です!

マルチタスクプログラム❷ -sample2.cpp-

sample1.cppの可読性の向上を考慮して改良したもの(sample2.cpp)が以下です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include "../taskflow/taskflow.hpp"
 
int main(){
tf::Taskflow tf;
 
int a, b, c;
int d, e, f;
int g;
 
// Create A~D task
tf::Task A = tf.emplace([](){}).name("Init a~g");
tf::Task B = tf.emplace([](){}).name("c = a * b");
tf::Task C = tf.emplace([](){}).name("f = d + e");
tf::Task D = tf.emplace([](){}).name("g = c / f");;
 
// Work of A~D task
A.work([&a, &b, &c, &d, &e, &f, &g] () {
std::cout << "==== Task A ====" << std::endl;
a = 6, b = 8, c = 0;
d = 3, e = 9, f = 0;
g = 0;
std::cout << "Init Param" << std::endl;
std::cout << "a = " << a << std::endl;
std::cout << "b = " << b << std::endl;
std::cout << "d = " << d << std::endl;
std::cout << "e = " << e << std::endl;
});
 
B.work([&a, &b, &c] () {
std::cout << "==== Task B ====" << std::endl;
c = a * b;
std::cout << "c = a * b" << std::endl;
std::cout << "c = " << c << std::endl;
});
 
C.work([&d, &e, &f] () {
std::cout << "==== Task C ====" << std::endl;
f = d + e;
std::cout << "f = d + e" << std::endl;
std::cout << "f = " << f << std::endl;
});
 
D.work([&c, &f, &g] () {
std::cout << "==== Task D ====" << std::endl;
g = c / f;
std::cout << "g = c / f" << std::endl;
std::cout << "g = " << g << std::endl;
});
 
// Precede
A.precede(B); // B runs after A
A.precede(C); // C runs after A
B.precede(D); // D runs after B
C.precede(D); // D runs after C
 
// debug
tf.dump(std::cout);
 
// block until finished
tf.wait_for_all();
 
// work of main()
std::cout << "==== main ====" << std::endl;
std::cout << "g - 2 = " << g - 2 << std::endl;
 
return 0;
}

コードの行数は少し増えましたが、可読性が良くなったと思いませんか?

好みもありますが、各タスクの生成と処理を同時に書くと、可読性が悪くなる印象を受けました。

また、事前に検討したタスクフロー図(設計図)に従い、ソースコードを作成するので…

【Cpp-Taskflowによるソースコード作成手順】

  1. 各タスクに名前を付ける
  2. 各タスクの処理を書く

という流れで作成する方が、読み手・書き手どちらにも理解しやすいソースコードになります。

可読性の良いソースコードを作成することは、チームメンバーだけでなく”未来の自分”のためにもなります!

マルチタスクプログラム➌ -sample3.cpp-

sample2.cppは簡単な演算しか実装していませんが、今後は各タスクに複雑な処理を実装するかもしれません。

なので、ソースコードの”拡張性”を考慮して改良したもの(sample3.cpp)を作成します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include "../taskflow/taskflow.hpp"
 
// For Work B
int mult(int x, int y){
return x * y;
}
 
// For Work C
int add(int x, int y){
return x + y;
}
 
// For Work D
int divs(int x, int y){
return x / y;
}
 
// For Work main()
int sub(int x, int y){
return x - y;
}
 
int main(){
tf::Taskflow tf;
 
int a, b, c;
int d, e, f;
int g;
 
// Create A~D task
tf::Task A = tf.emplace([](){}).name("Init a~g");
tf::Task B = tf.emplace([](){}).name("c = a * b");
tf::Task C = tf.emplace([](){}).name("f = d + e");
tf::Task D = tf.emplace([](){}).name("g = c / f");;
 
// Work of A~D task
A.work([&a, &b, &c, &d, &e, &f, &g] () {
std::cout << "==== Task A ====" << std::endl;
a = 6, b = 8, c = 0;
d = 3, e = 9, f = 0;
g = 0;
std::cout << "Init Param" << std::endl;
std::cout << "a = " << a << std::endl;
std::cout << "b = " << b << std::endl;
std::cout << "d = " << d << std::endl;
std::cout << "e = " << e << std::endl;
});
 
B.work([&a, &b, &c] () {
std::cout << "==== Task B ====" << std::endl;
c = mult(a, b);
std::cout << "c = a * b" << std::endl;
std::cout << "c = " << c << std::endl;
});
 
C.work([&d, &e, &f] () {
std::cout << "==== Task C ====" << std::endl;
f = add(d, e);
std::cout << "f = d + e" << std::endl;
std::cout << "f = " << f << std::endl;
});
 
D.work([&c, &f, &g] () {
std::cout << "==== Task D ====" << std::endl;
g = divs(c, f);
std::cout << "g = c / f" << std::endl;
std::cout << "g = " << g << std::endl;
});
 
// Precede and gather
A.precede(B, C); // A run before B, C (A -> B, A -> C)
D.gather(B, C); // D runs after B, C (B -> D, C -> D)
 
// debug
tf.dump(std::cout);
 
// block until finished
tf.wait_for_all();
 
// work of main()
std::cout << "==== main ====" << std::endl;
std::cout << "g - 2 = " << sub(g, 2) << std::endl;
 
return 0;
}

sample2とsample3の違いは、各タスクの処理を”関数化”している点です。

簡単な四則演算だと関数化する旨味は少ないですが、複雑な処理(アルゴリズム)でも”関数化”しておけば、ソースコードの”拡張性”および”可読性”が向上します。

また、チーム開発では、関数やクラス毎に担当者(チームメンバ)を割り振って開発する方が開発効率が向上します。

あと好みもありますが、”Precede”や”Gather”は【1 on 1】よりも【1 on N】または【N on 1】で書いた方が「並列処理」していることを直観的に理解しやすい印象を受けました。

  • 処理を意味のある単位で”関数化”することで、ソースコードの”拡張性”と”可読性”が向上します
  • 関数やクラス毎に担当者を決めて分散開発する方が開発効率が良いです
スポンサーリンク

ビルドと動作確認

”Cpp-Taskflow|GitHub”をクローン生成あるいはダウンロードすると、以下の場所に”taskflow.hpp”があります。

cpp-taskflow/taskflow/taskflow.hpp

”Cpp-Taskflow”は”ヘッダーオンリーなライブラリ”なので、”taskflow.hpp”をインクルードするだけで使うことができます。

本記事で説明したソースコードは、以下の記事で紹介したWSL(Windows Subsystem for Linux)で動作確認しています。

VSCodeからWSLを使う
【WSL】Windows10とUbuntuとVSCodeで快適なプログラミング環境を構築Windows Subsystem for Linux(WSL)とVisual Studio Code(VSCode)で快適なプログラミング環境を構築する方法を説明します。C言語とC++とPythonなどのプログラミング言語を使う人にオススメの記事です。...

※C++17がビルドできる環境ならWindows/Linuxどちらでも動きます。

ソースコードのビルドから実行までを順番に説明していきます。

前準備

以下のコマンドで前準備を行います。

git clone https://github.com/cpp-taskflow/cpp-taskflow.git
cd cpp-taskflow/
mkdir Myproject
cp -r taskflow Myproject/include/

本記事で説明したソースコード(sample3.cppなど)を”Myproject”に保存します。

ビルド&実行

以下のコマンドで”Myproject”に移動します。

cd Myproject

sample3.cppを以下のコマンドでビルドします。

g++ sample3.cpp -std=c++17 -O2 -pthread -o CppTaskflow_Sample3

なお、本記事で説明したソースコードは”taskflow.hpp”を相対パスでインクルードしていますが、以下のように絶対パスでインクルードしたとします。

1
2
3
4
 
// #include "../taskflow/taskflow.hpp"
#include <taskflow/taskflow.hpp>
 

その場合は、以下のコマンドでビルドします。

g++ sample3.cpp -I include/ -std=c++17 -O2 -pthread -o CppTaskflow_Sample3

CppTaskflow_Sample3という実行ファイルが生成できたらビルド成功です。

以下のコマンドで実行します。

./CppTaskflow_Sample3

makefile

ビルドおよび実行を効率的に実施するために、makefileを作成します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# compile option
CFLAGS = -std=c++17
 
# include path
INCLUDE = include/
 
# C++(cpp) source files
SOURCES = sample3.cpp
 
# Target
TARGET = CppTaskflow_Sample3
 
all:
$(CXX) $(SOURCES) -I $(INCLUDE) $(CFLAGS) -o $(TARGET) -O2 -pthread
# g++ sample3.cpp -I include/ -std=c++17 -O2 -pthread -o CppTaskflow_Sample3
 
run:
./$(TARGET)
 
clean:
rm -f $(TARGET)
 

ビルドするソースコードを変更するときは”SOURCES”を書き換え、実行ファイル名を変更するときは”TARGET”を書き換えます。

以下のコマンドでビルド

make

以下のコマンドで実行

make run

以下のコマンドで実行ファイルの削除

make clean

一連の流れを実施したときのターミナル画面は以下の通りです。

Cpp-Taskflowのビルドと実行

ちゃんと以下の演算ができていますね(*・ω・)ノ♪

g = a × b ÷ (d + e)
g = 6 × 8 ÷ (3 + 9)
g = 48 ÷ 12
g = 4

※c = a × b, f = d + e  

最後に”タスク D”が算出した「g = 4」に対し、main関数で「g – 2 = 2」の演算をしました。

(これで、main関数とタスク間で数値のやり取りができることを確認しました)

makefileでコマンドを自作することで、ビルドなどを効率良く実施できます

まとめ

長文読解お疲れさまでした。本記事は以下の【文章構成】で説明しました。

【文章構成】

  • 前編:マルチタスク・排他制御などの情報処理技術の説明
  • 後編:マルチタスクプログラミングの実践 -基礎から応用まで-

本記事を一読しただけでは、よく分からない!という人でも、本記事を繰り返し読み、手を動かしながらソースコードの動作を理解していけば…

C++でマルチタスクを実装できるようになります!

本記事をきっかけに…

くるる
くるる
マルチタスク・排他制御などの情報処理技術に興味をもった!
知りたガール
知りたガール
ソースコードの”可読性”や”拡張性”などのソフトウェア品質を向上させる”ノウハウ”をもっと勉強したい!

という人が増えると嬉しいです(*・ω・)ノ♪

はやぶさ
はやぶさ
理系応援ブロガー”はやぶさ”@Cpp_Learningは頑張る理系を応援します!

(後編 完)

おまけ -メッセージとおすすめの書籍-

時間に余裕のある人は読んでみてね↓

メッセージ

フクロウの”くるる”が笑顔で呟く

くるる
くるる
めっちゃ勉強になった!
はやぶさ
はやぶさ
そう言ってもらえると嬉しい!書いてよかったー

さっきまで笑顔だっったのに、今度は少し悲しい表情で呟く

くるる
くるる
Pythonプログラミングやソフトウェア設計(UML)の勉強してるけど、排他制御やマルチタスクとか全然知らなかった…
くるる

表情が”くるくる”変わって可愛い…!

”くるるちゃん”のように、落ち込んでる人いますか?

大丈夫です!これから学べば良いだけの話です!むしろ、この記事で勉強できてラッキーでしたね(*・ω・)ノ♪

本記事、実は後編の内容のみを書く予定でした(サンプルコードもsample1.cppだけ説明して終わる予定でした)。

しかし…

プログラミングはできるけど、マルチタスクなどの情報処理技術がよく分からない(´;ω;`)
知りたガール
知りたガール
ソースコードの”可読性”・”拡張性”などは気してるけど、何を学べば良いか分からない(´;へ;`)

という人をサポートしたい!という想いから、一気に書き上げました(結構書くの大変でした。。)

そのため、ライブラリの使い方だけでなく”考え方”や”マインド”なども書いたことで、長文になってしまいましたが、読み易くなるように最大限配慮したつもりです。。

くるる
くるる
はやぶさ先生ありがとう!本当に勉強になりました!!駆け出しエンジニア”くるる”はもっと勉強してクールなエンジニア”くるる”に進化します!
くるる

本記事を読んだ”くるるちゃん”のように落ち込んだ後、直ぐに立ち直って貪欲に学んでほしいと思います!応援してます!!

おすすめの書籍

うずうず…”くるる”が羽毛を揺れしながら呟く

くるる
くるる
勉強方法やおすすめの本教えてほしいなー

はい!「もっと勉強したい!」というモチベーションの高い人のために勉強方法や本を紹介!

基本情報技術者試験

情報処理技術やIT用語の理解に不安のある人は、基本情報技術者試験を受験し、実力を把握するのが良いと思います。

勉強時間を確保できない!という人でも「基本情報技術者試験の参考書」を一読して理解を深め、IT用語の辞書として手元に置いておくと良いですよ。

以下の参考書は、イラスト多めで読みやすい良本です。

キタミ式イラストIT塾 基本情報技術者 平成31/01年 (情報処理技術者試験)
created by Rinker

  • Kindleで探す
  • Amazonで探す
  • 楽天市場で探す
  • Yahoo!ショッピングで探す

平成31/01年 イメージ&クレバー方式でよくわかる 栢木先生の基本情報技術者教室
created by Rinker

  • Kindleで探す
  • Amazonで探す
  • 楽天市場で探す
  • Yahoo!ショッピングで探す

※動物好きなので、イメージ&クレバー方式の本をメインに使ってました

実装テクニック

本記事でソースコードの”可読性”や”拡張性”という単語を使いましたが、私自身の言葉を使えば…

『読み手や使い手のことを想ってソースコードを書くのが正解』

というのが私の思想です。

色々な人が作成したソースコードを眺め、自身も沢山コードを書いて…色々と苦悩しながら行き着いた先は”シンプルなコード”でした。

ここでいう”シンプルなコード”というのは、”短いコード”という意味ではなく…という説明をすると長くなるので『リーダブルコード 』を読んでください!

より良いコードを書くためのシンプルで実践的なテクニックを学べる良本です。

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)
created by Rinker

  • Amazonで探す
  • 楽天市場で探す
  • Yahoo!ショッピングで探す

ソフトウェア設計

本記事では、処理の流れを意識したソースコードを書いたり、処理の一部を”関数化”することで、ソースコードの”可読性”や”拡張性”を向上させました。

また、事前にソフトウェア設計を行い、クラス化などを検討した上で実装した場合も、”可読性”や”拡張性”が向上します。

”ソフトウェア設計”や”UML”が学べる記事を紹介します(記事内で本の紹介もあります)。

『ソフトウェア設計図を描く前に知っておきたいUMLの基礎知識』が学べます!
UML入門
【UML入門】ソフトウェア設計図を描く前に知っておきたいUMLの基礎知識こんにちは。 現役エンジニアのはやぶさ@Cpp_Learningです。 より正確にはソフトウェアの仕様検討から設計・実装まで...
UMLについて学んだら”PlantUML”でソフトウェア設計図を描いてみよう!
Visual Studio CodeからPlantUMLを使う
【UMLツール】Visual Studio CodeとPlantUMLでソフトウェア設計図を描く方法こんにちは。 Visual Studio Codeヘビーユーザーの”はやぶさ”@Cpp_Learningです。 前回『UML...
ゲームソフト開発を題材に”オブジェクト指向”と”ソフトウェア設計”を楽しく学ぼう!
python(pygame)でゲームを作る
【Python】ゲームソフト開発を題材にしたオブジェクト指向入門ゲームソフト作成を題材にした「オブジェクト指向」の入門記事を書きました。オブジェクト図/クラス図の書き方や考え方のポイントも説明した実践的な内容になっています!ソフトウェア設計やソフトウェア開発を学びたい人にオススメの記事です!...
くるる
くるる
どの記事も”くるる”が大活躍します!
はやぶさ
はやぶさ
”くるるちゃん”と一緒に楽しく学んで、ソフトウェア技術を習得してね!ファイト!!

(完)

スポンサーリンク
スポンサーリンク

PICK UP BOOKS

  • 数理モデル入門
    数理モデル
  • Jetoson Nano 超入門
    Jetoson Nano
  • 図解速習DEEP LEARNING
    DEEP LEARNING
  • Pythonによる因果分析
    Python
JPRiDE ワイヤレスイヤホン

【JPRiDE 708レビュー】おすすめのBluetoothワイヤレスイ...

【ShellExecution】C++とPythonを連携させてC++コ...

標準入出力でPythonとC/C++連携
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.