Подтвердить что ты не робот

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

В моем приложении состояние общего объекта изменяется путем запросов, и ответ зависит от состояния.

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

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

Будет ли это происходить, когда я увеличиваю свое приложение? На какие альтернативы глобальной переменной я должен смотреть?

4b9b3361

Ответ 1

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

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


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


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


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

Ответ 2

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

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

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

Быстрый и грязный механизм, не зависящий от потока:

  app = Flask(__name__)
  app.speed = 0.0 # add new properties to the instance
  app.last_check_tm = time.time()
  ...

У меня есть приложение, в котором Fask работает как локальный wifi-сервер на Raspberry Pi, чтобы пользователи могли подключаться с помощью мобильного телефона. Приложение должно "запоминать" данные, а также время между запросами и использовать эту информацию, поскольку оно собирает информацию с локальных аппаратных вводов (где session недоступно). NB это только любое использование, когда Flask обслуживает только одного пользователя за раз.

ИЗМЕНИТЬ. Как указано ниже, этот подход - плохая идея. sqlite имеет возможность создать базу данных в памяти, которая предотвращает повторную запись на SD-карту (чего я пытался избежать) PS К сожалению, sqlite не может работать в другом потоке, поэтому это простое решение, вероятно, не будет работайте для меня, и мне понадобится более сложная система (возможно, что-то вроде строк Fask в другой подпроцессе.Процесс и отправка информации в объекты и объекты с очередью, немного беспорядочно!) Как показало давидизм out sqlite может это сделать, и я перешел на этот гораздо лучший метод. Создал соединение с помощью conn = sqlite3.connect(':memory:',check_same_thread=False)