マルチスレッドでたくさんの仕事を一度にこなしたい (前編)

猫の手も借りたい

どうも、エイタです。

この時期、忙しくて「猫の手も借りたい」なんて人も多いんじゃないでしょうか? 「自分がもう 1 人いれば」とか「この作業をやってる時間がもったいない」とか、複数の仕事を同時にこなすことができたら・・・なんて思っちゃいますよね。

アプリケーションの操作中にも、そういうことをよく考えます。 時間がかかる処理を待っている間にコーヒーブレイクもいいですが、その時間を使って別の処理ができれば時間を有効に使えますよね。

「こんな話をするってことは PowerBuilder でもできるってことでしょ?」とお気づきのあなた。 冴えてますね。

そんなわけで今回は PowerBuilder で複数の処理を同時に行う「マルチスレッド処理」の実装について説明します。


PowerBuilder でマルチスレッド

マルチスレッド処理のざっくりしたイメージはこんな感じです。

シングルスレッドとマルチスレッド

パラレルに処理を行うことで CPU を効率的に使用でき、パフォーマンスの向上が期待できます。 ただし CPU のコア/スレッド数に左右されますし、負荷状況によって処理時間が変動するため、1 処理ぶんの時間ですべての処理が完了するというわけではありません。

このように、複数の処理を同時に行うには処理を非同期にする必要があります。 PowerBuilder で非同期処理を行うには「共有オブジェクト」を利用するのですが、手順は大まかに下記のようになります。

  • 共有オブジェクトの登録 (SharedObjectRegister)
  • 共有オブジェクトの参照/取得 (SharedObjectGet)
  • 共有オブジェクトの登録解除 (SharedObjectUnregister)

それぞれについて少し説明しましょう。


共有オブジェクトの登録

共有オブジェクトの登録は SharedObjectRegister 関数を使います。

SharedObjectRegister 関数はそのプロセス(アプリケーション)で共有できるオブジェクトを登録します。登録した共有オブジェクトはアプリケーションのどこからでも参照することができます。

【構文】

SharedObjectRegister ( readonly string classname, readonly string instancename )

引数はいずれも文字列です。 もし uo_sample というオブジェクトを “sample1” という名前で共有オブジェクトとして登録する場合は下記のようになります。

  SharedObjectRegister ( "uo_sample", "sample1" )

なお、非同期処理は非表示オブジェクト (NonVisualObject) のみで行うことができ、コントロールやウィンドウなどの GraphicObject を直接扱うことができません。 たとえばカスタムビジュアルオブジェクトを共有オブジェクトにして非同期処理を実行することはできません。 また、関数の引数としてデータウィンドウコントロールを渡したり、オブジェクトのインスタンス変数にウィンドウを格納したりしても正しく動作しません。

このため、共有オブジェクトの処理結果を画面に反映させるためには工夫が必要になります。 その工夫については後編でご紹介します。


共有オブジェクトの参照/取得

SharedObjectRegister 関数で登録された共有オブジェクトを利用する際は、SharedObjectGet 関数を使用します。

【構文】

SharedObjectGet ( readonly string instancename, ref powerobject objectinstance )

第 1 引数は SharedObjectRegister で指定した共有名、第 2 引数は取得したオブジェクトを格納するオブジェクト変数です。

先ほど “sample1” という名前で登録した uo_sample を取得してみましょう。

  uo_sample luo_sample //共有オブジェクトを格納する変数
  
  //共有オブジェクトを取得
  SharedObjectGet( "sample1", luo_sample )

これで登録したオブジェクトを取得することができます。 以降は格納したオブジェクト変数 luo_sample を利用して関数を呼び出すことができます。 通常、オブジェクトを使用する場合は CREATE 文でのインスタンス化を行いますが、SharedObjectGet で参照する場合は必要ありません。

ところで uo_sample には、とても長い処理を行う関数 of_longtimeproc() が実装されているとします。 この処理は時間がかかりすぎるあまり、ついついお菓子に手が伸びてしまうためダイエットに勤しむユーザーにはとても不評です。

このような処理は非同期で実行し、その間にほかの作業をしてもらいましょう。 お菓子を食べる暇は与えません。

非同期で処理するためには、オブジェクトの関数を POST で呼び出します。

  uo_sample luo_sample //共有オブジェクトを格納する変数
  
  //共有オブジェクトを取得
  SharedObjectGet( "sample1", luo_sample )
  
  //共有オブジェクト関数を非同期で呼び出し
  luo_sample.POST of_longtimeproc()

