◆PROCESSING 逆引きリファレンス
カテゴリー:制御系
マルチスレッドを実現するには
【解説】
PROCESSINGはJavaをベースにした言語ですので、Javaのマルチスレッド機能がそのまま利用可能です。
スレッド?と思われた方は、下記コラムに簡単な説明を掲載しておきましたので、参照してください。
PROCESSINGでマルチスレッドを実現するには、以下の3つの方法があります。
- ケース1:JavaのThreadクラスを利用する
- ケース2:JavaのRunnableインタフェースを利用する
- ケース3:PROCESSINGの thread 命令を利用する
どの方法でもマルチスレッドが実現可能です。もっとも簡単なのはPROCESSINGの命令を使う事ですね。
ただしどの方法であっても、サブスレッド側で画面に描画する処理を行わせることはできません。画面の描画は、メインスレッドにある draw() メソッドだけが行えます。
どうしてもサブスレッドから描画を行いたい場合は、メインスレッドにある draw() メソッドへ「描画を依頼する」などの工夫が必要となるでしょう。
またどの方法を使う場合でも、マルチスレッド特有の注意事項(変数の書き換えや処理順番の制御など)があります。
特に標準エディタでコーディングしている場合は、変数の競合に注意してください。
標準エディタの場合、メインスレッドのクラスが隠蔽されているため、setup() や draw() などのメソッドの外側に書いた変数は、すべてグローバル変数になります。
グローバル変数はサブスレッドからも見えます。なので簡単に書き換えることができます。でも不用意に書き換えると不具合を発生させる原因となります(汗)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// これはグローバル変数 String myName; void setup(){ // 適当なものを代入 myName = "KENJI"; } void draw(){ … } // ここからサブスレッド class SubThread1 extends Thread { @Override public void run(){ // myName を書き換えることが可能!(注意) myName = "YUUKO"; } } |
上記例では myName という変数がグローバル変数です。サブスレッド側からも書き換えが可能ですので、注意が必要です。
Javaのマルチスレッドに関しては、以下のサイト様に非常によくまとめられています。本格的なマルチスレッドプログラミングをしたい方は、ぜひ熟読されると良いでしょう。私も勉強させて頂きました。ありがとうございます。
【詳細】
ケース1:JavaのThreadクラスを利用する
JavaのThreadクラスを継承したクラス(サブスレッドクラス)を定義します。
メインスレッドからサブスレッドクラスのインスタンスを生成し、start() メソッドを発行する事で、サブスレッドクラスの run() メソッドに書かれた処理が別スレッドとして動作します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
//------------------------ // ここはメインスレッド //------------------------ void setup(){ // サブスレッドクラスを生成 SubThread1 sample1 = new SubThread1(); // 実行開始 sample1.start(); } void draw(){ … } //------------------------ // ここからサブスレッド //------------------------ class SubThread1 extends Thread { @Override public void run(){ // ここがサブスレッドとして動作する } } |
run() メソッドに書かれた処理が全て終了すると、サブスレッドは自動的に停止します。
もしもサブスレッドをしばらく動作させておきたい場合は、run() の中に loop 処理を記述する事になりますが、メインスレッド側からサブスレッドを強制停止する事は非推奨となっている事に注意してください。
サブスレッドを安全に停止するためには、サブスレッド側で loop を終わる手段を設けておき、メインスレッド側から停止を依頼する事になります。
詳しくは、下記サンプルプログラムを参照してください。
ケース2:JavaのRunnableインタフェースを利用する
JavaのRunnableインタフェースを実装クラス(サブスレッドクラス)を定義します。
メインスレッドからサブスレッドクラスのインスタンスを生成し、さらに Thread クラスにサブスレッドクラスのインスタンスを与えてThreadインスタンスを作成します。
Threadインスタンスの start() メソッドを発行する事で、サブスレッドクラスの run() メソッドに書かれた処理が別スレッドとして動作します。
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 |
//------------------------ // ここはメインスレッド //------------------------ void setup(){ // サブスレッドクラスを生成 SubThread2 sample2 = new SubThread2(); //Threadクラスを生成 Thread subThread = new Thread(sample2); //実行開始 subThread.start(); } void draw(){ … } //------------------------ // ここからサブスレッド //------------------------ class SubThread2 implements Runnable { @Override public void run(){ // ここがサブスレッドとして動作する } } |
ケース1の手順に比べて一手間多いのですが、Javaとしてはこちらのほうが推奨手順となっています。
run() メソッドに書かれた処理が全て終了すると、サブスレッドは自動的に停止します。
loop しているサブスレッド側の処理を終了させる事については、ケース1と同じ注意点があります。
ケース3:PROCESSINGの thread 命令を利用する
PROCESSINGには標準で、あるメソッドをサブスレッドとして実行する thread() 命令があります。
使い方は簡単です。
thread( “サブスレッドで動かしたいメソッド名” );
と記述するだけです。
1 2 3 4 5 6 7 8 9 10 11 |
void setup(){ // SubThread() メソッドをサブスレッドで実行 thread("SubThread"); } void draw(){ … } void SubThread(){ //ここがサブスレッドで実行される } |
お手軽、簡単、便利、すばらしい!。
ただしサブスレッドとして動作させるメソッド(上記例なら SubThread )は、戻り値なし(void)で、かつ引数なしの関数である必要があります。
メソッドの処理が全て終了すると、サブスレッドは自動的に停止します。
loop しているサブスレッド側の処理を終了させる事については、ケース1と同じ注意点があります。
【関連記事】
なし
サンプルプログラム
ケース1:JavaのThreadクラスを利用する例:
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 |
/** * PROCESSING 3.0 マルチスレッド Sample * @auther MSLABO * @version 2018/09 1.0 */ //------------------------ // ここはメインスレッド //------------------------ SubThread1 sample1 = null; void setup(){ size(200,200); textSize(16); textAlign(LEFT,TOP); } void draw(){ background(200); fill(0); text("I am Main Thread.",8,0); if( sample1 != null && sample1.running ){ // サブスレッドが実行中 text("Sub Thread is Running.",8,20); } else { // サブスレッドは停止中 sample1 = null; } } void keyPressed(){ if( key == 's' || key == 'S' ){ // サブスレッド停止中なら生成する if( sample1 == null ){ // 生成 sample1 = new SubThread1(); // 実行開始 sample1.start(); } } else if(key == 'q' || key == 'Q'){ // サブスレッド実行中なら停止を依頼する if( sample1 != null && sample1 .running){ sample1.stopRunning(); } } } //------------------------ // ここからサブスレッド //------------------------ class SubThread1 extends Thread { // 実行許可FLG private boolean running = true; @Override public void run(){ // 実行許可がある間だけ繰り返す while(running){ // 何かの処理を記述(今回は寝るだけ:笑) try{ println("I am Sleeping..."); sleep(1000); } catch( InterruptedException ex){ ex.printStackTrace(); } } println("Sub Thread exit"); } // メイン側より停止指示を受け取るメソッド public void stopRunning(){ running = false; } } |
Sキーを押下するとサブスレッドが開始します。サブスレッドは1秒寝ながらコンソールにメッセージを出し続けます。
Qキーを押下するとサブスレッドが停止します。
loop しているサブスレッドを停止する方法に注目してください。メインスレッドから停止依頼を受ける(loopを継続するFLGを書き換える)事で、サブスレッド自身が処理を停止しています。
ケース2:JavaのRunnableインタフェースを利用する例:
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 |
/** * PROCESSING 3.0 マルチスレッド Sample * @auther MSLABO * @version 2018/09 2.0 */ //------------------------ // ここはメインスレッド //------------------------ SubThread2 sample2 = null; void setup(){ size(200,200); textSize(16); textAlign(LEFT,TOP); } void draw(){ background(200); fill(0); text("I am Main Thread.",8,0); if( sample2 != null && sample2.running ){ // サブスレッドが実行中 text("Sub Thread is Running.",8,20); } else { // サブスレッドは停止中 sample2 = null; } } void keyPressed(){ if( key == 's' || key == 'S' ){ // サブスレッド停止中なら生成する if( sample2 == null ){ // サブスレッドクラスを生成 sample2 = new SubThread2(); //Threadクラスを生成 Thread subThread = new Thread(sample2); //実行開始 subThread.start(); } } else if(key == 'q' || key == 'Q'){ // サブスレッド実行中なら停止を依頼する if( sample2 != null && sample2 .running){ sample2.stopRunning(); } } } class SubThread2 implements Runnable { // 実行許可FLG private boolean running = true; // ここが実行される @Override public void run(){ // 実行許可がある間だけ繰り返す while(running){ // 何かの処理を記述(今回は寝るだけ:笑) try{ println("I am Sleeping..."); Thread.sleep(1000); } catch( InterruptedException ex){ ex.printStackTrace(); } } println("Sub Thread exit"); } public void stopRunning(){ running = false; } } |
基本的な動作はケース1と同じです。
ケース3:PROCESSINGの thread 命令を利用する例:
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 |
** * PROCESSING 3.0 マルチスレッド Sample * @auther MSLABO * @version 2018/09 3.0 */ //------------------------ // ここはメインスレッド //------------------------ boolean running = false; void setup(){ size(200,200); textSize(16); textAlign(LEFT,TOP); } void draw(){ background(200); fill(0); text("I am Main Thread.",8,0); if( running ){ // サブスレッドが実行中 text("Sub Thread is Running.",8,20); } } void keyPressed(){ if( key == 's' || key == 'S' ){ // サブスレッド停止中なら生成する if( running == false ){ running = true; thread("SubThread3"); } } else if(key == 'q' || key == 'Q'){ // サブスレッド実行中なら停止を依頼する if( running ){ running = false; } } } //------------------------ // このメソッドはサブスレッド //------------------------ void SubThread3(){ // 実行許可がある間だけ繰り返す while(running){ // 何かの処理を記述(今回は寝るだけ:笑) try{ println("I am Sleeping..."); Thread.sleep(1000); } catch( InterruptedException ex){ ex.printStackTrace(); } } println("Sub Thread exit"); } |
基本的な動作はケース1と同じです。
コラム:スレッドに関する予備知識
スレッドとは?
スレッドとは何でしょうか?
ネット掲示板などでは「スレを立てる」なんて言いますが、プログラムにおけるスレッドとは「プログラムの流れを実行する物」の事を指します。
「プログラムの流れ」とは、起動されたアプリケーションが行う様々な処理の事です。
例えば、ある家庭の女性(プログラム)が
- 朝起きる
- 顔を洗う
- 着替える
- 出勤する
という作業を実行するとして、この1~4の行動が「プログラムの流れ」となります。
そして、この「プログラムの流れ」を実行している人(つまり女性)の事をスレッドと呼びます。
(画像URL:illust-AC 様 acworks さん、すーさん)
この例では流れは1つ(1から4)しかありません。この流れが全てですので、この流れを実行する人(女性)の事をメインスレッドと呼びます。
では、以下のような流れがある場合はどうでしょうか?
- 朝起きる
- 顔を洗う
- 彼に弁当の用意をしてもらう
- 着替える
- 出勤する
3番目の手順に「彼に依頼する」項目が増えました。女性は「彼」とは別人です。依頼された彼氏は
- 料理をする
- 弁当を詰める
といった行動(別の流れ)を取るものとします。
このように「主となる流れ」から派生した別の流れを実行する人(彼)もスレッドです。そして別の流れを実行する人(彼)の事を「サブスレッド」と呼びます。
(画像URL:illust-AC 様 acworks さん、すーさん)
このように1つのプログラム内に複数の手順があり、それぞれを別々に実行させる事をマルチスレッドと呼びます。
メインスレッドとサブスレッドは、基本的には別々に(並行に)動作します。ですので多くの場合、全てを1つのスレッドで行うのに比べて効率が良くなります。
特に最近のPCやスマホのように複数のCPUやコアを搭載したコンピュータでは、威力を発揮する事になります。
マルチスレッドを行う上での注意
マルチスレッドはメリットが大きいのですが、注意も必要です。処理によっては期待した動作にならない事があるためです。
例えば「料理をするお母さん=メインスレッド」、「遊ぶ子供=サブスレッド」というケースを元に説明しましょう。
お母さんは料理をする前に、材料を「台」に置きます。そして別の作業(調味料を混ぜるなど)をした後で、「台」から材料を取って調理するものとします。
子供は遊ぶ前に、クレヨンを「台」に置きます。そして別の作業(紙を用意するなど)をした後で、「台」からクレヨンを取って絵を描くものとします。
狭い家だったので(汗)、「台」はお母さんと子供で共用しているとしましょう。しかもこの「台」には、小さな台なので1度に1種類のものしか乗せられません。
さて、無事に作業が進むでしょうか?。
(画像URL:illust-AC 様 かえるWORKS さん、麦 さん)
このケースではお母さんが台に置いた材料を取り出す前に、子供がクレヨンを置いてしまうかもしれませんよね? あるいは子供がクレヨンを置いて紙の準備をしている間に、お母さんが台に材料を置いてしまうかもしれません。
このように同じ変数(台)を複数のスレッドが共用している場合には、意図しないタイミングで内容が書き換わってしまう事があるのです。
マルチスレッドにおいては、この例のような変数の意図しない書き換えや、処理順番の不整合といった問題が発生します。こういった問題が発生しないようにプログラミングするのは、プログラマーの腕の見せどころです。
本ページで利用しているアイコン画像は、下記サイト様より拝借しております。各画像の著作権は、それぞれのサイト様および作者にあります。