flask 之信号(事件)、信号量(计数器、锁)

一、信号

1、什么是信号(事件、绑定、触发)

https://flask.palletsprojects.com/en/stable/api/#core-signals-list

在 Flask 中,信号(Signals)是一种用于在应用程序的不同部分之间进行解耦和通信机制。信号允许不同的组件在不直接相互依赖的情况下进行交互和通知。

这在处理某些事件或操作时非常有用,例如请求完成、用户登录、实例创建等。

Flask 使用了一个名为 blinker 的库来实现信号。通过信号,让开发者可是在flask请求过程中定制一些用户行为

基本概念

  • 信号:信号是一个事件,它可以在特定条件发生时被发送。其他部分可以选择“监听”这个信号。
  • 连接:连接是指某个函数被绑定到信号上,以便在该信号被发送时执行。
  • 发送信号:当某个事件发生时,您可以发送信号,通知所有的连接函数。

2、flask 信号

1、flask信号种类

request_started = _signals.signal('request-started')                # 请求到来前执行
request_finished = _signals.signal('request-finished')              # 请求结束后执行

before_render_template = _signals.signal('before-render-template')  # 模板渲染前执行
template_rendered = _signals.signal('template-rendered')            # 模板渲染后执行

got_request_exception = _signals.signal('got-request-exception')    # 请求执行出现异常时执行
request_tearing_down = _signals.signal('request-tearing-down')      # 请求执行完毕后自动执行(无论成功与否)

appcontext_tearing_down = _signals.signal('appcontext-tearing-down')# 应用上下文执行完毕后自动执行(无论成功与否)
appcontext_pushed = _signals.signal('appcontext-pushed')            # 应用上下文push时执行
appcontext_popped = _signals.signal('appcontext-popped')            # 应用上下文pop时执行

message_flashed = _signals.signal('message-flashed')                # 调用flask在其中添加数据时,自动触发

2、flask 内置信号的使用

需求: 每次模板渲染完,都记录日志--->笨办法--->每个模板渲染完的方法中都要写日志
使用内置信号机制中的 template_rendered 实现:

from flask import Flask, render_template, signals

app = Flask(__name__)
app.debug = True

# 第一步:写一个函数
def render_logger(*args, **kwargs):
    print(args)
    print(kwargs)
    print(kwargs.get('template'))
    app.logger.info('模板渲染了,123')

# 第二步:跟内置信号绑定
signals.template_rendered.connect(render_logger)

# 第三步:正常操作,等信号触发
@app.route('/')
def index():
    return render_template('index.html', name='彭于晏')

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

3、flask自定义信号

from flask import Flask
from flask.signals import _signals

app = Flask(__name__)
app.debug = True

# 第一步:定义自定义信号
home_exec = _signals.signal('print_args')


# 第二步:写个函数
def test_func(*args, **kwargs):
    print(args)
    print(kwargs)
    print('自定义的信号,访问home视图触发,信号执行了')


# 第三步:绑定信号
home_exec.connect(test_func)


# 第四步:触发信号
@app.route('/home')
def home():
    print('home echoing')
    # 第四步:触发信号
    home_exec.send('位置参数', value='传给自定义信号的话')
    return 'home'


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

3、django中使用信号

1、django内置的信号种类

允许你在特定的事件发生时执行一些额外的逻辑。

1. 模型信号 (Model Signals)
pre_save: 在模型实例保存之前发送。
post_save: 在模型实例保存之后发送。
pre_delete: 在模型实例删除之前发送。
post_delete: 在模型实例删除之后发送。
m2m_changed: 在多对多关系中添加、移除或清除对象时发送。
pre_init: 在模型实例初始化之前发送。
post_init: 在模型实例初始化之后发送。

2. 数据库信号 (Database Signals)
pre_migrate: 在迁移操作之前发送。
post_migrate: 在迁移操作之后发送。

3. 请求/响应信号 (Request/Response Signals) request_started: 在 Django 开始处理请求时发送。 request_finished: 在 Django 完成请求处理时发送。 got_request_exception: 在 Django 处理请求时发生异常时发送。
4. 管理信号 (Management Signals) pre_migrate: 在迁移命令运行之前发送。 post_migrate: 在迁移命令运行之后发送。
5. 用户认证信号 (User Authentication Signals) user_logged_in: 在用户登录成功时发送。 user_logged_out: 在用户注销时发送。 user_login_failed: 在用户登录失败时发送。
6. 其他信号 connection_created: 在数据库连接创建之后发送。

2、django内置信号的使用

user_logged_in

  • 触发时机:当用户成功登录后触发。
  • 用途:通常用于记录用户登录时间、更新用户状态或记录日志。
from django.contrib.auth.signals import user_logged_in
from django.dispatch import receiver
from django.utils import timezone

@receiver(user_logged_in)
def log_user_login(sender, request, user, **kwargs):
    # 执行一些操作,例如,记录登录时间
    user.last_login = timezone.now()
    user.save()
    print(f"User {user.username} has logged in.")

user_logged_out

  • 触发时机:当用户成功注销后触发。
  • 用途:可以用于记录用户注销时间、清理会话数据或更新状态。
from django.contrib.auth.signals import user_logged_out
from django.dispatch import receiver

