python 初级Python线程问题

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/482263/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 20:12:53  来源:igfitidea点击:

Beginner-level Python threading problems

pythonmultithreadingpygtk

提问by crashsystems

As someone new to GUI development in Python (with pyGTK), I've just started learning about threading. To test out my skills, I've written a simple little GTK interface with a start/stop button. The goal is that when it is clicked, a thread starts that quickly increments a number in the text box, while keeping the GUI responsive.

作为 Python GUI 开发的新手(使用 pyGTK),我刚刚开始学习线程。为了测试我的技能,我编写了一个带有开始/停止按钮的简单 GTK 小界面。目标是当它被点击时,一个线程开始在文本框中快速增加一个数字,同时保持 GUI 响应。

I've got the GUI working just fine, but am having problems with the threading. It is probably a simple problem, but my mind is about fried for the day. Below I have pasted first the trackback from the Python interpreter, followed by the code. You can go to http://drop.io/pxgr5idto download it. I'm using bzr for revision control, so if you want to make a modification and re-drop it, please commit the changes. I'm also pasting the code at http://dpaste.com/113388/because it can have line numbers, and this markdown stuff is giving me a headache.

我的 GUI 工作得很好,但线程有问题。这可能是一个简单的问题,但我的想法是关于这一天的煎炸。下面我首先粘贴了 Python 解释器的引用,然后是代码。你可以去http://drop.io/pxgr5id下载。我正在使用 bzr 进行版本控制,因此如果您想进行修改并重新删除它,请提交更改。我还在http://dpaste.com/113388/ 上粘贴代码,因为它可以有行号,而这种降价的东西让我很头疼。

Update 27 January, 15:52 EST: Slightly updated code can be found here: http://drop.io/threagui/asset/thread-gui-rev3-tar-gz

美国东部时间 1 月 27 日 15:52 更新:可在此处找到稍微更新的代码:http: //drop.io/threagui/asset/thread-gui-rev3-tar-gz

Traceback

追溯

crashsystems@crashsystems-laptop:~/Desktop/thread-gui$ python threadgui.pybtnStartStop clicked
Traceback (most recent call last):
  File "threadgui.py", line 39, in on_btnStartStop_clicked
    self.thread.stop()
  File "threadgui.py", line 20, in stop
    self.join()
  File "/usr/lib/python2.5/threading.py", line 583, in join
    raise RuntimeError("cannot join thread before it is started")
RuntimeError: cannot join thread before it is started
btnStartStop clicked
threadStop = 1
btnStartStop clicked
threadStop = 0
btnStartStop clicked
Traceback (most recent call last):
  File "threadgui.py", line 36, in on_btnStartStop_clicked
    self.thread.start()
  File "/usr/lib/python2.5/threading.py", line 434, in start
    raise RuntimeError("thread already started")
RuntimeError: thread already started
btnExit clicked
exit() called

Code

代码

#!/usr/bin/bash
import gtk, threading

class ThreadLooper (threading.Thread):
    def __init__ (self, sleep_interval, function, args=[], kwargs={}):
        threading.Thread.__init__(self)
        self.sleep_interval = sleep_interval
        self.function = function
        self.args = args
        self.kwargs = kwargs
        self.finished = threading.Event()

    def stop (self):
        self.finished.set()
        self.join()

    def run (self):
        while not self.finished.isSet():
            self.finished.wait(self.sleep_interval)
            self.function(*self.args, **self.kwargs)

class ThreadGUI:
    # Define signals
    def on_btnStartStop_clicked(self, widget, data=None):
        print "btnStartStop clicked"
        if(self.threadStop == 0):
            self.threadStop = 1
            self.thread.start()
        else:
            self.threadStop = 0
            self.thread.stop()
        print "threadStop = " + str(self.threadStop)

    def on_btnMessageBox_clicked(self, widget, data=None):
        print "btnMessageBox clicked"
        self.lblMessage.set_text("This is a message!")
        self.msgBox.show()

    def on_btnExit_clicked(self, widget, data=None):
        print "btnExit clicked"
        self.exit()

    def on_btnOk_clicked(self, widget, data=None):
        print "btnOk clicked"
        self.msgBox.hide()

    def on_mainWindow_destroy(self, widget, data=None):
        print "mainWindow destroyed!"
        self.exit()

    def exit(self):
        print "exit() called"
        self.threadStop = 1
        gtk.main_quit()

    def threadLoop(self):
        # This will run in a thread
        self.txtThreadView.set_text(str(self.threadCount))
        print "hello world"
        self.threadCount += 1

    def __init__(self):
        # Connect to the xml GUI file
        builder = gtk.Builder()
        builder.add_from_file("threadgui.xml")

        # Connect to GUI widgets
        self.mainWindow = builder.get_object("mainWindow")

        self.txtThreadView = builder.get_object("txtThreadView")
        self.btnStartStop = builder.get_object("btnStartStop")
        self.msgBox = builder.get_object("msgBox")
        self.btnMessageBox = builder.get_object("btnMessageBox")
        self.btnExit = builder.get_object("btnExit")
        self.lblMessage  = builder.get_object("lblMessage")
        self.btnOk = builder.get_object("btnOk")

        # Connect the signals
        builder.connect_signals(self)

        # This global will be used for signaling the thread to stop.
        self.threadStop = 1

        # The thread
        self.thread = ThreadLooper(0.1, self.threadLoop, (1,0,-1))
        self.threadCounter = 0