これにより、of_longtimeproc() は現在の処理とは非同期で実行されますので、結果を待たずに後続の処理が動き続けます。

さらに、別の処理を並行して実行することもできます。 下記の処理は共有オブジェクトを登録/取得し、[処理A] と [処理B] を並行して実行するスクリプトです。

  uo_sample luo_sample1, luo_sample2 //共有オブジェクトを格納する変数
  
  //共有オブジェクトを登録 ([処理A]用、[処理B]用に別のオブジェクトとして登録する)
  SharedObjectRegister ( "uo_sample", "sample1" )
  SharedObjectRegister ( "uo_sample", "sample2" )
  
  //それぞれ別のインスタンスとして共有オブジェクトを取得
  SharedObjectGet( "sample1", luo_sample1 )
  SharedObjectGet( "sample2", luo_sample2 )
    
  //共有オブジェクト関数を非同期で呼び出し
  luo_sample1.POST of_longtimeproc() //[処理A]
  luo_sample2.POST of_littlelongproc() //[処理B]

非同期の処理中は画面の操作が可能なので、ユーザーは処理を待っている間に同じアプリケーションで別の作業をすることができます。 ただし、ウィンドウを閉じたりボタンを押したりすることも可能なので、処理中に行われては困る操作については制御してあげる必要があります。 また、当然ですが [処理A] の結果を受けて [処理B] を行うような処理ではこの方法は使えません。

もうひとつ注意点。 POST で呼び出すと説明しましたが、仕様上 POST 呼び出しでは戻り値を受け取ることができません。 このため関数が結果を返し、その値を後続の処理で使用するような使い方はできないので気を付けましょう。


共有オブジェクトの登録解除

登録したオブジェクトは破棄しなければなりません。 そのままにしておくと動作が不安定になる場合があります。 たとえば、デバッグのため IDE から実行し、共有オブジェクトを破棄せずにアプリケーションを終了するとPowerBuilder IDE がクラッシュします。

共有オブジェクトの破棄には SharedObjectUnregister 関数を使います。

【構文】

SharedObjectUnregister ( readonly string instancename )

少なくともアプリケーションを終了する前には共有オブジェクトを破棄しましょう。 使い方は登録したオブジェクトの共有名を渡すだけです。

  //共有オブジェクトを破棄
  SharedObjectUnregister( "sample1" )
  SharedObjectUnregister( "sample2" )

とは言っても、共有オブジェクトをたくさん登録しすぎて「どんな名前でオブジェクトを登録したのか?」、「すでにオブジェクトは破棄されているのか?」 がわからなくなる人もいると思います。 ・・・僕です。

そんな僕のために PowerBuilder は素晴らしい関数を用意してくれていました。 それが SharedObjectDirectory です。 この関数は現在登録されている共有オブジェクトのリストを返してくれます。

【構文】

[構文1] SharedObjectDirectory ( ref string instancenames[] )
[構文2] SharedObjectDirectory ( ref string instancenames[], ref string classnames[] )

[構文1] は共有名のみ、[構文2] は共有名とそのオブジェクト名をそれぞれ配列に格納して返してくれます。 一括ですべてのオブジェクトを破棄する場合は 1 つめの構文で十分ですね。

  string ls_names[]
  integer i
  
  //登録されている共有オブジェクトの名称を取得
  SharedObjectDirectory( ls_names ) 
  
  //共有オブジェクトをすべて破棄
  for i = 1 to UpperBound( ls_names )
      SharedObjectUnregister( ls_names[i] )
  next

ここまでのまとめ

マルチスレッド処理では「共有オブジェクト」を利用することで複数の処理を同時に行うことにより、うまく実装すればパフォーマンスの向上が期待できます。 また、処理の待ち時間のあいだにユーザーにアプリケーションを操作させることも可能です。

これで猫に手伝ってもらう必要はなくなるかもしれませんね。

最後にマルチスレッド処理を実装するポイントをおさらいします。

  • 非同期処理は「共有オブジェクト」を登録 (SharedObjectRegister)、参照 (SharedObjectGet)、破棄 (SharedObjectUnregister)
  • 共有オブジェクトから直接 graphicobject (Window や コントロールなど、目に見えるオブジェクト) にはアクセスできない
  • 非同期にしたい処理は POST で呼び出す (戻り値は受け取れない)

このポイントを踏まえ、次回は実際にアプリケーションをつくって「複数データウィンドウを同時に検索」にチャレンジしてみます。 乞うご期待!!

 

以上、エイタでした!

テクニカルブログ 一覧を見る
PowerBuilder マイグレーション
PowerBuilder学習、動画で始めちゃう?