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
XIVN1987
V2EX  ›  Python

PyQt 编程中多线程应该用 QThread、QTimer 还是 threading??

  •  
  •   XIVN1987 · 2018-01-31 13:20:01 +08:00 · 13981 次点击
    这是一个创建于 2521 天前的主题,其中的信息可能已经有所发展或是发生改变。

    按我的理解,由于 GIL 的存在,threading.Thread 肯定无法利用多核,任意时刻只有一个线程在跑; QThread 的话在 C++里肯定是能多个同时跑的,但在 PyQt 里它执行的也是 Python 代码,所以应该也和 threading 一样并不能真的并行;而 QTimer 似乎原本就是在创建它的那个线程里跑的,但既然大家都没法真的并行,那跟另外两个似乎是一回事儿,,而且 QTimer 写起来感觉更简洁、直观,也不用 sleep(),,所以我觉得写 PyQt 程序的时候并发逻辑用 QTimer 写就行了,,

    32 条回复    2018-02-01 13:15:19 +08:00
    crysislinux
        1
    crysislinux  
       2018-01-31 13:41:53 +08:00 via Android
    qthread 是真线程。Python 只是包装,底下还是 c++在跑
    XIVN1987
        2
    XIVN1987  
    OP
       2018-01-31 13:46:29 +08:00
    @crysislinux

    可是 QThread 的 run 函数里的代码都是 python 代码啊,,比如:
    class TimeThread(QtCore.QThread):
    def run(self):
    # ... ...
    XIVN1987
        3
    XIVN1987  
    OP
       2018-01-31 13:47:41 +08:00
    v2ex 的回复真是,,,即不能修改、也不能预览,,
    XIVN1987
        4
    XIVN1987  
    OP
       2018-01-31 13:48:20 +08:00
    @crysislinux
    ''' python
    class TimeThread(QtCore.QThread):
    def run(self):
    # ... ...
    '''
    crysislinux
        5
    crysislinux  
       2018-01-31 13:49:31 +08:00 via Android
    @XIVN1987 这个还真没仔细想过。但是我以前用过,你可以试试在线程里 sleep,看看主线程 ui 会卡住不
    XIVN1987
        6
    XIVN1987  
    OP
       2018-01-31 13:55:22 +08:00
    @crysislinux

    其实都不用试,要是真能通过这种方法实现真正的并行,,那大家就不会再提 GIL 的事情了,,
    GeruzoniAnsasu
        7
    GeruzoniAnsasu  
       2018-01-31 13:55:39 +08:00
    @XIVN1987 qt 的 runtime 都是 c++封装的,不必担心

    类似于 run1(){PyEval_CallObject(run)}
    XIVN1987
        8
    XIVN1987  
    OP
       2018-01-31 13:58:13 +08:00
    @GeruzoniAnsasu

    只要你执行 Python 代码,,那 GIL 你就避不开,,

    你这样一样得 Python 的虚拟机执行啊,,GIL 在虚拟机层面实现的。。
    GeruzoniAnsasu
        9
    GeruzoniAnsasu  
       2018-01-31 14:09:51 +08:00
    @XIVN1987 emmmmm 好像的确是这样,但属于 qt 框架本身的东西可以绕开 python 封装,worker 线程间可能会被 gil 影响,但 gui 的部分应该影响不着

    当然了我也是猜的,确实没细想过这种问题
    h4lbhg1G
        10
    h4lbhg1G  
       2018-01-31 14:10:23 +08:00
    Jython and IronPython have no GIL and can fully exploit multiprocessor systems

    PyPy currently has a GIL like CPython

    in Cython the GIL exists, but can be released temporarily using a "with" statement
    weyou
        11
    weyou  
       2018-01-31 15:07:48 +08:00
    @crysislinux 你怕不是对 python 的线程有什么误解吧。不管是 python 还是 Qt,任何线程里 sleep,其他线程都不可能卡住啊。Python 的线程也是货真价实的系统线程啊。只不过 python 的 GIL 做了解释器级别的同步,在非 IO 的情况下硬生生弄成了单线程,但本质上还是多线程。对于 sleep 这个操作来说,还是会真的挂起线程释放 CPU 到其他线程的。
    justou
        12
    justou  
       2018-01-31 15:46:36 +08:00
    QThread 的最大作用是可以发送信号, 利用信号-槽机制可以方便的与 GUI 跨线程交互, 从这个方面来抉择使用哪种线程,
    用一个 QThread 来管理一堆 python 的线程或进程在 GUI 多线程编程中是一种比较方便的模式
    XIVN1987
        13
    XIVN1987  
    OP
       2018-01-31 16:00:09 +08:00
    @justou
    用 QTimer 直接在同一个线程里,根本不用发信号,,岂不更方便。。
    brightguo
        14
    brightguo  
       2018-01-31 16:21:41 +08:00
    额,QTimer 是定时器,和多线程有啥关系。。。
    给你一个复杂计算,界面不就卡死了了
    XIVN1987
        15
    XIVN1987  
    OP
       2018-01-31 16:34:57 +08:00
    @brightguo

    嗯,好像有道理,,

    不过大多数情况下用 QTimer 还是没问题的,,我好几个 PyQt 程序用 QTimer 替代多线程,运行良好
    nicevar
        16
    nicevar  
       2018-01-31 16:54:03 +08:00
    @XIVN1987 额。。。QTimer 与线程有啥关系,怎么能代替线程呢,你的程序能用只能说明代码有问题或者不需要多线程,还可能已经有异步实现
    用 QThread 肯定躲不过 GIL,假设 PyQt 程序信号量超级多,肯定会堵塞,但是 PyQt 都是些小程序,到不了这个地步
    crysislinux
        17
    crysislinux  
       2018-01-31 16:58:01 +08:00
    @weyou 你再仔细看看我说的呢,我的意思是在 QThread 里 sleep,这里只有 QThread,没有 Python 的 thread,要是代码实际上并没有在 QThread 里跑,那就应该卡住
    wwqgtxx
        18
    wwqgtxx  
       2018-01-31 16:59:45 +08:00
    @nicevar 其实说到“信号量超级多”这个问题,就算没有 GIL 他也一样会堵塞呀,因为 UI 线程还是只有一个,如果你要是在两个普通的 QThread 之间发送数据的话,那还是老老实实用 python 标准库的 queue 方便,反正都绕不过 GIL
    justou
        19
    justou  
       2018-01-31 17:30:06 +08:00
    用 QTimer 来完成复杂任务避免 GUI 堵塞是一种摒弃的做法了, 一点都不 OOP, 随着任务变多变复杂整个代码只会变得一团糟. 在两种情形下用过 QTimer: 1) GUI 初始化时, 用 QTimer.singleShot(0, load_data)来加载数据避免界面半天不出来, 2) 纯定时器.

    对于 1), 如果预处理比较复杂, 我宁愿放到单独的预处理线程, 界面只用于数据输入, 数据展示, 处理信号 /事件. 这样界面就很流畅

    This is the traditional way of implementing heavy work in GUI applications, but as multithreading is nowadays becoming available on more and more platforms, we expect that zero-millisecond QTimer objects will gradually be replaced by QThreads.

    http://doc.qt.io/qt-5/qtimer.html

    另外, 线程中有 sleep 的代码充满着坏味道
    nicevar
        20
    nicevar  
       2018-01-31 17:30:26 +08:00
    @wwqgtxx 相对于 Qt 程序,pyQt 有 GIL,线程越多 UI 线程的时间片就越有限,堵塞的临界点就不一样了
    XIVN1987
        21
    XIVN1987  
    OP
       2018-01-31 17:39:21 +08:00
    @justou

    感谢回复,,

    可你也要考虑 PyQt 和 Qt 的区别,Qt 里面是有真正的并行执行的,,两个线程分别占一个 CPU 核、并行执行;这时候 QThread 当然比 QTimer 好 100 倍

    可是 PyQt 里面由于 GIL 的问题,你开了 QThread 它和主线程也只是交替执行的,,并没有真正的并行,,这时候感觉线程和 QTimer 区别就没那么明显了
    justou
        22
    justou  
       2018-01-31 18:23:24 +08:00
    @XIVN1987 如果是要在 PyQt 程序中进行密集计算的话, 我会用一个 QThread 来管理一些进程, 或者干脆把密集计算部分用 C++来实现; 如果线程只是进行 IO 操作的话, PyQt 的 QThread 和 C++的 QThread 跑起来感觉不到任何差别, 甚至可以利用协程, 这比 C++的 QThread 有更好的并发.

    用 QThread 不用 Timer 很大程度上是从设计上考虑, 用 QThread 可以形成更好的程序结构. 用 QThread 当线程 /进程管理者, 负责跟界面的交互, 而不是生成一堆 QThread 去完成具体的任务
    wwqgtxx
        23
    wwqgtxx  
       2018-01-31 18:47:50 +08:00 via iPhone
    @justou 其实吧,要是能在 c++中无缝操作 python 的 list 和 dict 就完美了,可惜 python 并没有提供官方的 c++接口
    weyou
        24
    weyou  
       2018-01-31 19:12:10 +08:00
    @crysislinux 恩,可能有点误解了
    justou
        25
    justou  
       2018-01-31 19:17:51 +08:00
    @wwqgtxx 为什么有这种需求呢?
    wwqgtxx
        26
    wwqgtxx  
       2018-01-31 21:16:55 +08:00 via iPhone
    @justou 有些时候希望把一些运算量大的部分用 c++写,但是数据源依然是来自 python 代码,而这个时候,如果需要处理大量数据,那么这些数据就一定是放在 list 或者 dict 中的,有些时候还要求返回的依然是一个 list,所以如果可以在 c++中像 std::vector 以及 std::map 中操作 list 和 map 那么互操作性就大大增加了
    这点 Boost.Python 做的就挺好,但是整个 boost 类库有点过于庞大了,要是能拆分出来就完美了
    justou
        27
    justou  
       2018-01-31 22:07:31 +08:00
    @wwqgtxx "大量数据"要看具体类型, 数值数据用 list 存储是极其低效的, 可以用标准库的 array 或 numpy 存储, 想要避开 GIL 处理字符串可以在 Cython 中直接使用 C++的容器(string, vector, map 等, http://docs.cython.org/en/latest/src/userguide/wrapping_CPlusPlus.html ). array 和 numpy 也可以很容易把底层指针暴露给 C++处理

    没实际用过 boost python, 感觉不如 Cython 简便直观, 有空深入学习下. 要拆分出来应该很容易, 只需要单独编译 boost python 库就行了 http://www.boost.org/doc/libs/1_66_0/libs/python/doc/html/building/installing_boost_python_on_your_.html
    wizardforcel
        28
    wizardforcel  
       2018-01-31 22:13:19 +08:00 via Android
    qtimer 不是多线程,它是把消息定期插入消息队列,消息队列各个消息是串行的。
    wizardforcel
        29
    wizardforcel  
       2018-01-31 22:16:35 +08:00 via Android
    @weyou 是限制在单核上的多线程。因为有调度,执行顺序还是随机的。
    wwqgtxx
        30
    wwqgtxx  
       2018-02-01 08:09:02 +08:00 via iPhone
    @justou 很多情况下存在数据来源不可控的问题,原来的代码是使用纯 python 代码写的,自然也就没有用 array 和 numpy 的容器,而有些运算是需要做一些修改源数组的,这时候如果再写一部分衔接代码在两种数据结构之间拷贝就不太划来了
    wizardforcel
        31
    wizardforcel  
       2018-02-01 13:08:11 +08:00 via Android
    @XIVN1987 还是有区别的。比如 qtimer 里面长时间 io 操作会卡界面。而线程里不会,等 io 的时候它会让渡控制权。

    再比如线程里面对字段或者全局变量的访问需要上锁,因为有调度,qtimer 就不用。

    单核上的线程也是线程,不能假设它是严格串行的。
    XIVN1987
        32
    XIVN1987  
    OP
       2018-02-01 13:15:19 +08:00
    @wizardforcel

    多谢,这点我已经理解了!!

    我现在的想法是:执行时间短的操作用 QTimer,可以直接操作主线程里的界面元素,方便;执行时间长的操作用线程,不会卡死界面
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5519 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 07:35 · PVG 15:35 · LAX 23:35 · JFK 02:35
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.