if __name__ == "__main__":
    # Start GUI instance
    GUI = ThreadGUI()
    GUI.mainWindow.show()
    gtk.main()

回答by zgoda

Threading with PyGTK is bit tricky if you want to do it right. Basically, you should not update GUI from within any other thread than main thread (common limitation in GUI libs). Usually this is done in PyGTK using mechanism of queued messages (for communication between workers and GUI) which are read periodically using timeout function. Once I had a presentation on my local LUG on this topic, you can grab example code for this presentation from Google Code repository. Have a look at MainWindowclass in forms/frmmain.py, specially for method _pulse()and what is done in on_entry_activate()(thread is started there plus the idle timer is created).

如果你想正确地使用 PyGTK 线程有点棘手。基本上,您不应从主线程以外的任何其他线程中更新 GUI(GUI 库中的常见限制)。通常这是在 PyGTK 中使用排队消息机制(用于工作人员和 GUI 之间的通信)完成的,这些消息使用超时功能定期读取。一旦我在本地 LUG 上就该主题进行了演示,您就可以从Google 代码存储库中获取此演示文稿的示例代码。看看MainWindowin中的类forms/frmmain.py,特别是方法_pulse()和其中所做的事情on_entry_activate()(线程在那里启动,并创建了空闲计时器)。

def on_entry_activate(self, entry):
    text = entry.get_text().strip()
    if text:
        store = entry.get_completion().get_model()
        if text not in [row[0] for row in store]:
            store.append((text, ))
        thread = threads.RecommendationsFetcher(text, self.queue)# <- 1
        self.idle_timer = gobject.idle_add(self._pulse)# <- 2
        tv_results = self.widgets.get_widget('tv_results')
        model = tv_results.get_model()
        model.clear()
        thread.setDaemon(True)# <- 3
        progress_update = self.widgets.get_widget('progress_update')
        progress_update.show()
        thread.start()# <- 4

This way, application updates GUI when is "idle" (by GTK means) causing no freezes.

这样,应用程序在“空闲”(通过 GTK 方式)时更新 GUI,不会导致冻结。

  • 1: create thread
  • 2: create idle timer
  • 3: daemonize thread so the app can be closed without waiting for thread completion
  • 4: start thread
  • 1:创建线程
  • 2:创建空闲定时器
  • 3:守护线程,这样应用程序就可以在不等待线程完成的情况下关闭
  • 4:启动线程

回答by habnabit

Generally it's better to avoid threads when you can. It's very difficult to write a threaded application correctly, and even more difficult to know you got it right. Since you're writing a GUI application, it's easier for you to visualize how to do so, since you already have to write your application within an asynchronous framework.

通常,最好尽可能避免使用线程。正确编写线程应用程序非常困难,更难知道您是否做对了。由于您正在编写 GUI 应用程序,因此更容易想象如何执行此操作,因为您已经必须在异步框架内编写应用程序。

The important thing to realize is that a GUI application is doing a whole lot of nothing. It spends most of its time waiting for the OS to tell it that something has happened. You can do a lot of stuff in this idle time as long as you know how to write long-running code so it doesn't block.

需要意识到的重要一点是,GUI 应用程序实际上什么都不做。它大部分时间都在等待操作系统告诉它发生了一些事情。只要您知道如何编写长时间运行的代码使其不会阻塞,您就可以在这段空闲时间做很多事情。

You can solve your original problem by using a timeout; telling your GUI framework to call back some function after a delay, and then resetting that delay or starting another delayed call.

您可以通过使用超时来解决您原来的问题;告诉您的 GUI 框架在延迟后回调某个函数,然后重置该延迟或启动另一个延迟调用。

Another common question is how to communicate over the network in a GUI application. Network apps are like GUI apps in that they do a whole lot of waiting. Using a network IO framework (like Twisted) makes it easy to have both parts of your application wait cooperatively instead of competitively, and again alleviates the need for extra threads.

另一个常见问题是如何在 GUI 应用程序中通过网络进行通信。网络应用程序类似于 GUI 应用程序,因为它们需要大量等待。使用网络 IO 框架(如Twisted)可以轻松地让应用程序的两个部分协同等待而不是竞争,并再次减轻对额外线程的需求。

Long-running calculations can be written iteratively instead of synchronously, and you can do your processing while the GUI is idle. You can use a generator to do this quite easily in python.

可以迭代地而不是同步地编写长时间运行的计算,并且您可以在 GUI 空闲时进行处理。您可以使用生成器在 python 中轻松完成此操作。

def long_calculation(param, callback):
    result = None
    while True:
        result = calculate_next_part(param, result)
        if calculation_is_done(result):
            break
        else:
            yield
    callback(result)

Calling long_calculationwill give you a generator object, and calling .next()on the generator object will run the generator until it reaches either yieldor return. You would just tell the GUI framework to call long_calculation(some_param, some_callback).nextwhen it has time, and eventually your callback will be called with the result.

