4Dリモートにおけるプリエンプティブ・プロセス ([原文](http://library.4d-japan.com/00_xfer/00_tn/19-10_PreemptiveProcessOn4DRemote.pdf)
はじめに
マルチコアのコンピュータシステムが簡単に使えるようになり、マシンに追加したコアの恩恵を受けられるアプリケーションをカスタマーは期待しています。伝統的には、4Dはシングルスレッド・アプリケーションでしたが、追加コアの利点を使えるように4Dアプリケーションの見方をアップデートしてきました。最初に、4Dのバックエンド部分、例えばSQLとHTTPサーバーなどは、マルチコアの利点を使用できるようにアップデートされました。その後、ランゲージ・コマンドは革新的にスレッドセーフになりました。元々はシングルユーザー・モードあるいはサーバーでコードが実行された時に有効でした。4D v17 R4からは、4Dリモート(例:4D Client)でもマルチコア・システムでの使用が可能です。このテックノートでは、プリエンプティブ・モードの使用の必要性と、スレッドセーフ処理を確実にするステップとについて説明しています。
概要
v17 R4の新機能について語る前に、最初にこの機能の条件と必要環境を振り返っておきましょう。
プリエンプティブとコオペラティブ・モードの違い
コオペラティブ・モードは、4D v15 R5までは4Dで唯一のモードでした。コオペラティブとは、4DがシングルCPUを共有してコオペレート(一緒に作業)することを意味していました。時には、IDLEコマンドが使われた時のように、明示的にコオペレートしたり、他のほとんどの4Dランゲージ・コマンドが使われた時のように、暗示的にコオペレートしたりしていました。これは、内部的にはIDLEコマンドを使ってCPUに別の処理を渡していたからです。結果的に、コオペラティブに実行する時には、一つのCPUコアだけが使われていました。
プリエンプティブ・モードでは、IDLEコマンドを呼び出す必要はなく、4DのプロセスはCPUを共有するためにコオペレートする必要もありません。オペレーティングシステム (OS)によって行われるからです。オペレーティング・システムは、一つのスレッドあるいはその他に対してプリエンプティブにCPUを与えます。それで、もし複数のCPUを持つマシンを使用したら、いくつかのスレッドは同時に実行することができます。
結果として、プリエンプティブ・モードでは、アプリケーションの全体のパフォーマンスが向上し、特にマルチコア・マシンでは改善します。なぜなら複数のプロセス(スレッド)が実際に同時に起動できるからです。しかし、実際のパフォーマンスは、実行されるオペレーションに依存します。
その代わり、プリエンプティブ・モードでは各スレッドは他スレッドから独立しているので、アプリケーションによって直接管理されていないため、実行できる内容には一定の限度があります。これをスレッドセーフと呼びます。プリエンプティブに実行するには、プロセスはスレッドセーフ・コマンドのみを使用する必要があります。さらに、プリエンプティブ実行は、特定のコンテキスト(すなわち、新しいネットワーク層でコンパイルされた64ビット)でのみ使用できます。
プリエンプティブ・プロセスを使用するのに必要な環境
4Dでプリエンプティブにコードを使用するためには、いくつかの要件を満たす必要があります。以下のセクションでは、これらの要件の概要と、それらを満たす方法について説明します。
4Dの64ビット版 / 4D Serverが必須
プリエンプティブ・モードは、4Dの64ビット版でのみ可能です;4D 32ビット版はプリエンプティブ 実行モードには対応していません。さらに、4Dは64ビット版(v17 R5から)でしか利用できないため、この要件は自動的に満たされるはずです。
アプリケーションのコードはコンパイルされていなければならない
プリエンプティブ で実行するには、4Dはコンパイルされたコンテキストで実行され、以下の運用オプションがあります:
- マージされたダブルクリックで起動するアプリケーション
- コンパイルされた4DCストラクチャ・ファイル
- コンパイルされたコンテキストで起動するインタプリタ版の4DBストラクチャ
4Dがインタプリタ版のコンテキストで起動する時には、プリエンプティブ ・モードは使用できません。
コードの各パートがスレッドセーフでなければならない
プロセス内から呼ばれる全てのコマンドとメソッドを含め、全体のプロセスはスレッドセーフであることが必須です。CALL WORKERやNew processコマンドへ渡すメソッドから始めるには、メソッド・プロパティ内でExecution Mode設定を「プリエンプティブ ・プロセスで起動可」にしなければなりません。
そのメソッド内から呼ばれた各コマンドもスレッドセーフであることが必須です。もし他のメソッドを呼んだら、そのメソッドは内部の各コマンドも含めてスレッドセーフでなければなりません。
コマンドのスレッドセーフは、ドキュメントの中でもコンパイラーによってチェックすることができます。数千のスレッドセーフ・コマンドを含む4D v17ですが、いくつかのレガシー・コマンド(とUIで扱うコマンド)はスレッドセーフではありません。
ドキュメントからスレッドセーフをチェックする
ドキュメントからスレッドセーフをチェックするには、ドキュメントページの右手側に配置されているコマンドのプロパティinboxの中のプリエンプティブ ・モードのアイコンを探します。
上記のドキュメントのスクリーンショットでは、緑色のプリエンプティブ ・アイコンが表示されていて、このコマンドがスレッドセーフでプリエンプティブ ・メソッドで使用できることを示しています。
コンパイラーからスレッドセーフをチェックする
コンパイラーからスレッドセーフをチェックするには、シンタックスをコンパイルもしくはチェックして、スレッドセーフのエラーがあるかを結果で確認します。
上記のコンパイル・ウィンドウは、エラーを示しています。開発者にSTART SQL SERVERコマンドがスレッドセーフではないと知らせています。
このコマンドはスレッドセーフではないので、開発者はプロセスを再構築して、コマンドを直接呼び出さないようにしなければなりません。
新しいネットワークレイヤーを使う(4Dリモートに対して)
4D v17 R4からは、4D Serverやスタンドアロンに加えて、4Dリモートでもプリエンプティブ ・モードが可能になりました。4Dリモート環境からプリエンプティブ の恩恵を得るためには、新しいネットワークレイヤーをクライアント/サーバー接続で使わなければなりません。
新しいネットワークレイヤーは、新しいアプリケーションでは初期設定で可能ですが、アップグレードされたアプリケーションはまだレガシーのネットワークレイヤーが必要かもしれません。
もし「レガシー・ネットワークレイヤーを使用する」が有効の場合、4Dリモート(クライアント)はプリエンプティブ ・モードを使用できません。
4Dリモートのプリエンプティブ
V17 R4から、プリエンプティブ ・プロセスは4Dリモート上で作動できるようになりました。このセクションでは、さらに詳しく環境とアプリケーションの準備に必要なアイテムをカバーします。
プリエンプティブ に対する環境の準備
環境の準備はとても簡単です:
- v17 R4以降を使用(4Dリモートでプリエンプティブ 実行はv18の機能です)
- 4Dの64ビット版を使用
- 新しいネットワークレイヤーを使う
4Dリモートでのプリエンプティブ ・モードの準備の最初のステップは、適切なバージョンの4Dを使用することです。4Dリモートのプリエンプティブ 実行は、v18の機能で、最初にv17 R4で可能となりました。4Dリモートのプリエンプティブ 実行はv17.x(例: v17.0, v17.1, v17.2など)ではできません。4Dリモートでのプリエンプティブ 実行を使おうとする際に、4Dの適切なバージョンの確認が必要です。
環境の準備の最終ステップは、新しいネットワークレイヤーを使用することです。「データベースの設定」の「コンパティビリティ」オプションで現在の設定をチェックします:
もしも「レガシー・ネットワークを使う」が有効になっている場合、新しいネットワークレイヤーを使うために無効にしなければなりません。
プリエンプティブ に対するコードの準備
初期設定によって、4Dは全てのプロジェクト・メソッドをコオペラティブ・モーで実行します。もし、開発者がプリエンプティブ 機能の利点を使いたい場合は、最初のステップはメソッド・プロパティ内でプリエンプティブにしたい全てのメソッドを明示的に宣言することです。
これは、プリエンプティブで起動したい各メソッドに対して行う必要があります。
次に、コードのコンパイルが必要です。もしスレッドセーフではないコードを含むメソッドが含まれている場合、コードのコンパイルは以下のようなエラーで失敗します:
上記のスクリーンショットは、メソッド1(「プリエンプティブ・プロセスでは動かせません」に印を付けている)は、スレッドセーフとして宣言されていないメソッド(メソッド3)を呼ぶことを示しています。しかし、コンパイラーは、コードのどのパートが問題を起こしているのか示していません。メソッド全体(メソッド3)がスレッドセーフではないことを示しています。
このエラーを修正するために、エラー(メソッド3)中にリストされたメソッドのメソッド・プロパティをチェックします。メソッド3のメソッド・プロパティのチェックから、実行モード・プロパティが異なる設定になっていることに気づきます:
この設定を使って、コンパイラーはメソッドがスレッドセーフであるかを調査することができますが、どのコードが問題を起こしているかを調査するためにコンテンツをチェックすることはしません。
この状況を修正して少し深くコンパイラー・チェックするために、開発者はメソッド・プロパティ内でメソッドが「プリエンプティブ・モードで実行できる」を宣言します。
実行モードのメソッド・プロパティを変更し、コンパイラーを再起動し、なぜメソッドがスレッドセーフ・チェックの通貨に失敗したのか、その手がかりを得ることができます:
上記のスクリーンショットは、下部に表示されているエラーがメソッド内の SET TEXT TO PASTEBOARD コマンドの使用に起因するものであることを示しています。
この状況を修正するために、コマンドの機能をスレッドセーフ・コマンドを使って置き換えます。この特定の状況では、 SET TEXT TO PASTEBOARD コマンドの呼び出しを単純に削除することでこのメソッドをスレッドセーフにします。
問題を起こすコマンドをプリエンプティブ・コール・チェーンから削除した後は、コンパイル・チェックを再起動して、パスするかチェックします。
もしコンパイルが成功していたら、アプリケーションを再起動して、コンパイルモードでさらにテストを実行します。
問題のあるコマンドをメソッドから削除できない場合はどうしたら良いのか?
最後のサンプルはコマンドをシンプルに削除できたと仮定していますが、コードやロジックをメソッドから削除できなかったらどうしたら良いのか?
もし非スレッドセーフ・コードがメソッドから削除できなかったら、短い答えとしては「メソッドはプリエンプティブ・コンテキストで走らせることができない」ですが、開発者が決心すればそれをリファクターする方法はあります。
ソリューションは、コードが同期して動く必要があるかどうか、あるいは非同期で動くことができるかどうかによります。
プリエンプティブ・プロセスから非同期でコオペラティブ・タスクを呼ぶ
もしプリエンプティブ・プロセスがコオペラティブ・タスクを呼び出す必要があり、かつプリエンプティブ・プロセスがコオペラティブ・タスクの終了を待つ必要がない場合、New processコマンドによる非同期の呼び出しが可能です。このアプローチはとても効果的です。
もしプリエンプティブ・タスクがコオペラティブ・タスクの完了を待たなければならない場合は、開発者は同期手法でコオペラティブを呼び出したいと思うでしょう。この方法では、プリエンプティブ・タスクは一時停止して、コオペラティブ・タスクの完了を待ちます。このアプローチはこのドキュメントの次のセクションで説明します。
同期してプリエンプティブ・プロセスからコオペラティブ・タスクを呼び出す
もしプリエンプティブ・プロセスがコオペラティブ・タスクを呼ぶ必要がある場合、開発者は、プリエンプティブ・プロセスを一時停止する方法として新しいSignalコマンドを使うことができ、他のプロセスが完了するのを待つことができます。
Signalオブジェクトを得るためにNew signalコマンドを呼び出すことから始めます。Signalは共有オブジェクトの特別なタイプで、ワーカーからパラメータとして、あるいは他のワーカーやプロセスに対するプロセスとして渡すことができるので:
- 呼び出したワーカー/プロセスは、特定のプロセスが完了した後で、signalオブジェクトをアップデートできます。
- 呼び出されているワーカー/プロセスは、実行を止めて、signalがアップデートされるまで、CPUのリソースを消費することなく、待つことができます。
プリエンプティブとコオペラティブ・スレッドの間で同期したワークを行うのにこれは特に有効です。
Signalオブジェクトには、コオペラティブとプリエンプティブ・タスク間のより広い相互通信を可能にする以下のプロパティが付いています:
- signal.signaled - ブーリン、リードオンリー・プロパティ。最初はfalseでsignal.trigger()メソッドを呼んだ後でtrueになる。
- signal.description - テキスト、これにはsignal(オプション)のカスタム・ディスクリプションが含まれる
Signalオブジェクトはまた、相互通信を円滑にする以下のメソッドも持っています:
-
signal.wait() - これは待機するためのコーリング・プロセスによって使用されます。 - 無制限に待機にするために値無しで使用することができます。 - 待機するための秒数を特定するための値を使うことができます。
-
signal.trigger() - これは、signalをトリガーしてsignal.signaledをtrueに設定するために呼び出されたプロセスが使用します。
共有オブジェクトの性質により、signalオブジェクトもまたプリエンプティブとコオペラティブ・タスクの間で情報を送ったり戻したりするのに使うことができます。
プロセスがプリエンプティブかチェックする
4Dは、プロセスがプリエンプティブ・モードかコオペラティブ・モードのどちらで実行されているかチェックする複数の方法を提供します。含まれるのは:
-
PROCESS PROPERTIES コマンドは、プロセスがプリエンプティブあるいはコオペラティブ・モードのどちらで動作しているかを見つけることができます。
-
Runtimeエクスプローラと4D Server管理ウィンドウの両方がプリエンプティブ・プロセスの特定のアイコンを表示します:
サンプル
以下のメソッドは、プロセスがプリエンプティブあるいはコオペラティブ・コンテキストで実行されているかをチェックするのに使います:
上記のメソッドは、プロセス番号をパラメータとして取得し、特定のプロセスをプリエンプティブ・モードで実行したらtrueを返し、コオペラティブ・モードで実行したらfalseを返します。
終わりに
このテクニカル・ノートは、4Dからプリエンプティブ・プロセスを使う全般的なコンセプトを記載しました。メソッドから非スレッドセーフ・タスクをリファクタリングする二つのテクニックを提示しています。この情報で、4Dデベロッパーはプリエンプティブ・コードを書いてテストすることができ、どちらのリファクタリング・テクニックでも選ぶことができます。