Tkinter sometimes has a busy-wait main loop (more or less)
One of my always-running programs is a little Tkinter-based Python
app. For a while now I've noticed that it was accumulating a surprising
amount of CPU time and would sometimes show up on top
as active,
despite me not doing anything with it. Today I finally got around to
figuring out why, and the answer is that Tkinter's main loop effectively
busy-waits in some Python environments.
The standard way for Tkinter-based programs to operate is to make all of
the Tkinter calls to set up your windows and then call root.mainloop()
(where root
is the object you got from calling Tk()
). If your version
of CPython is built with thread support (the common case) and your version
of Tcl is built without threads enabled (this depends), the inner core
of mainloop()
looks more or less like this:
while keep_running: Tcl_DoOneEvent(TCL_DONT_WAIT); if no event processed: sleep(20 msec); check_for_signals();
(One reason for this loop is to handle Unix signals promptly; the full code also has some thread-related locking stuff. See Tkapp_MainLoop() in Modules/_tkinter.c in the CPython source for the gory details.)
The net effect is that when your Tkinter-based program is sitting idle, it wakes up every 20 milliseconds to spin around doing nothing; over time this can add up to visible and even significant CPU usage.
(In theory the sleep interval can be increased; in practice you can't turn this up without lowering the responsiveness of your application, because your program won't process new events until it wakes up from the sleep (and it's going to wind up in the sleep fairly often). If you really want to touch this, see Tkinter._tkinter.setbusywaitinterval().)
My solution was to replace my use of root.mainloop()
with the
following code:
global exit_mainloop while not exit_mainloop: root.dooneevent(0)
In Tkinter-related code where I was previously calling .quit()
on
Tkinter objects to get the application to quit, I instead set the
exit_mainloop
global to 1; this is more or less what .quit()
does
anyways. This is probably somewhat less efficient if your application is
active all the time (since you now go through (more) interpreted Python
code for every event), but is much more efficient if your application
spends most of its time idle; strace
now shows my program sitting
there doing nothing instead of constantly twitching around in system
calls.
The one caution with this approach is that .mainloop()
also exits if
there are no Tk main windows left. If this matters for your application,
you'll need to keep track of this yourself and set exit_mainloop
appropriately.
Sidebar: how to see if your Tcl is built with threads enabled
Run tclsh
and give it the command 'parray tcl_platform
'. If it
has a threaded
entry, your platform built Tcl with --enable-threads
(this information is from here). It appears
that Fedora 11 and FreeBSD build Tcl without threads while Debian, Ubuntu
and RHEL 5 build it with threads.
(I have no idea why Fedora and RHEL are different here.)
Comments on this page:
|
|