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()
确保所有线程执行完成)对线程进行统一管理。