[Julia] マルチプロセスのはまりどころ

# はじめに

Juliaは、標準ライブラリで手軽にマルチプロセスを実現することができますが、かなり初歩的な部分でハマってしまったのでメモしておきます。以下の二つのエラーに延々ハマっていました。

```julia
ERROR: LoadError: On worker 2:
ArgumentError: Package Flux [587475ba-b771-5e3f-ad9e-33799f191a9c] is required but does not seem to be installed:
 - Run `Pkg.instantiate()` to install all recorded dependencies.
```

```julia
ERROR: LoadError: On worker 2:
UndefVarError: #func not defined
```


# 結局

- `@everywhere` は `addprocs` の後に書く
- 仮想環境の場合は、 `addprocs(n, exeflags="--project")` とする ([公式ドキュメント](https://docs.julialang.org/en/v1/manual/distributed-computing/#Starting-and-managing-worker-processes))


# 正しく動く書き方

```julia
using Distributed
using Printf

addprocs(2, exeflags="--project")  # <- project を明示

# @everywhere マクロは addproc の後に書く
@everywhere using Flux

@everywhere function veryHeavyFunc()
    sleep(1)
    return Flux.mae([1.1,1.9,3.1], 1:3)
end

@everywhere function veryVeryHeavyFunc()
    sleep(2)
    return Flux.mse([1.1,1.9,3.1], 1:3)
end

function main()
    s1 = @spawnat 2 veryHeavyFunc()
    s2 = @spawnat 3 veryVeryHeavyFunc()

    r1 = fetch(s1)
    r2 = fetch(s2)

    @printf("r1 = %.5f\n", r1)
    @printf("r2 = %.5f\n", r2)
end

main()
```

# 誤った書き方

下のコードは2箇所変更する必要があるので、順に解説します。

```julia
using Distributed
using Printf
@everywhere using Flux

@everywhere function veryHeavyFunc()
    sleep(1)
    return Flux.mae([1.1,1.9,3.1], 1:3)
end

@everywhere function veryVeryHeavyFunc()
    sleep(20)
    return Flux.mse([1.1,1.9,3.1], 1:3)
end

function main()
    addprocs(2)

    s1 = @spawnat 2 veryHeavyFunc()
    s2 = @spawnat 3 veryVeryHeavyFunc()

    r1 = fetch(s1)
    r2 = fetch(s2)

    @printf("r1 = %.5f\n", r1)
    @printf("r2 = %.5f\n", r2)
end

main()
```

## 誤りその1

```julia:main_wrong.jl
using Distributed
using Printf
@everywhere using Flux

@everywhere function veryHeavyFunc()
    sleep(1)
...
end

function main()
    addprocs(2)  # <- wrong!
    
    s1 = @spawnat 2 veryHeavyFunc()
    ....
end
```

何も考えずに書いたら、新たなプロセスが必要となる箇所付近でプロセスを追加すると思うんですけどねー。これがまずダメでした。 `addprocs` をした後に `@everywhere` を付けた関数の定義および `using Pacakge` を書きましょう。

```julia:main_correct.jl
using Distributed
using Printf

addprocs(2)  # <- correct!

@everywhere using Flux

@everywhere function veryHeavyFunc()
    sleep(1)
...
end

function main()
    s1 = @spawnat 2 veryHeavyFunc()
    ....
end
```


## 誤りその2

これは公式ドキュメントに書いているので比較的早く気づくことができましたが、

```bash
julia --project=/path/to/env main.jl
```

のように仮想環境を指定して実行している場合、インストールしているパッケージを作成するプロセスでも使用したいのであれば、

```julia
addprocs(2, exeflags="--project")
```

のように第二引数を忘れずに記載しましょう。

## ~~別解~~

コマンドラインで `-p 2` などとすれば、コード中に `addprocs(2)` と書いた時と同じ効果が得られます。
つまり、 **`addprocs(2, exeflags="--project")` とはならない**ので、残念ながらエラーになります。

```bash
julia --project=/path/to/env -p 2 main.jl

ERROR: LoadError: On worker 2:
ArgumentError: Package Flux [587475ba-b771-5e3f-ad9e-33799f191a9c] is required but does not seem to be installed:
 - Run `Pkg.instantiate()` to install all recorded dependencies.
Stacktrace:
[...]
```

## 正しく動く書き方(再掲)

```julia
using Distributed
using Printf

addprocs(2, exeflags="--project")

@everywhere using Flux

@everywhere function veryHeavyFunc()
    sleep(10)
    return Flux.mae([1.1,1.9,3.1], 1:3)
end

@everywhere function veryVeryHeavyFunc()
    sleep(20)
    return Flux.mse([1.1,1.9,3.1], 1:3)
end

function main()
    s1 = @spawnat 2 veryHeavyFunc()
    s2 = @spawnat 3 veryVeryHeavyFunc()

    r1 = fetch(s1)
    r2 = fetch(s2)

    @printf("r1 = %.5f\n", r1)
    @printf("r2 = %.5f\n", r2)
end

main()
```


# まとめ

juliaの更なる発展に期待！