杰瑞科技汇

Flask线程安全与并发处理如何实现?

Of course! Let's dive deep into threading in Flask. This is a crucial topic for understanding how Flask works and how to build performant applications.

Flask线程安全与并发处理如何实现?-图1
(图片来源网络,侵删)

We'll cover three main areas:

  1. How Flask's Built-in Development Server Works (The "Why")
  2. How to Run Your Own Background Threads in a Flask App (The "How")
  3. The Production Environment: Using a Proper WSGI Server (The "Best Practice")

The Flask Development Server: A Single Thread

When you run your Flask app with flask run, you're using the built-in development server. This server is not designed for production, and a key characteristic is that it handles requests one at a time in a single thread.

Let's prove this with a simple example.

Example: The Blocking Request

Imagine you have a route that performs a long, blocking task (like a network call or a heavy calculation).

Flask线程安全与并发处理如何实现?-图2
(图片来源网络,侵删)

app.py

import time
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
    return "Hello, World! This is the home page."
@app.route('/long-task')
def long_task():
    print("Starting a long task that will take 5 seconds...")
    time.sleep(5)  # This simulates a blocking operation
    print("Long task finished.")
    return "The long task is finally done!"
if __name__ == '__main__':
    app.run(debug=True, port=5000)

How to Test It:

  1. Run the app: python app.py
  2. Open your browser and go to http://127.0.0.1:5000/long-task.
  3. Immediately, open a second browser tab or window and try to go to http://127.0.0.1:5000/.

What you'll observe:

  • The first request to /long-task will hang for 5 seconds.
  • The second request to will not respond until the first request is complete. You'll see the "Hello, World!" message only after the 5-second sleep is over.

This is because the development server has only one thread. It's busy handling the first request and cannot serve the second one until it's free. This is why it's unsuitable for production, where you expect concurrent users.

Flask线程安全与并发处理如何实现?-图3
(图片来源网络,侵删)

Running Your Own Background Threads in Flask

Sometimes you need to run tasks in the background without making the user wait. This is perfect for:

  • Sending emails.
  • Processing uploaded files.
  • Running periodic maintenance tasks.
  • Interacting with a long-running external API.

Flask provides a fantastic, built-in way to do this with the app.app_context() and threading module.

The Correct Way: Using app.app_context()

When you start a new thread, it doesn't automatically have access to the Flask application context. The application context contains information about the current request, URL, and other things. If your background task tries to access request or g, it will fail.

You must manually create an application context for the thread.

Example: Sending an Email in the Background

Let's create a route that triggers a "send email" task in a background thread.

app_with_thread.py

import time
import threading
from flask import Flask, jsonify
app = Flask(__name__)
def send_email_background(recipient):
    """
    A function that simulates sending an email.
    It needs to run in a background thread.
    """
    print(f"[{threading.current_thread().name}] Starting to send email to {recipient}...")
    time.sleep(3)  # Simulate network delay
    print(f"[{threading.current_thread().name}] Email successfully sent to {recipient}!")
    # In a real app, you might log this to a database here.
@app.route('/send-email/<recipient>')
def send_email(recipient):
    """
    This endpoint starts the email-sending task in a new thread.
    The user gets an immediate response.
    """
    # Create a new thread that will run our email function
    thread = threading.Thread(
        target=send_email_background,
        args=(recipient,),
        name="EmailThread"
    )
    # Start the thread. It will run in the background.
    thread.start()
    return jsonify({
        "status": "success",
        "message": f"Email sending process for {recipient} has been initiated."
    })
if __name__ == '__main__':
    app.run(debug=True, port=5000)

How to Test It:

  1. Run the app: python app_with_thread.py
  2. Open your browser and go to http://127.0.0.1:5000/send-email/test@example.com.

What you'll observe:

  • You will get an immediate JSON response: {"message": "Email sending process for test@example.com has been initiated.", "status": "success"}.
  • Almost instantly, you will see the print statements from the background thread appear in your terminal:
    [EmailThread] Starting to send email to test@example.com...
    [EmailThread] Email successfully sent to test@example.com!
  • Your server is now free to handle other requests immediately.

Crucial Point: What if your background task needs to access the database or Flask's g object?

You must wrap the function's logic in app.app_context().

Example: Background Task with Database Access

# Assuming you have an app and a database setup
# from flask_sqlalchemy import SQLAlchemy
# db = SQLAlchemy(app)
def update_user_background(user_id):
    with app.app_context():  # <-- THIS IS THE MAGIC LINE
        # Now you can safely access the database
        # user = db.session.get(User, user_id)
        # user.last_login = datetime.utcnow()
        # db.session.commit()
        print(f"Updated user {user_id} in the database.")
        time.sleep(2)
        print(f"Finished updating user {user_id}.")
# In your route:
# thread = threading.Thread(target=update_user_background, args=(1,))
# thread.start()

The Production Environment: A Proper WSGI Server

The background threading solution above is great for your application's own tasks, but it does not solve the single-threaded nature of the Flask development server. For production, you need a server that can handle many concurrent requests.

This is where a production-grade WSGI (Web Server Gateway Interface) server comes in. These servers are built for concurrency and performance. The most popular ones are:

  • Gunicorn: A pure Python WSGI server that uses "worker" processes. By default, it uses sync workers, which are multi-threaded.
  • uWSGI: A very fast and feature-rich WSGI server, often used with Nginx.
  • Waitress: A pure Python WSGI server with no external dependencies.

How Gunicorn Works (The Simple View)

When you deploy with Gunicorn, you tell it how many "workers" to run.

Command:

# Run the app with 4 worker processes
gunicorn --workers 4 --bind 0.0.0.0:8000 myapp:app
  • --workers 4: This starts 4 separate Python processes.
  • Each worker process, by default, can handle multiple requests simultaneously using its own threads (or async workers if configured).
  • So, with 4 workers, you can now handle 4 (or more) requests at the same time, completely bypassing the single-threaded limitation of the dev server.

Summary: Which Approach to Use?

Scenario Solution Why?
Local Development flask run Simple and fast to get started. Not for production.
Running long tasks triggered by a user threading.Thread with app.app_context() Keeps your API responsive. The user gets an immediate response while the task completes in the background.
Periodic tasks (e.g., every hour) APScheduler or a dedicated task queue (Celery, RQ). APScheduler is a library for cron-like jobs in Python. It integrates cleanly with Flask and handles the threading for you.
Production Deployment A WSGI server like Gunicorn or uWSGI. These servers are built for performance and concurrency, allowing your Flask app to handle many users at once.

Final Recommendation

  1. For development: Stick with flask run.
  2. For background tasks triggered by your app: Use threading.Thread and always wrap your function's logic in with app.app_context():.
  3. For production: Do not use the Flask development server. Use a proper WSGI server like Gunicorn to run your application. Use background threads or a task queue for your long-running application-specific tasks.
分享:
扫描分享到社交APP
上一篇
下一篇