PROJ.4におけるスレッドセーフティの実装
本記事はPROJ.4開発ドキュメントの翻訳であり、原文は http://proj4.org/development/threads.html を参照。
スレッドセーフティの課題
pj_errorというグローバル変数は複数のスレッド間で共有されるため、エラー処理を安全に行うことが困難です(これはC言語のerrnoとは異なります)。この問題を解決するために、実行コンテキストとしてprojCtxが導入されました。
また、原点移動に使用するグリッドファイルの情報は、グローバルに共有されるキャッシュリストに格納されます。この構造体へのアクセスを保護するため、PROJ 4.7.0以降ではmutex(排他制御)が導入され、関連データの読み書きがスレッドセーフになっています(pj_mutex.cを参照)。
投影コンテキスト projCtx
pj_errnoをグローバル変数として維持するリスクを回避するため、4.8.0バージョンからthread context(スレッドコンテキスト)が新しいAPIに組み込まれました。これにより、pj_init()やpj_init_plus()の代わりに、pj_init_ctx()およびpj_init_plus_ctx()が提供されるようになりました。これらの関数は、投影設定を保持するコンテキストオブジェクトを引数として受け取ります。
コンテキストはpj_ctx_alloc()関数で生成できます。アプリケーション側で明示的にコンテキストを指定しない場合、デフォルトのグローバルコンテキストが自動的に使用されます。以下の関数群により、コンテキストの作成・操作・確認・破棄が可能となっています。さらに、コンテキストはデバッグモードの設定やエラーメッセージの出力機能を保持する役割も果たします。
projPJ pj_init_ctx(projCtx, int, char **);
projPJ pj_init_plus_ctx(projCtx, const char *);
projCtx pj_get_default_ctx(void);
projCtx pj_get_ctx(projPJ);
void pj_set_ctx(projPJ, projCtx);
projCtx pj_ctx_alloc(void);
void pj_ctx_free(projCtx);
int pj_ctx_get_errno(projCtx);
void pj_ctx_set_errno(projCtx, int);
void pj_ctx_set_debug(projCtx, int);
void pj_ctx_set_logger(projCtx, void (*)(void *, int, const char *));
void pj_ctx_set_app_data(projCtx, void *);
void *pj_ctx_get_app_data(projCtx);
マルチスレッド環境では、各スレッドに対してpj_ctx_alloc()で個別のprojCtxを確保すべきです。必要に応じて、コンテキストに紐づくアプリケーションデータを変更できるように設計されていますが、各コンテキストには独自のエラー値が保持されており、pj_ctx_get_errno()経由でアクセスすることが推奨されます。一方、pj_errnoは依然として存在し、pj_ctx_set_errno()によって内部的に更新されるものの、グローバルな共有状態にあるため、マルチスレッドアプリケーションでは使用しないように注意が必要です。
重要: pj_init_ctx()やpj_init_plus_ctx()は、生成されたprojPJオブジェクトにprojCtxを関連付けます。pj_transform()、pj_fwd()、pj_inv()などの関数は、そのprojPJが関連付けられたコンテキストを使ってエラー情報を報告します。
テストプログラム multistresstest.c
PROJ.4のマルチスレッド動作を検証するためのサンプルテストプログラムがsrc/multistresstest.cに用意されています。このプログラムは、事前に定義された座標変換タスクを実行して基準結果を取得した後、複数のスレッドで同時に再実行し、出力結果の整合性を検証します。このテストは標準ビルドでは含まれませんが、Linux系環境では以下のようにコンパイル可能です。
gcc -g multistresstest.c .libs/libproj.so -lpthread -o multistresstest
./multistresstest