플라스크에서 전역 변수는 스레드로부터 안전합니까? 요청간에 데이터를 공유하려면 어떻게합니까?


101

내 앱에서 공통 객체의 상태는 요청을 통해 변경되며 응답은 상태에 따라 다릅니다.

class SomeObj():
    def __init__(self, param):
        self.param = param
    def query(self):
        self.param += 1
        return self.param

global_obj = SomeObj(0)

@app.route('/')
def home():
    flash(global_obj.query())
    render_template('index.html')

내 개발 서버에서 이것을 실행하면 1, 2, 3 등을 얻을 것으로 예상됩니다. 100 개의 서로 다른 클라이언트에서 동시에 요청을하면 문제가 발생할 수 있습니까? 예상되는 결과는 100 개의 서로 다른 클라이언트가 각각 1에서 100까지의 고유 한 숫자를 보게되는 것입니다. 또는 다음과 같은 일이 발생합니다.

  1. 클라이언트 1 쿼리. self.param1 씩 증가합니다.
  2. return 문이 실행되기 전에 스레드 self.param는 클라이언트 2로 전환 됩니다. 다시 증가합니다.
  3. 스레드는 클라이언트 1로 다시 전환되고 클라이언트는 숫자 2를 반환합니다.
  4. 이제 스레드가 클라이언트 2로 이동하여 숫자 3을 반환합니다.

클라이언트가 두 개뿐이므로 예상 결과는 2와 3이 아니라 1과 2였습니다. 숫자를 건너 뛰었습니다.

애플리케이션을 확장 할 때 실제로 이런 일이 발생합니까? 전역 변수에 대한 대안은 무엇입니까?

답변:


97

이러한 종류의 데이터를 보유하기 위해 전역 변수를 사용할 수 없습니다. 스레드로부터 안전하지 않을뿐만 아니라 프로세스에 안전 하지도 않으며 프로덕션 환경의 WSGI 서버는 여러 프로세스를 생성합니다. 요청을 처리하기 위해 스레드를 사용하는 경우 계산이 잘못되었을뿐만 아니라 요청을 처리 한 프로세스에 따라 달라질 수 있습니다.

Flask 외부의 데이터 소스를 사용하여 전역 데이터를 보관합니다. 데이터베이스, memcached 또는 redis는 필요에 따라 모두 적절한 별도의 스토리지 영역입니다. Python 데이터를로드하고 액세스해야하는 경우 multiprocessing.Manager. 사용자 당 간단한 데이터에 세션을 사용할 수도 있습니다.


개발 서버는 단일 스레드 및 프로세스에서 실행될 수 있습니다. 각 요청이 동 기적으로 처리되므로 설명하는 동작을 볼 수 없습니다. 스레드 또는 프로세스를 활성화하면 볼 수 있습니다. app.run(threaded=True)또는 app.run(processes=10). (1.0에서는 서버가 기본적으로 스레드됩니다.)


일부 WSGI 서버는 gevent 또는 다른 비동기 작업자를 지원할 수 있습니다. 전역 변수는 여전히 대부분의 경쟁 조건에 대한 보호가 없기 때문에 스레드로부터 안전하지 않습니다. 한 작업자가 값을 얻고, 양보하고, 다른 작업자가 값을 수정하고, 양보 한 다음 첫 번째 작업자도 값을 수정하는 시나리오를 가질 수 있습니다.


요청 중에 일부 전역 데이터를 저장해야하는 경우 Flask의 gobject를 사용할 수 있습니다 . 또 다른 일반적인 경우는 데이터베이스 연결을 관리하는 최상위 개체입니다. 이러한 유형의 "글로벌"의 차이점 요청 간에 사용되지 않고 각 요청에 고유 하며 리소스의 설정 및 해체를 관리하는 것이 있다는 것입니다.


30

이것은 글로벌 스레드 안전성에 대한 답이 아닙니다.

하지만 여기서 세션을 언급하는 것이 중요하다고 생각합니다. 클라이언트 별 데이터를 저장하는 방법을 찾고 있습니다. 모든 연결은 스레드 세이프 방식으로 자체 데이터 풀에 액세스 할 수 있어야합니다.

이것은 서버 측 세션에서 가능하며 매우 깔끔한 플라스크 플러그인 ( https://pythonhosted.org/Flask-Session/) 에서 사용할 수 있습니다.

세션을 설정하면 session모든 경로에서 변수를 사용할 수 있으며 사전처럼 작동합니다. 이 사전에 저장된 데이터는 각 연결 클라이언트에 대해 개별적입니다.

다음은 짧은 데모입니다.

from flask import Flask, session
from flask_session import Session

app = Flask(__name__)
# Check Configuration section for more details
SESSION_TYPE = 'filesystem'
app.config.from_object(__name__)
Session(app)

@app.route('/')
def reset():
    session["counter"]=0

    return "counter was reset"

@app.route('/inc')
def routeA():
    if not "counter" in session:
        session["counter"]=0

    session["counter"]+=1

    return "counter is {}".format(session["counter"])

@app.route('/dec')
def routeB():
    if not "counter" in session:
        session["counter"] = 0

    session["counter"] -= 1

    return "counter is {}".format(session["counter"])


if __name__ == '__main__':
    app.run()

이후 pip install Flask-Session에이 작업을 실행할 수 있습니다. 다른 브라우저에서 액세스를 시도하면 카운터가 서로 공유되지 않음을 알 수 있습니다.


3

위의 upvoted 답변을 완전히 받아들이고 플라스크 '개발 서버'에서 실행되는 프로토 타이핑 또는 정말 간단한 서버의 목적을 위해 생산 및 확장 가능한 플라스크 저장소에 전역 사용을 권장하지 않습니다.

... 파이썬 내장 데이터 유형, 개인적 dict으로 파이썬 문서 ( https://docs.python.org/3/glossary.html#term-global-interpreter-lock )에 따라 global을 사용하고 테스트했습니다. 스레드로부터 안전합니다. 안 처리 안전합니다.

이러한 (서버 전역) dict의 삽입, 조회 및 읽기는 개발 서버에서 실행되는 각 (동시 가능) flask 세션에서 정상입니다.

이러한 전역 딕셔너리가 고유 플라스크 세션 키로 입력 될 때, 쿠키에 맞지 않는 세션 특정 데이터의 서버 측 저장에 다소 유용 할 수 있습니다 (최대 크기 4k).

물론 이러한 서버 전역 딕셔너리는 너무 커져서 메모리에 저장되지 않도록주의해서 보호해야합니다. 일종의 만료 '이전'키 / 값 쌍은 요청 처리 중에 코딩 될 수 있습니다.

다시 말하지만 프로덕션 또는 확장 가능한 배포에는 권장되지 않지만 별도의 db가 주어진 작업에 너무 많은 로컬 작업 지향 서버에는 적합 할 수 있습니다.

...

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.