V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
Alexf4
V2EX  ›  Python

Flask-SocketIO 多线程遇到的问题

  •  
  •   Alexf4 ·
    alexcc4 · 2021-04-14 10:48:53 +08:00 · 2055 次点击
    这是一个创建于 1317 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近在使用 Flask-SocketIO 想实现实时跟踪 celery 的任务日志(每个任务一个日志文件)的功能。 在本地运行,开了两个窗口,第二个窗口会输出自己的日志,第一个窗口会输入自己的加后一个窗口的日志。类似后续每开的一个窗口,前面的窗口都会 append 此窗口的日志。

    client

    import sys
    
    import socketio
    
    
    client = socketio.Client(reconnection=False)
    
    
    @client.on('log', namespace='/cdc')
    def on_log(args):
        print(args['data'])
    
    
    def send(log_path):
        try:
            client.connect('http://127.0.0.1:5000', namespaces='/cdc')
            client.emit('log', {'log_path': log_path}, namespace='/cdc')
        except KeyboardInterrupt:
            try:
                sys.exit(0)
            except SystemExit:
                sys.exit(1)
    

    server

    sio = flask_socketio.SocketIO(async_mode=async_mode)
    
    
    @sio.on('log', namespace='/cdc')
    def log(data):
        thread = sio.start_background_task(scan_log, data['log_path'], None, flask.request.sid)
    
    
    def scan_log(log_path, app=None, client_id=None):
        counter = 0
    
        with open(log_path, 'r') as f:
            while True:
                line = f.readline()
                if not line:
                    sio.sleep(0.1)
                    continue
                counter += 1
                sio.emit('log', {'data': line.strip()}, namespace='/cdc', to=client_id)
    

    各位有没有遇到类似的情况?或者有什么大概的思路?

    第 1 条附言  ·  2021-04-14 11:55:37 +08:00

    之前启动 server 的方式是默认的 flask run

    后续尝试了用 gunicorn + eventlet 的方式启动 gunicorn --worker-class eventlet -w 2 "app:create_app()" 然后两个 client 的 log 输出就正常了。

    5 条回复    2021-04-14 12:56:52 +08:00
    dassh
        1
    dassh  
       2021-04-14 11:18:09 +08:00
    每个日志应该有一个独立的标识,然后 on_log 里对标识进行判断,如果是对应的才 print
    Alexf4
        2
    Alexf4  
    OP
       2021-04-14 11:35:10 +08:00
    @dassh 这里每一个日志都是独立的(每个任务都生成对应的日志)
    所以 client on log 会接受 server 多个线程分别 emit 的数据?
    我这个 scan log 不是用多个线程进行的么,另外 emit 的时候用 to 这个参数也区别了 client 的发送的呀
    dassh
        3
    dassh  
       2021-04-14 11:47:37 +08:00
    sio.on('log', ...)就表示你订阅了所有 server emit 的'log'的事件,所以你需要进行过滤判断,to 参数我没用过哈,给你一种解决方案
    ```

    @client.on('log', namespace='/cdc')
    def on_log(args):
    if args['log_path'] == sys.argv[1] #这里的 sys.argv[1]就是你传入 log_path 的地址
    print(args['data'])

    def scan_log(log_path, app=None, client_id=None):
    ...
    sio.emit('log', {'data': line.strip(), 'log_path': log_path}, namespace='/cdc', to=client_id)

    ```
    Latin
        4
    Latin  
       2021-04-14 12:55:08 +08:00
    同一楼加 uuid 过滤
    Alexf4
        5
    Alexf4  
    OP
       2021-04-14 12:56:52 +08:00
    @dassh 感谢你的思路~
    - to 的参数我测试了下, 在不调用 `start_background_task ` 的情况下,可以完成独立 print 功能
    - 在调用 `start_background_task ` 才会出现提到的,旧的窗口也会获取到新窗口的数据(日志数据)
    - 所以是比较想好奇这一块的实现方式或者原理
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3298 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 11:57 · PVG 19:57 · LAX 03:57 · JFK 06:57
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.