Embedding Julia
Calling C and Fortran Codeで見たように、JuliaはCで書かれた関数を呼び出すためのシンプルで効率的な方法を提供しています。しかし、逆にCコードからJulia関数を呼び出す必要がある状況もあります。これは、すべてをC/C++で書き直すことなく、Juliaコードをより大きなC/C++プロジェクトに統合するために使用できます。Juliaにはこれを可能にするC APIがあります。ほとんどすべてのプログラミング言語にはC関数を呼び出す方法があるため、JuliaのC APIはさらに言語ブリッジを構築するためにも使用できます(例:Python、Rust、C#からJuliaを呼び出す)。RustとC++はC埋め込みAPIを直接使用できますが、両方ともそれを助けるパッケージがあります。C++の場合、Jlunaが便利です。
High-Level Embedding
Note: This section covers embedding Julia code in C on Unix-like operating systems. For doing this on Windows, please see the section following this, High-Level Embedding on Windows with Visual Studio.
シンプルなCプログラムから始めて、Juliaを初期化し、いくつかのJuliaコードを呼び出します:
#include <julia.h>
JULIA_DEFINE_FAST_TLS // only define this once, in an executable (not in a shared library) if you want fast code.
int main(int argc, char *argv[])
{
/* required: setup the Julia context */
jl_init();
/* run Julia commands */
jl_eval_string("print(sqrt(2.0))");
/* strongly recommended: notify Julia that the
program is about to terminate. this allows
Julia time to cleanup pending write requests
and run all finalizers
*/
jl_atexit_hook(0);
return 0;
}このプログラムをビルドするには、Juliaヘッダーへのパスをインクルードパスに追加し、libjuliaにリンクする必要があります。たとえば、Juliaが$JULIA_DIRにインストールされている場合、上記のテストプログラムtest.cをgccを使用してコンパイルすることができます:
gcc -o test -fPIC -I$JULIA_DIR/include/julia -L$JULIA_DIR/lib -Wl,-rpath,$JULIA_DIR/lib test.c -ljulia代わりに、Juliaソースツリーのtest/embedding/フォルダーにあるembedding.cプログラムを見てください。ファイルcli/loader_exe.cプログラムは、libjuliaにリンクしながらjl_optionsオプションを設定する方法の別の簡単な例です。
JuliaのC関数を呼び出す前に最初に行うべきことは、Juliaを初期化することです。これは、jl_initを呼び出すことで行われ、Juliaのインストール場所を自動的に特定しようとします。カスタムの場所を指定する必要がある場合や、読み込むシステムイメージを指定する必要がある場合は、代わりにjl_init_with_image_fileまたはjl_init_with_image_handleを使用してください。
テストプログラムの2番目のステートメントは、jl_eval_stringを呼び出してJuliaステートメントを評価します。
プログラムが終了する前に、jl_atexit_hookを呼び出すことを強く推奨します。上記の例のプログラムは、mainから戻る直前にこれを呼び出します。
現在、libjulia 共有ライブラリと動的にリンクするには、RTLD_GLOBAL オプションを渡す必要があります。Python では、これは次のようになります:
>>> julia=CDLL('./libjulia.dylib',RTLD_GLOBAL)
>>> julia.jl_init.argtypes = []
>>> julia.jl_init()
250593296もしJuliaプログラムがメイン実行ファイルからシンボルにアクセスする必要がある場合、Linuxでコンパイル時にjulia-config.jlによって生成されたものに加えて、-Wl,--export-dynamicリンカーフラグを追加する必要があるかもしれません。共有ライブラリをコンパイルする際には、これは必要ありません。
Using julia-config to automatically determine build parameters
スクリプト julia-config.jl は、埋め込みJuliaを使用するプログラムに必要なビルドパラメータを特定するのを助けるために作成されました。このスクリプトは、呼び出された特定のJuliaディストリビューションのビルドパラメータとシステム構成を使用して、そのディストリビューションと対話するために埋め込みプログラムが必要とするコンパイラフラグをエクスポートします。このスクリプトは、Juliaの共有データディレクトリにあります。
Example
#include <julia.h>
int main(int argc, char *argv[])
{
jl_init();
(void)jl_eval_string("println(sqrt(2.0))");
jl_atexit_hook(0);
return 0;
}On the command line
このスクリプトの簡単な使用法は、コマンドラインからです。julia-config.jl が /usr/local/julia/share/julia にあると仮定すると、コマンドラインから直接呼び出すことができ、3つのフラグの任意の組み合わせを受け取ります:
/usr/local/julia/share/julia/julia-config.jl
Usage: julia-config [--cflags|--ldflags|--ldlibs]上記の例のソースがファイル embed_example.c に保存されている場合、次のコマンドはLinuxおよびWindows(MSYS2環境)で実行可能なプログラムにコンパイルします。macOSでは、gcc の代わりに clang を使用してください。
/usr/local/julia/share/julia/julia-config.jl --cflags --ldflags --ldlibs | xargs gcc embed_example.cUse in Makefiles
一般的に、埋め込みプロジェクトは上記の例よりも複雑になるため、以下では一般的なmakefileサポートも提供します。これは、shellマクロ展開の使用によりGNU makeを前提としています。さらに、julia-config.jlは通常/usr/localディレクトリにありますが、もしそうでない場合は、Julia自体を使用してjulia-config.jlを見つけることができ、makefileはこれを利用できます。上記の例はmakefileを使用するように拡張されています:
JL_SHARE = $(shell julia -e 'print(joinpath(Sys.BINDIR, Base.DATAROOTDIR, "julia"))')
CFLAGS += $(shell $(JL_SHARE)/julia-config.jl --cflags)
CXXFLAGS += $(shell $(JL_SHARE)/julia-config.jl --cflags)
LDFLAGS += $(shell $(JL_SHARE)/julia-config.jl --ldflags)
LDLIBS += $(shell $(JL_SHARE)/julia-config.jl --ldlibs)
all: embed_example現在、ビルドコマンドは単に make です。
High-Level Embedding on Windows with Visual Studio
JULIA_DIR 環境変数が設定されていない場合は、Visual Studio を起動する前にシステムパネルを使用して追加してください。JULIA_DIR の下の bin フォルダーは、システムの PATH に含まれている必要があります。
Visual Studioを開き、新しいコンソールアプリケーションプロジェクトを作成することから始めます。 'stdafx.h' ヘッダーファイルを開き、最後に以下の行を追加します:
#include <julia.h>次に、プロジェクト内の main() 関数をこのコードに置き換えてください:
int main(int argc, char *argv[])
{
/* required: setup the Julia context */
jl_init();
/* run Julia commands */
jl_eval_string("print(sqrt(2.0))");
/* strongly recommended: notify Julia that the
program is about to terminate. this allows
Julia time to cleanup pending write requests
and run all finalizers
*/
jl_atexit_hook(0);
return 0;
}次のステップは、Juliaのインクルードファイルとライブラリを見つけるためにプロジェクトを設定することです。Juliaのインストールが32ビットか64ビットかを知ることが重要です。進む前に、Juliaのインストールに対応しないプラットフォーム設定を削除してください。
プロジェクトのプロパティダイアログを使用して、C/C++ | Generalに移動し、Additional Include Directoriesプロパティに$(JULIA_DIR)\include\julia\を追加します。次に、Linker | Generalセクションに移動し、Additional Library Directoriesプロパティに$(JULIA_DIR)\libを追加します。最後に、Linker | Inputの下で、ライブラリのリストにlibjulia.dll.a;libopenlibm.dll.a;を追加します。
この時点で、プロジェクトはビルドされ、実行されるべきです。
Converting Types
実際のアプリケーションは、式を実行するだけでなく、その値をホストプログラムに返す必要があります。 jl_eval_string は jl_value_t* を返し、これはヒープに割り当てられたJuliaオブジェクトへのポインタです。 Float64 のような単純なデータ型をこの方法で格納することを boxing と呼び、格納されたプリミティブデータを抽出することを unboxing と呼びます。 2の平方根を計算し、Cで結果を読み戻す改善されたサンプルプログラムの本体には、現在このコードが含まれています:
jl_value_t *ret = jl_eval_string("sqrt(2.0)");
if (jl_typeis(ret, jl_float64_type)) {
double ret_unboxed = jl_unbox_float64(ret);
printf("sqrt(2.0) in C: %e \n", ret_unboxed);
}
else {
printf("ERROR: unexpected return type from sqrt(::Float64)\n");
}retが特定のJulia型であるかどうかを確認するために、jl_isa、jl_typeis、またはjl_is_...関数を使用できます。Juliaシェルにtypeof(sqrt(2.0))と入力すると、返り値の型がFloat64(Cのdouble)であることがわかります。上記のコードスニペットでは、ボックス化されたJulia値をCのdoubleに変換するためにjl_unbox_float64関数が使用されます。
対応する jl_box_... 関数は、逆方向に変換するために使用されます:
jl_value_t *a = jl_box_float64(3.0);
jl_value_t *b = jl_box_float32(3.0f);
jl_value_t *c = jl_box_int32(3);次に見るように、特定の引数を持つJulia関数を呼び出すにはボクシングが必要です。
Calling Julia Functions
jl_eval_stringはCがJulia式の結果を取得することを可能にしますが、Cで計算された引数をJuliaに渡すことはできません。これを行うには、jl_callを使用してJulia関数を直接呼び出す必要があります。
jl_function_t *func = jl_get_function(jl_base_module, "sqrt");
jl_value_t *argument = jl_box_float64(2.0);
jl_value_t *ret = jl_call1(func, argument);最初のステップでは、jl_get_functionを呼び出すことでJulia関数sqrtへのハンドルが取得されます。jl_get_functionに渡される最初の引数は、sqrtが定義されているBaseモジュールへのポインタです。次に、ダブル値はjl_box_float64を使用してボックス化されます。最後のステップでは、jl_call1を使用して関数が呼び出されます。異なる数の引数を便利に処理するために、jl_call0、jl_call2、およびjl_call3関数も存在します。より多くの引数を渡すには、jl_callを使用します:
jl_value_t *jl_call(jl_function_t *f, jl_value_t **args, int32_t nargs)その第二引数 args は jl_value_t* 引数の配列であり、nargs は引数の数です。
別の、より簡単な方法でJulia関数を呼び出すこともでき、それは @cfunction を介して行います。 @cfunction を使用すると、Julia側で型変換を行うことができ、通常はC側で行うよりも簡単です。上記の sqrt の例は、@cfunction を使用して次のように書かれます:
double (*sqrt_jl)(double) = jl_unbox_voidpointer(jl_eval_string("@cfunction(sqrt, Float64, (Float64,))"));
double ret = sqrt_jl(2.0);最初にJuliaでC呼び出し可能な関数を定義し、そこから関数ポインタを抽出し、最終的にそれを呼び出します。高水準言語で型変換を行うことで簡素化することに加えて、@cfunctionポインタを介してJulia関数を呼び出すことで、すべての引数が「ボックス化」されるjl_callによって必要とされる動的ディスパッチのオーバーヘッドが排除され、ネイティブC関数ポインタと同等のパフォーマンスを持つはずです。
Memory Management
私たちが見たように、JuliaオブジェクトはCではjl_value_t*型のポインタとして表現されます。これにより、これらのオブジェクトを解放する責任が誰にあるのかという疑問が生じます。
通常、Juliaオブジェクトはガーベジコレクタ(GC)によって解放されますが、GCはCからJulia値への参照を保持していることを自動的に認識しません。これは、GCがオブジェクトをあなたの下から解放する可能性があり、ポインタが無効になることを意味します。
GCは新しいJuliaオブジェクトが割り当てられるときにのみ実行されます。jl_box_float64のような呼び出しは割り当てを行いますが、割り当てはJuliaコードの実行中の任意のポイントでも発生する可能性があります。
Juliaを埋め込むコードを書く際には、一般的にjl_value_t*値をjl_...呼び出しの間で使用することは安全です(GCはこれらの呼び出しによってのみトリガーされるため)。しかし、jl_...呼び出しを通過しても値が生き残ることを確実にするためには、Juliaに対してまだJulia root 値への参照を保持していることを伝える必要があります。このプロセスは「GCルーティング」と呼ばれます。値をルート化することで、ガベージコレクタがこの値を未使用として誤って識別し、その値を支えるメモリを解放しないようにすることができます。これはJL_GC_PUSHマクロを使用して行うことができます:
jl_value_t *ret = jl_eval_string("sqrt(2.0)");
JL_GC_PUSH1(&ret);
// Do something with ret
JL_GC_POP();JL_GC_POP 呼び出しは、前の JL_GC_PUSH によって確立された参照を解放します。 JL_GC_PUSH は C スタックに参照を格納するため、スコープを退出する前に必ず JL_GC_POP と正確にペアにする必要があります。つまり、関数が戻る前、または制御フローが JL_GC_PUSH が呼び出されたブロックを離れる前に行う必要があります。
複数のJulia値を一度にプッシュするには、JL_GC_PUSH2からJL_GC_PUSH6マクロを使用します:
JL_GC_PUSH2(&ret1, &ret2);
// ...
JL_GC_PUSH6(&ret1, &ret2, &ret3, &ret4, &ret5, &ret6);配列のJulia値をプッシュするには、次のように使用できるJL_GC_PUSHARGSマクロを使用できます。
jl_value_t **args;
JL_GC_PUSHARGS(args, 2); // args can now hold 2 `jl_value_t*` objects
args[0] = some_value;
args[1] = some_other_value;
// Do something with args (e.g. call jl_... functions)
JL_GC_POP();各スコープには JL_GC_PUSH* への呼び出しが1回だけ必要であり、単一の JL_GC_POP 呼び出しとペアである必要があります。ルート化したいすべての必要な変数を1回の JL_GC_PUSH* 呼び出しでプッシュできない場合、またはプッシュする変数が6つを超え、引数の配列を使用することができない場合は、内部ブロックを使用することができます:
jl_value_t *ret1 = jl_eval_string("sqrt(2.0)");
JL_GC_PUSH1(&ret1);
jl_value_t *ret2 = 0;
{
jl_function_t *func = jl_get_function(jl_base_module, "exp");
ret2 = jl_call1(func, ret1);
JL_GC_PUSH1(&ret2);
// Do something with ret2.
JL_GC_POP(); // This pops ret2.
}
JL_GC_POP(); // This pops ret1.jl_value_t* の有効な値を JL_GC_PUSH* を呼び出す前に持っている必要はないことに注意してください。いくつかを NULL に初期化しておき、それらを JL_GC_PUSH* に渡してから実際の Julia 値を作成することができます。例えば:
jl_value_t *ret1 = NULL, *ret2 = NULL;
JL_GC_PUSH2(&ret1, &ret2);
ret1 = jl_eval_string("sqrt(2.0)");
ret2 = jl_eval_string("sqrt(3.0)");
// Use ret1 and ret2
JL_GC_POP();変数へのポインタを関数(またはブロックスコープ)間で保持する必要がある場合、JL_GC_PUSH*を使用することはできません。この場合、Juliaのグローバルスコープで変数への参照を作成し保持する必要があります。これを達成する簡単な方法は、参照を保持するグローバルなIdDictを使用することで、GCによる解放を回避します。ただし、この方法は可変型に対してのみ正しく機能します。
// This functions shall be executed only once, during the initialization.
jl_value_t* refs = jl_eval_string("refs = IdDict()");
jl_function_t* setindex = jl_get_function(jl_base_module, "setindex!");
...
// `var` is the variable we want to protect between function calls.
jl_value_t* var = 0;
...
// `var` is a `Vector{Float64}`, which is mutable.
var = jl_eval_string("[sqrt(2.0); sqrt(4.0); sqrt(6.0)]");
// To protect `var`, add its reference to `refs`.
jl_call3(setindex, refs, var, var);変数が不変である場合、それは同等の可変コンテナ、または好ましくは RefValue{Any} にラップされてから IdDict にプッシュする必要があります。このアプローチでは、コンテナは C コードを使用して作成または填充する必要があり、例えば、関数 jl_new_struct を使用します。コンテナが jl_call* によって作成された場合、C コードで使用するためにポインタを再読み込みする必要があります。
// This functions shall be executed only once, during the initialization.
jl_value_t* refs = jl_eval_string("refs = IdDict()");
jl_function_t* setindex = jl_get_function(jl_base_module, "setindex!");
jl_datatype_t* reft = (jl_datatype_t*)jl_eval_string("Base.RefValue{Any}");
...
// `var` is the variable we want to protect between function calls.
jl_value_t* var = 0;
...
// `var` is a `Float64`, which is immutable.
var = jl_eval_string("sqrt(2.0)");
// Protect `var` until we add its reference to `refs`.
JL_GC_PUSH1(&var);
// Wrap `var` in `RefValue{Any}` and push to `refs` to protect it.
jl_value_t* rvar = jl_new_struct(reft, var);
JL_GC_POP();
jl_call3(setindex, refs, rvar, rvar);GCは、他の場所に変数への参照が保持されていない場合に、refsからそれへの参照を削除することで、delete!関数を使用して変数を解放することができます。
jl_function_t* delete = jl_get_function(jl_base_module, "delete!");
jl_call2(delete, refs, rvar);非常に単純なケースの代替として、Vector{Any} 型のグローバルコンテナを作成し、必要に応じてそこから要素を取得することが可能です。また、ポインタごとに1つのグローバル変数を作成することもできます。
jl_module_t *mod = jl_main_module;
jl_sym_t *var = jl_symbol("var");
jl_binding_t *bp = jl_get_binding_wr(mod, var, 1);
jl_checked_assignment(bp, mod, var, val);Updating fields of GC-managed objects
ガーベジコレクタは、すべての古い世代のオブジェクトが若い世代のオブジェクトを指していることを認識しているという前提の下で動作します。この前提を破るポインタが更新されるたびに、jl_gc_wb(書き込みバリア)関数を使用してコレクタに通知する必要があります。
jl_value_t *parent = some_old_value, *child = some_young_value;
((some_specific_type*)parent)->field = child;
jl_gc_wb(parent, child);一般的に、どの値が実行時に古くなるかを予測することは不可能なので、書き込みバリアはすべての明示的なストアの後に挿入する必要があります。注目すべき例外は、parentオブジェクトがちょうど割り当てられたばかりで、それ以降にガーベジコレクションが実行されていない場合です。ほとんどのjl_...関数は、時々ガーベジコレクションを呼び出すことがあることに注意してください。
書き込みバリアは、データを直接更新する際にポインタの配列にも必要です。通常、jl_array_ptr_setを呼び出す方がはるかに好まれます。しかし、直接更新も可能です。例えば:
jl_array_t *some_array = ...; // e.g. a Vector{Any}
void **data = jl_array_data(some_array, void*);
jl_value_t *some_value = ...;
data[0] = some_value;
jl_gc_wb(jl_array_owner(some_array), some_value);Controlling the Garbage Collector
GCを制御するためのいくつかの関数があります。通常の使用ケースでは、これらは必要ないはずです。
| Function | Description |
|---|---|
jl_gc_collect() | Force a GC run |
jl_gc_enable(0) | Disable the GC, return previous state as int |
jl_gc_enable(1) | Enable the GC, return previous state as int |
jl_gc_is_enabled() | Return current state as int |
Working with Arrays
JuliaとCは、配列データをコピーせずに共有できます。次の例では、これがどのように機能するかを示します。
Juliaの配列はCではデータ型jl_array_t*によって表されます。基本的に、jl_array_tは次の内容を含む構造体です:
- データ型に関する情報
- データブロックへのポインタ
- 配列のサイズに関する情報
物事をシンプルに保つために、1D配列から始めます。長さ10のFloat64要素を含む配列を作成するには、次のようにします:
jl_value_t* array_type = jl_apply_array_type((jl_value_t*)jl_float64_type, 1);
jl_array_t* x = jl_alloc_array_1d(array_type, 10);代わりに、すでに配列を割り当てている場合は、そのデータの周りに薄いラッパーを生成できます:
double *existingArray = (double*)malloc(sizeof(double)*10);
jl_array_t *x = jl_ptr_to_array_1d(array_type, existingArray, 10, 0);最後の引数は、Juliaがデータの所有権を持つべきかどうかを示すブール値です。この引数がゼロ以外の場合、GCは配列がもはや参照されていないときにデータポインタに対してfreeを呼び出します。
xのデータにアクセスするには、jl_array_dataを使用できます:
double *xData = jl_array_data(x, double);今、配列を埋めることができます:
for (size_t i = 0; i < jl_array_nrows(x); i++)
xData[i] = i;今、xに対してインプレース操作を行うJulia関数を呼び出しましょう:
jl_function_t *func = jl_get_function(jl_base_module, "reverse!");
jl_call1(func, (jl_value_t*)x);配列を印刷することで、xの要素が逆になったことを確認できます。
Accessing Returned Arrays
もしJulia関数が配列を返す場合、jl_eval_stringおよびjl_callの戻り値はjl_array_t*にキャストできます:
jl_function_t *func = jl_get_function(jl_base_module, "reverse");
jl_array_t *y = (jl_array_t*)jl_call1(func, (jl_value_t*)x);今、yの内容には以前と同様にjl_array_dataを使用してアクセスできます。常に、配列が使用中である間は参照を保持することを忘れないでください。
Multidimensional Arrays
Juliaの多次元配列は、メモリ内で列優先順序で保存されます。以下は、2D配列を作成し、そのプロパティにアクセスするコードです:
// Create 2D array of float64 type
jl_value_t *array_type = jl_apply_array_type((jl_value_t*)jl_float64_type, 2);
int dims[] = {10,5};
jl_array_t *x = jl_alloc_array_nd(array_type, dims, 2);
// Get array pointer
double *p = jl_array_data(x, double);
// Get number of dimensions
int ndims = jl_array_ndims(x);
// Get the size of the i-th dim
size_t size0 = jl_array_dim(x,0);
size_t size1 = jl_array_dim(x,1);
// Fill array with data
for(size_t i=0; i<size1; i++)
for(size_t j=0; j<size0; j++)
p[j + size0*i] = i + j;注意してください。Juliaの配列は1ベースのインデックスを使用しますが、C APIは0ベースのインデックスを使用します(例えば、jl_array_dimを呼び出す際に)。これは、慣用的なCコードとして読みやすくするためです。
Exceptions
Juliaのコードは例外をスローすることがあります。例えば、次のようなものを考えてみましょう:
jl_eval_string("this_function_does_not_exist()");この呼び出しは何もしていないように見えます。しかし、例外がスローされたかどうかを確認することは可能です:
if (jl_exception_occurred())
printf("%s \n", jl_typeof_str(jl_exception_occurred()));もしあなたが例外をサポートする言語(例:Python、C#、C++)からJulia C APIを使用している場合、libjuliaへの各呼び出しを、例外がスローされたかどうかをチェックする関数でラップし、ホスト言語で例外を再スローすることが理にかなっています。
Throwing Julia Exceptions
Juliaの呼び出し可能な関数を書く際には、引数を検証し、エラーを示すために例外をスローする必要がある場合があります。典型的な型チェックは次のようになります:
if (!jl_typeis(val, jl_float64_type)) {
jl_type_error(function_name, (jl_value_t*)jl_float64_type, val);
}一般的な例外は、次の関数を使用して発生させることができます:
void jl_error(const char *str);
void jl_errorf(const char *fmt, ...);jl_errorはC文字列を受け取り、jl_errorfはprintfのように呼び出されます:
jl_errorf("argument x = %d is too large", x);この例では x は整数であると仮定されています。
Thread-safety
一般的に、Julia C APIは完全にスレッドセーフではありません。マルチスレッドアプリケーションにJuliaを埋め込む際には、以下の制限を違反しないように注意する必要があります:
jl_init()はアプリケーションのライフタイム中に一度だけ呼び出すことができます。同様にjl_atexit_hook()も、jl_init()の後にのみ呼び出すことができます。jl_...()API 関数は、jl_init()が呼び出されたスレッドからのみ呼び出すことができ、または Julia ランタイムによって開始されたスレッドから呼び出すことができます。ユーザーが開始したスレッドから Julia API 関数を呼び出すことはサポートされておらず、未定義の動作やクラッシュを引き起こす可能性があります。
上記の第二の条件は、Juliaによって開始されていないスレッドからjl_...()関数を安全に呼び出すことができないことを意味します(jl_init()を呼び出すスレッドは例外です)。例えば、以下のようなことはサポートされておらず、最も可能性が高いのはセグメンテーションフォルトです:
void *func(void*)
{
// Wrong, jl_eval_string() called from thread that was not started by Julia
jl_eval_string("println(Threads.threadid())");
return NULL;
}
int main()
{
pthread_t t;
jl_init();
// Start a new thread
pthread_create(&t, NULL, func, NULL);
pthread_join(t, NULL);
jl_atexit_hook(0);
}代わりに、同じユーザー作成スレッドからすべてのJulia呼び出しを実行することができます:
void *func(void*)
{
// Okay, all jl_...() calls from the same thread,
// even though it is not the main application thread
jl_init();
jl_eval_string("println(Threads.threadid())");
jl_atexit_hook(0);
return NULL;
}
int main()
{
pthread_t t;
// Create a new thread, which runs func()
pthread_create(&t, NULL, func, NULL);
pthread_join(t, NULL);
}Julia自身が開始したスレッドからJulia C APIを呼び出す例:
#include <julia/julia.h>
JULIA_DEFINE_FAST_TLS
double c_func(int i)
{
printf("[C %08x] i = %d\n", pthread_self(), i);
// Call the Julia sqrt() function to compute the square root of i, and return it
jl_function_t *sqrt = jl_get_function(jl_base_module, "sqrt");
jl_value_t* arg = jl_box_int32(i);
double ret = jl_unbox_float64(jl_call1(sqrt, arg));
return ret;
}
int main()
{
jl_init();
// Define a Julia function func() that calls our c_func() defined in C above
jl_eval_string("func(i) = ccall(:c_func, Float64, (Int32,), i)");
// Call func() multiple times, using multiple threads to do so
jl_eval_string("println(Threads.threadpoolsize())");
jl_eval_string("use(i) = println(\"[J $(Threads.threadid())] i = $(i) -> $(func(i))\")");
jl_eval_string("Threads.@threads for i in 1:5 use(i) end");
jl_atexit_hook(0);
}このコードを2つのJuliaスレッドで実行すると、次の出力が得られます(注:出力は実行やシステムによって異なります):
$ JULIA_NUM_THREADS=2 ./thread_example
2
[C 3bfd9c00] i = 1
[C 23938640] i = 4
[J 1] i = 1 -> 1.0
[C 3bfd9c00] i = 2
[J 1] i = 2 -> 1.4142135623730951
[C 3bfd9c00] i = 3
[J 2] i = 4 -> 2.0
[C 23938640] i = 5
[J 1] i = 3 -> 1.7320508075688772
[J 2] i = 5 -> 2.23606797749979ご覧のとおり、Juliaスレッド1はpthread ID 3bfd9c00に対応し、Juliaスレッド2はID 23938640に対応しています。これは、Cレベルで複数のスレッドが使用されていることを示しており、これらのスレッドからJulia C APIルーチンを安全に呼び出すことができることを示しています。