在pyside中使用Qt线程

时间:2015-03-13 08:32:23

标签: python pyside

我不理解found here下方示例代码的输出。当前线程和工作线程具有相同的地址,这怎么可能?

from PySide import QtCore

class Master(QtCore.QObject):
    command = QtCore.Signal(str)
    def __init__(self):
        QtCore.QObject.__init__(self)

class Worker(QtCore.QObject):
    def __init__(self):
        QtCore.QObject.__init__(self)
    def do_something(self, text):
        print('in thread {} message {}'.format(QtCore.QThread.currentThread(), text))

if __name__ == '__main__':
    app = QtCore.QCoreApplication([])
    print(QtCore.QThread.currentThread())
    # give us a thread and start it
    thread = QtCore.QThread()
    thread.start()
    print(thread)

    # create a worker and move it to our extra thread
    worker = Worker()
    worker.moveToThread(thread)

    # create a master object and connect it to the worker
    master = Master()
    master.command.connect(worker.do_something)

    # call a method of the worker directly (will be executed in the actual thread)
    worker.do_something('in main thread')

    # communicate via signals, will execute the method now in the extra thread
    master.command.emit('in worker thread')

    # start the application and kill it after 1 second
    QtCore.QTimer.singleShot(1000, app.quit)
    app.exec_()

    # don't forget to terminate the extra thread
    thread.quit()

输出:

<PySide.QtCore.QThread object at 0x0000000002537688>
<PySide.QtCore.QThread object at 0x0000000002537688>
in thread <PySide.QtCore.QThread object at 0x00000000025377C8> message in main thread
in thread <PySide.QtCore.QThread object at 0x0000000002537688> message in worker thread

2 个答案:

答案 0 :(得分:8)

没有“包装器被重用”,只是旧的包装器对象(已删除)碰巧与新的包装器对象驻留在同一个内存地址。

PySide使用shiboken库将QObject包装到Python对象中。 shiboken documentation说明如下:

  

任何python绑定,基于项目的绑定都使用引用计数来处理包装器对象的生命周期(包含C ++对象的Python对象,不要与包装的C ++对象混淆)。当引用计数达到零时,Python垃圾收集器删除包装器并尝试删除包装的实例,但有时包装的C ++对象已被删除,或者C ++对象可能不会被释放Python包装器超出范围并死掉,因为C ++已经在处理被包装的实例。

除此之外,CPython malloc实现(以及许多其他常见的mallocs)often reuse the memory address that belonged to just deleted object随后创建了对象,这导致了常见的陷阱:

>>> {} is {}
False
>>> id({}) == id({})
True

因此,总是使得活对象的地址/ id是不同的;曾经生活过,然后死去的物品的地址只是好奇心。

在第一种情况下,两个字典的引用都被保留了,所以从技术上讲,它们id是截然不同的,而在第二种情况下左手边的字典在左手后不久就被删除了 id被调用,之后才创建右手词典,并在相同的内存地址分配。


然而,它有点复杂了。当你调用currentThread()时,如果旧的包装器仍处于活动状态(意味着它在python端引用了它),它将为QThread 返回一个新的包装器对象,除了 ,在这种情况下,返回旧的包装器:因此,

QtCore.QThread.currentThread() is QtCore.QThread.currentThread() 

永远是真的!


现在,您在

中获得2个地址的原因
in thread <PySide.QtCore.QThread object at 0x00000000028EB888> message in main thread
in thread <PySide.QtCore.QThread object at 0x00000000028EB8C8> message in worker thread

再一次不一定与包装纸“不被重复使用”无关,或者它们被保持;相反,在它们之间创建的其他一些对象可能会发生同样的效果,它们的引用被保留 - 也就是说,不应该认为包装对象保持活着。

但是,如果你持有对2个不同QThread个对象的引用,那么它们的id()将在其生命周期中不同。


总而言之,你不应该依赖shiboken包装器对象的地址做任何有用的事情;相反,如果你自己在Python代码中持有引用,那么is测试是有用的


请注意__str__的{​​{1}}将打印QObject(如果已设置);因此,您可以通过以下方式轻松了解输出:

objectName

并且更清晰app = QtCore.QCoreApplication([]) QtCore.QThread.currentThread().setObjectName('main thread') thread = QtCore.QThread() thread.start() thread.setObjectName('worker thread')

