Являются ли глобальные переменные потокобезопасными в Flask? Как передать данные между запросами?

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.paramувеличивается на 1.
  2. Прежде чем оператор return может быть выполнен, поток переключается на клиент 2. self.paramснова увеличивается на единицу.
  3. Поток переключается обратно на клиента 1, и, скажем, клиенту возвращается номер 2.
  4. Теперь поток переходит к клиенту 2 и возвращает ему / ей номер 3.

Поскольку клиентов было всего два, ожидаемые результаты были 1 и 2, а не 2 и 3. Число было пропущено.

Произойдет ли это на самом деле, когда я расширю свое приложение? Какие альтернативы глобальной переменной мне следует искать?

Sayantankhan
источник

Ответы:

98

Вы не можете использовать глобальные переменные для хранения таких данных. Это не только не потокобезопасно, но и не безопасно для процессов , а серверы WSGI в производстве порождают несколько процессов. Если бы вы использовали потоки для обработки запросов, ваши подсчеты не только были бы неправильными, но и зависели бы от того, какой процесс обработал запрос.

Используйте источник данных за пределами Flask для хранения глобальных данных. База данных, memcached или redis - все подходящие отдельные области хранения, в зависимости от ваших потребностей. Если вам нужно загрузить данные Python и получить к ним доступ, подумайте multiprocessing.Manager. Вы также можете использовать сеанс для простых данных для каждого пользователя.


Сервер разработки может работать в одном потоке и в одном процессе. Вы не увидите описанного вами поведения, поскольку каждый запрос будет обрабатываться синхронно. Включите потоки или процессы, и вы это увидите. app.run(threaded=True)или app.run(processes=10). (В версии 1.0 сервер по умолчанию является многопоточным.)


Некоторые серверы WSGI могут поддерживать gevent или другой асинхронный рабочий. Глобальные переменные по-прежнему не являются потокобезопасными, потому что все еще нет защиты от большинства состояний гонки. У вас все еще может быть сценарий, когда один рабочий получает значение, дает, другой изменяет его, дает, а затем первый рабочий также изменяет его.


Если вам нужно сохранить некоторые глобальные данные во время запроса, вы можете использовать gобъект Flask . Другой распространенный случай - это объект верхнего уровня, который управляет подключениями к базе данных. Различие для этого типа «глобального» состоит в том, что он уникален для каждого запроса, не используется между запросами, и есть что-то, управляющее настройкой и разрывом ресурса.

давидизм
источник
30

На самом деле это не ответ на безопасность потоков глобальных объектов.

Но я считаю важным упомянуть здесь о сессиях. Вы ищете способ хранить данные для конкретного клиента. Каждое соединение должно иметь доступ к своему собственному пулу данных поточно-ориентированным способом.

Это возможно с сеансами на стороне сервера, и они доступны в очень удобном плагине flask: 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этого вы сможете запустить это. Попробуйте получить к нему доступ из разных браузеров, вы увидите, что счетчик между ними не используется.

lhk
источник
3

Полностью принимая предыдущие одобренные ответы и не рекомендуя использовать глобальные переменные для производства и масштабируемого хранилища Flask, с целью создания прототипов или действительно простых серверов, работающих под флягой `` сервер разработки '' ...

...

Встроенные типы данных Python, и я лично использовал и тестировал глобальные dict, согласно документации Python, являются потокобезопасными . Не безопасный процесс .

Вставки, поиск и чтение из такого (глобального сервера) dict будут в порядке из каждого (возможно, одновременного) сеанса Flask, запущенного на сервере разработки.

Когда такой глобальный dict связан с уникальным сеансовым ключом Flask, он может быть весьма полезен для хранения на стороне сервера специфичных для сеанса данных, которые в противном случае не помещаются в cookie (максимальный размер 4 КБ).

Конечно, такой глобальный запрос сервера должен быть тщательно защищен, чтобы он не стал слишком большим и не оказался в памяти. Во время обработки запроса может быть закодировано какое-то истечение срока действия «старых» пар ключ / значение.

Опять же, это не рекомендуется для производственных или масштабируемых развертываний, но, возможно, подходит для локальных ориентированных на задачи серверов, где отдельная база данных слишком велика для данной задачи.

...

Р. Симак
источник