@receiver(user_logged_out)
def log_user_logout(sender, request, user, **kwargs):
    # 执行一些操作,例如,打印注销信息
    if user:
        print(f"User {user.username} has logged out.")
    else:
        print("User logged out with no active session.")

补充: 如何确保信号接收器生效

为了确保这些信号接收器在 Django 启动时被正确注册,您需要确保在应用的 apps.py 文件中导入相关模块。

**配置 apps.py**:

# myapp/apps.py
from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'myapp'

    def ready(self):
        import myapp.signals  # 确保信号接收器被加载

在 __init__.py 文件中指定应用配置:

# myapp/__init__.py
default_app_config = 'myapp.apps.MyAppConfig'

3、django自定义信号

定义自定义信号

首先,在 Django 应用中创建一个文件 signals.py(如果还没有),并在其中定义自定义信号。

# myapp/signals.py
from django.dispatch import Signal

# 定义一个自定义信号
my_custom_signal = Signal(providing_args=["message"])

创建接收器函数

接收器函数将在信号触发时执行。您可以在 receivers.py 中创建这个函数。

# myapp/receivers.py
from django.dispatch import receiver
from .signals import my_custom_signal

@receiver(my_custom_signal)
def my_custom_signal_receiver(sender, **kwargs):
    message = kwargs.get("message", "")
    print(f"Received signal with message: {message}")
    # 在这里可以添加其他逻辑

发送信号

在需要的时候(例如某个视图函数中),发送信号使接收器触发。

# myapp/views.py
from django.shortcuts import render
from .signals import my_custom_signal

def my_view(request):
    # 发送自定义信号
    my_custom_signal.send(sender=my_view.__class__, message="Hello, this is a custom signal!")
    
    return render(request, 'my_template.html')

确保接收器被加载

为确保接收器在 Django 启动时被正确加载和注册,需在应用的 apps.py 中导入接收器文件。

# myapp/apps.py
from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'myapp'

    def ready(self):
        import myapp.receivers  # 确保信号接收器被导入,这样它们才能被注册

配置应用设置

在应用的 __init__.py 文件中使用 default_app_config 来确保 apps.py 被正确使用。

# myapp/__init__.py
default_app_config = 'myapp.apps.MyAppConfig'

二、Semaphore信号量

信号量(Semaphore)是多线程编程中的一种同步原语,用于控制对共享资源的访问。信号量能够限制同时访问某个资源的线程数量,是实现线程间同步的重要工具之一。

1.、信号量的基本概念

  • 计数信号量:信号量维护一个内部计数器。计数器初始化为一个非负整数,用于表示当前可访问资源的单位数量。
  • P 操作(等待):线程试图访问资源时,需要执行信号量的 acquire() 操作。如果信号量的计数大于零,计数减一且线程成功获取访问。如果为零,线程将被阻塞直到信号量释放。
  • V 操作(信号):线程用完资源后,执行 release() 操作,信号量计数加一,可能唤醒等待的线程。

2、使用案例

import threading
import time

# 初始化信号量,允许最多 5 个线程同时访问资源
semaphore = threading.Semaphore(5)

def worker(num):
    print(f"Thread-{num} is trying to acquire the semaphore")
    with semaphore:
        print(f"Thread-{num} has acquired the semaphore")
        time.sleep(2)
        print(f"Thread-{num} is releasing the semaphore")

# 创建并启动50个线程
threads = [threading.Thread(target=worker, args=(i,)) for i in range(50)]

# 启动线程
for thread in threads:
    thread.start()

# 等待所有线程完成
for t in threads:
    t.join()

print("All threads have finished their execution.")

补充:

1 with semaphore: 使用上下文管理器

  • 上下文管理器with 语句是Python中的上下文管理器的使用方式,它会自动管理资源的获取和释放。在信号量的上下文中,with semaphore: 自动调用了 semaphore.acquire() 来获取信号量,并会在代码块结束时自动调用 semaphore.release() 来释放信号量。这样可以确保信号量的获取和释放是成对出现的,即使代码块中发生异常也是如此。
  • 作用:使用 with semaphore: 可以让代码更加简洁,并减少错误释放信号量的风险。

如果不使用 with semaphore: 语法(上下文管理器),我们需要手动调用 acquire() 和 release() 方法来控制信号量。

def worker(num):
    print(f"Thread-{num} is trying to acquire the semaphore")
    semaphore.acquire()  # 手动获取信号量
    try:
        print(f"Thread-{num} has acquired the semaphore")
        time.sleep(2)  # 模拟一些长时间运行的任务
    finally:
        semaphore.release()  # 确保信号量的释放
        print(f"Thread-{num} is releasing the semaphore")

2、threads = [] 定义空线程列表

threads = [] 的作用是定义一个空的列表,用来存储创建并启动的线程对象,该列表确实用于管理和跟踪线程,是实现多个线程并发的重要组件。

详细解释:

  • 列表作用:在 Python 中,我们通常使用列表来收集和管理线程,因为每个线程都是一个独立的对象。将线程对象存储在列表中可以方便地操控,如启动、等待或检查线程的状态。
  • 线程管理:在创建 20 个线程后,我们把每个线程对象添加到 threads 列表中。这可以让我们在后续处理时(例如调用 join() 确保所有线程执行完成)对线程进行统一管理。

 

posted @ 2024-11-15 15:00  凡人半睁眼  阅读(8)  评论(0编辑  收藏  举报