내 앱에서 공통 객체의 상태는 요청을 통해 변경되며 응답은 상태에 따라 다릅니다.
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 쿼리.
self.param
1 씩 증가합니다. - return 문이 실행되기 전에 스레드
self.param
는 클라이언트 2로 전환 됩니다. 다시 증가합니다. - 스레드는 클라이언트 1로 다시 전환되고 클라이언트는 숫자 2를 반환합니다.
- 이제 스레드가 클라이언트 2로 이동하여 숫자 3을 반환합니다.
클라이언트가 두 개뿐이므로 예상 결과는 2와 3이 아니라 1과 2였습니다. 숫자를 건너 뛰었습니다.
애플리케이션을 확장 할 때 실제로 이런 일이 발생합니까? 전역 변수에 대한 대안은 무엇입니까?
답변
이러한 종류의 데이터를 보유하기 위해 전역 변수를 사용할 수 없습니다. 스레드로부터 안전하지 않을뿐만 아니라 프로세스에 안전 하지도 않으며 프로덕션 환경의 WSGI 서버는 여러 프로세스를 생성합니다. 요청을 처리하기 위해 스레드를 사용하는 경우 계산이 잘못되었을뿐만 아니라 요청을 처리 한 프로세스에 따라 달라질 수 있습니다.
Flask 외부의 데이터 소스를 사용하여 전역 데이터를 보관합니다. 데이터베이스, memcached 또는 redis는 필요에 따라 모두 적절한 별도의 스토리지 영역입니다. Python 데이터를로드하고 액세스해야하는 경우 multiprocessing.Manager
. 사용자 당 간단한 데이터에 세션을 사용할 수도 있습니다.
개발 서버는 단일 스레드 및 프로세스에서 실행될 수 있습니다. 각 요청이 동 기적으로 처리되므로 설명하는 동작을 볼 수 없습니다. 스레드 또는 프로세스를 활성화하면 볼 수 있습니다. app.run(threaded=True)
또는 app.run(processes=10)
. (1.0에서는 서버가 기본적으로 스레드됩니다.)
일부 WSGI 서버는 gevent 또는 다른 비동기 작업자를 지원할 수 있습니다. 전역 변수는 여전히 대부분의 경쟁 조건에 대한 보호가 없기 때문에 스레드로부터 안전하지 않습니다. 한 작업자가 값을 얻고, 양보하고, 다른 작업자가 값을 수정하고, 양보 한 다음 첫 번째 작업자도 값을 수정하는 시나리오를 가질 수 있습니다.
요청 중에 일부 전역 데이터를 저장해야하는 경우 Flask의 g
object를 사용할 수 있습니다 . 또 다른 일반적인 경우는 데이터베이스 연결을 관리하는 최상위 개체입니다. 이러한 유형의 “글로벌”의 차이점 은 요청 간에 사용되지 않고 각 요청에 고유 하며 리소스의 설정 및 해체를 관리하는 것이 있다는 것입니다.
답변
이것은 글로벌 스레드 안전성에 대한 답이 아닙니다.
하지만 여기서 세션을 언급하는 것이 중요하다고 생각합니다. 클라이언트 별 데이터를 저장하는 방법을 찾고 있습니다. 모든 연결은 스레드 세이프 방식으로 자체 데이터 풀에 액세스 할 수 있어야합니다.
이것은 서버 측 세션에서 가능하며 매우 깔끔한 플라스크 플러그인 ( 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
에이 작업을 실행할 수 있습니다. 다른 브라우저에서 액세스를 시도하면 카운터가 서로 공유되지 않음을 알 수 있습니다.
답변
위의 upvoted 답변을 완전히 받아들이고 플라스크 ‘개발 서버’에서 실행되는 프로토 타이핑 또는 정말 간단한 서버의 목적을 위해 생산 및 확장 가능한 플라스크 저장소에 전역 사용을 권장하지 않습니다.
… 파이썬 내장 데이터 유형, 개인적 dict
으로 파이썬 문서 ( https://docs.python.org/3/glossary.html#term-global-interpreter-lock )에 따라 global을 사용하고 테스트했습니다. 스레드로부터 안전합니다. 안 처리 안전합니다.
이러한 (서버 전역) dict의 삽입, 조회 및 읽기는 개발 서버에서 실행되는 각 (동시 가능) flask 세션에서 정상입니다.
이러한 전역 딕셔너리가 고유 플라스크 세션 키로 입력 될 때, 쿠키에 맞지 않는 세션 특정 데이터의 서버 측 저장에 다소 유용 할 수 있습니다 (최대 크기 4k).
물론 이러한 서버 전역 딕셔너리는 너무 커져서 메모리에 저장되지 않도록주의해서 보호해야합니다. 일종의 만료 ‘이전’키 / 값 쌍은 요청 처리 중에 코딩 될 수 있습니다.
다시 말하지만 프로덕션 또는 확장 가능한 배포에는 권장되지 않지만 별도의 db가 주어진 작업에 너무 많은 로컬 작업 지향 서버에는 적합 할 수 있습니다.
…