print
消息将是:

print('in thread: "{}" - message: "{}"'
    .format(QtCore.QThread.currentThread().objectName(), text))

使用fetch the address of the underlying C++ object模块还有shiboken的方法;这对于调试其他东西也很方便;唉,默认情况下,Ubuntu 14.10上根本没有安装Python库,也没有安装任何与shiboken相关的软件包;经过无数次的回答,我终于设法安装了它。

结果比我想象的更可怕:

in thread: "main thread" - message: "in main thread"
in thread: "worker thread" - message: "in worker thread"

打印:

app = QtCore.QCoreApplication([])
print(QtCore.QCoreApplication.instance().thread())
obj = QtCore.QObject()
print(obj.thread())
del obj
print(QtCore.QThread.currentThread())

没错,在删除QObject后,线程包装器的地址发生了变化!因此,让我们从<PySide.QtCore.QThread object at 0x7fb4a1149cc8> <PySide.QtCore.QThread object at 0x7fb4a1149cc8> <PySide.QtCore.QThread object at 0x7fb4a1149d08> 导入dump

Shiboken.shiboken

输出

from Shiboken.shiboken import dump

def info(obj):
    print(id(obj))
    print(dump(obj))

app = QtCore.QCoreApplication([])
info(QtCore.QCoreApplication.instance().thread())

obj = QtCore.QObject()
info(obj.thread())    
del obj

info(QtCore.QCoreApplication.instance().thread())

也就是说,shiboken生成 last 对象,其140323585370568 C++ address....... PySide.QtCore.QThread/0xe3d880 hasOwnership...... 0 containsCppWrapper 0 validCppObject.... 1 wasCreatedByPython 0 parent............ <PySide.QtCore.QCoreApplication object at 0x7f9fa175a948> 140323585370568 C++ address....... PySide.QtCore.QThread/0xe3d880 hasOwnership...... 0 containsCppWrapper 0 validCppObject.... 1 wasCreatedByPython 0 parent............ <PySide.QtCore.QObject object at 0x7f9fa175aa48> 140323585370696 C++ address....... PySide.QtCore.QThread/0xe3d880 hasOwnership...... 0 containsCppWrapper 0 validCppObject.... 1 wasCreatedByPython 0 方法我们称之为线程的 parent 。每当删除对象时(例如,最后调用thread()的对象),包装器也会被删除。确实非常可疑的行为;我不确定它是否是一个bug,但这证明了信任包装对象的.thread()根本不可信任。

答案 1 :(得分:2)

我认为在此上下文中使用QThread.currentThread()并期望获得有意义的结果是无效的。

正如我所理解的那样,QThread不是一个线程,而是一个线程的包装器,我们看到的只是为工作线程重用的包装器。 如果我将QThread.currentThread()替换为QCoreApplication.instance().thread(),我会得到正确的输出:

<PySide.QtCore.QThread object at 0x00000000029D98C8>
<PySide.QtCore.QThread object at 0x00000000029D9908>
in thread <PySide.QtCore.QThread object at 0x00000000029D98C8> message in main thread
in thread <PySide.QtCore.QThread object at 0x00000000029D9908> message in worker thread

这背后的原因非常简单,前一个调用没有保留对QThread包装器的引用,不像QCoreApplication.instance().thread()确实保留了引用。 我们可以通过修改原始样本来验证这一点:

main_thread = QtCore.QThread.currentThread() # holds the reference
print(main_thread)
# give us a thread and start it
thread = QtCore.QThread()
print(thread)

现在输出:

<PySide.QtCore.QThread object at 0x00000000028EB888>
<PySide.QtCore.QThread object at 0x00000000028EB8C8>
in thread <PySide.QtCore.QThread object at 0x00000000028EB888> message in main thread
in thread <PySide.QtCore.QThread object at 0x00000000028EB8C8> message in worker thread

编辑:

通过重复使用包装器,我的意思是存储第一个包装器的内存块被第二个包装器重用,这就是包装器具有相同内存地址的原因。

但是我很感兴趣,这背后的机制是什么。原因是因为Python对小对象使用 PyMalloc (默认情况下从Python 2.3开始启用),但也可能完全是glibc分配器本身的副作用(如果是现代分配器,如jemalloc或tcmalloc)和python使用标志--without-pymalloc)编译:

http://www.evanjones.ca/memoryallocator/