Python joblibの並列処理はuWSGI環境だと動かない。uWSGI上で並列処理するには？

# はじめに
Pythonで、機械学習やディープランニングをやっていると処理が重たくなって、性能面で問題になることがありますよね。

最近、私も重い処理があり性能改善のために並列処理を入れることにしました。
ローカルで`joblib` という並列処理用のライブラリを使い性能面で改善が見られれました。

しかし、開発環境上で同じ処理を実行したエラーに。。。
どうやら、uWSGI環境だとjoblibは動かないようです。
そのあたりの対処についてまとめました。

## uWSGIとは

- uWSGIとはPythonでWebサービスを動かすためのアプリケーションサーバです。

## 環境情報
- python: 3.6.9
- nginx: 1.13.1
- uWSGI: 2.0.18
- joblib: 0.14.0

## uWSGI上で失敗するjoblibのコード

- 並列化したい処理

```python
length = 1000
def sum(i, j):
  return i + j
```

- 並列化

```python
from joblib import Parallel, delayed

sum_list = Parallel(n_jobs=-1)( [delayed(calc_sum)(i, j) for j in range(length) for i in range(length)])
```

-　しかし、uWSGI上で動かすとこんな感じのエラーに

```bash
exception calling callback for <Future at 0x7fbc520c7eb8 state=finished raised TerminatedWorkerError>
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/joblib/externals/loky/_base.py", line 625, in _invoke_callbacks
    callback(self)
  File "/usr/local/lib/python3.7/site-packages/joblib/parallel.py", line 309, in __call__
    self.parallel.dispatch_next()
  File "/usr/local/lib/python3.7/site-packages/joblib/parallel.py", line 731, in dispatch_next
    if not self.dispatch_one_batch(self._original_iterator):
  File "/usr/local/lib/python3.7/site-packages/joblib/parallel.py", line 759, in dispatch_one_batch
    self._dispatch(tasks)
  File "/usr/local/lib/python3.7/site-packages/joblib/parallel.py", line 716, in _dispatch
    job = self._backend.apply_async(batch, callback=cb)
  File "/usr/local/lib/python3.7/site-packages/joblib/_parallel_backends.py", line 510, in apply_async
    future = self._workers.submit(SafeFunction(func))
  File "/usr/local/lib/python3.7/site-packages/joblib/externals/loky/reusable_executor.py", line 151, in submit
    fn, *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/joblib/externals/loky/process_executor.py", line 1022, in submit
    raise self._flags.broken
joblib.externals.loky.process_executor.TerminatedWorkerError: A worker process managed by the executor was unexpectedly terminated. This could be caused by a segmentation fault while calling the function or by an excessive memory usage causing the Operating System to kill the worker. The exit codes of the workers are {EXIT(1), EXIT(1), EXIT(1), EXIT(1)}
```

## 対処法1： threadsなら動く

```python
from joblib import Parallel, delayed

sum_list = Parallel(n_jobs=-1, prefer='threads')( [delayed(calc_sum)(i, j) for j in range(length) for i in range(length)])
```
- もちろん、処理によってはthreadsにしてしまうと遅くなってしまうこともある。

## 対象法2: multiprocessingにする

```python
import multiprocessing
from multiprocessing import Process

with multiprocessing.Pool() as pool:
    process = [pool.apply_async(calc_sum, (i, j)) for j in range(length) for i in range(length)]
    sum_list = [f.get() for f in process]
```

## 対処法3: uWSGIではなく、guicornにする
私は、今回この方法をとりませんでした。
理由は、すでにuWSGIで稼働していたためAPサーバを変更するのはリスクがでかいためです。

## 最後に
### 速度比較

| 通常 | joblib(multiprocess) | joblib(threads) | multiprocessing |
| ---- | ---- | ---- | ---- |
| 32.9 µs | 11 µs | 40.1 µs | 4.05 µs |

joblibの方が可読性はよく、書きやすいのですがmultiprocessingの方がPython本体に組み込まれている機能のため、性能が出ることも多いです。
また、今回みたいなエラーも踏むこともなさそうです。