调用long_calculation会给你一个生成器对象,调用.next()生成器对象将运行生成器,直到它到达yieldreturn。您只需告诉 GUI 框架long_calculation(some_param, some_callback).next在有时间时调用,最终将使用结果调用您的回调。

I don't know GTK very well, so I can't tell you which gobject functions you should be calling. With this explanation, though, you should be able to find the necessary functions in the documentation, or at worst, ask on a relevant IRC channel.

我不太了解 GTK,所以我不能告诉你应该调用哪些 gobject 函数。但是,有了这个解释,您应该能够在文档中找到必要的功能,或者最坏的情况是,在相关的 IRC 频道上询问。

Unfortunately there is no good general-case answer. If you clarify with exactly what you're trying to do, it would be easier to explain why you don't need threads in that situation.

不幸的是,没有好的一般情况答案。如果您明确说明您要做什么,那么解释为什么在这种情况下不需要线程会更容易。

回答by Charles Duffy

You can't restart a stopped thread object; don't try. Instead, create a new instance of the object if you want to restart it after it's truly stopped and joined.

您不能重新启动已停止的线程对象;不要尝试。相反,如果您想在它真正停止并加入后重新启动它,请创建该对象的新实例。

回答by Ed.

I've played with different tools to help clean up the work with threads, idle processing, etc.

我使用过不同的工具来帮助清理线程、空闲处理等工作。

make_idle is a function decorator that allows you to run a task in the background cooperatively. This is a good middle ground between something short enough to run once in the UI thread and not affect the responsiveness of the app and doing a full out thread in special synchronization. Inside the decorated function you use "yield" to hand the processing back over to the GUI so it can remain responsive and the next time the UI is idle it will pick up in your function where you left off. So to get this started you just call idle_add to the decorated function.

make_idle 是一个函数装饰器,允许你在后台协同运行任务。这是一个很好的中间立场,它足够短,可以在 UI 线程中运行一次,并且不会影响应用程序的响应性,也可以在特殊同步中执行完整的线程。在装饰函数中,您使用“yield”将处理交回给 GUI,这样它就可以保持响应,下次 UI 空闲时,它将在您离开的函数中恢复。因此,要开始此操作,您只需将 idle_add 调用到装饰函数即可。

def make_idler(func):
    """
    Decorator that makes a generator-function into a function that will
continue execution on next call
    """
    a = []

    @functools.wraps(func)
    def decorated_func(*args, **kwds):
        if not a:
            a.append(func(*args, **kwds))
        try:
            a[0].next()
            return True
        except StopIteration:
            del a[:]
            return False

    return decorated_func

If you need to do a bit more processing, you can use a context manager to lock the UI thread whenever needed to help make the code a bit safer

如果你需要做更多的处理,你可以使用上下文管理器在需要时锁定 UI 线程,以帮助使代码更安全

@contextlib.contextmanager
def gtk_critical_section():
    gtk.gdk.threads_enter()
    try:
        yield
    finally:
        gtk.gdk.threads_leave()

with that you can just

这样你就可以

with gtk_critical_section():
    ... processing ...

I have not finished with it yet, but in combining doing things purely in idle and purely in a thread, I have a decorator (not tested yet so not posted) that you can tell it whether the next section after the yield is to be run in the UI's idle time or in a thread. This would allow one to do some setup in the UI thread, switch to a new thread for doing background stuff, and then switch over to the UI's idle time to do cleanup, minimizing the need for locks.

我还没有完成,但是结合纯粹在空闲和纯粹在线程中做事,我有一个装饰器(尚未测试所以未发布),您可以告诉它是否要运行yield之后的下一部分在 UI 的空闲时间或线程中。这将允许人们在 UI 线程中进行一些设置,切换到一个新线程来执行后台操作,然后切换到 UI 的空闲时间进行清理,从而最大限度地减少对锁的需求。

回答by Sebastian Rittau

I haven't looked in detail on your code. But I see two solutions to your problem:

我没有详细查看您的代码。但我看到您的问题有两种解决方案:

Don't use threads at all. Instead use a timeout, like this:

根本不要使用线程。而是使用超时,如下所示:

import gobject

i = 0
def do_print():
    global i
    print i
    i += 1
    if i == 10:
        main_loop.quit()
        return False
    return True

main_loop = gobject.MainLoop()
gobject.timeout_add(250, do_print)
main_loop.run()

When using threads, you must make sure that your GUI code is only called from one thread at the same time by guarding it like this:

使用线程时,您必须确保您的 GUI 代码只在同一时间从一个线程调用,方法是像这样保护它:

import threading
import time

import gobject
import gtk

gtk.gdk.threads_init()

def run_thread():
    for i in xrange(10):
        time.sleep(0.25)
        gtk.gdk.threads_enter()
        # update the view here
        gtk.gdk.threads_leave()
    gtk.gdk.threads_enter()
    main_loop.quit()
    gtk.gdk.threads_leave()

t = threading.Thread(target=run_thread)
t.start()
main_loop = gobject.MainLoop()
main_loop.run()