Python
Flask
Thread

PythonでThreadを使うflaskサンプルを作ってみた

Flaskで時間のかかる処理をするとき、どうするのが良いか実行してみたので、自分用にメモ。

主な解法

ちょっと調べてみた感じだと、下記が主なやり方になりそうでした。

  • celery でジョブキューを使う
    • redisなど使うため準備が少し必要
    • Pythonではメジャーな実装っぽい
  • Threadを使う
    • Javaに近い感じ
    • celeryよりは実装が楽そう
  • 単純に実行が終わるまで待つ
    • 実装は単純(というか何もしない)
    • タイムアウトなど起こり得る
    • 待ち時間が長いとクライアント側の使い勝手がよくない

今回は、Threadを試してみました。

Threadについて

Javaで使ったことがあると親しみやすいかもしれません。
実行する方法が2つあります。

  1. Threadを使う
    1. threading.Thread(target=[job])でjobを渡してThreadオブジェクトを作る
    2. start()で処理を開始する
  2. Threadを継承したクラスを使う
    1. 継承したクラス内で runメソッドを実装すると、それが処理内容になる
    2. オブジェクトを作り、start()で処理を開始する

Javaと異なる点は、停止処理などがないことです。

現状では、優先度 (priority)やスレッドグループがなく、スレッドの破壊 (destroy)、中断 (stop)、一時停止 (suspend)、復帰 (resume)、割り込み (interrupt) は行えません。

ただ、停止させる方法がないわけではなく、フラグを外から更新してあげることで実現可能です。

今回は停止処理なども試したかったので、継承したクラスを使いました。

サンプル

app.py
from datetime import datetime
from flask import Flask, make_response
from time import sleep
import threading

app = Flask(__name__)

class MyThread(threading.Thread):
    def __init__(self):
        super(MyThread, self).__init__()
        self.stop_event = threading.Event()

    def stop(self):
        self.stop_event.set()

    def run(self):
        try:
            for _ in range(1000):
                print(f'{datetime.now():%H:%M:%S}')
                sleep(1)

                # 定期的にフラグを確認して停止させる
                if self.stop_event.is_set():
                    break
        finally:
            print('時間のかかる処理が終わりました\n')

jobs = {}

@app.route('/start/<id>/')
def root(id):
    t = MyThread()
    t.start()
    jobs[id] = t
    return make_response(f'{id}の処理を受け付けました\n'), 202

@app.route('/stop/<id>/')
def stop(id):
    jobs[id].stop()
    del jobs[id]
    return make_response(f'{id}の中止処理を受け付けました\n'), 202

@app.route('/status/<id>/')
def status(id):
    if id in jobs:
        return make_response(f'{id}は実行中です\n'), 200
    else:
        return make_response(f'{id}は実行していません\n'), 200

Flaskを起動します。

Flaskの起動
$ FLASK_APP=app.py flask run

下記のようにリクエストを送ってみると、クライアント側にはレスポンスがすぐ返り、かつ時間のかかる処理が継続していることが確認できました。

リクエストを送ってみる
$ curl localhost:5000/start/1/
1の処理を受け付けました
$ curl localhost:5000/status/1/
1は実行中です
$ curl localhost:5000/stop/1/
1の中止処理を受け付けました
Flask側の結果
$ FLASK_APP=app.py flask run
 * Serving Flask app "route"
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [18/Jan/2018 00:33:04] "GET /start/1/ HTTP/1.1" 202 -
00:33:05
00:33:06
127.0.0.1 - - [18/Jan/2018 00:33:07] "GET /status/1/ HTTP/1.1" 200 -
00:33:07
00:33:08
00:33:09
00:33:10
127.0.0.1 - - [18/Jan/2018 00:33:10] "GET /stop/1/ HTTP/1.1" 202 -
時間のかかる処理が終わりました

まとめ

celeryを使うほどじゃないけど、処理時間がかかる場合などはThreadを使うと良いかなと思いました。(celeryまだ使ったことない。汗)

他にもっと良い方法などありましたらコメントいただけますと幸いです。