PDA

View Full Version : Yet another another multithreading problem!



nimaweb
11th August 2009, 01:04
Hi,
Even if you're not familiar with python, I don't think you would have any trouble understanding this code:


from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
from time import sleep

class Thread(QThread):
def __init__(self, parent):
QThread.__init__(self, parent)
self.lock = QReadWriteLock()
def run(self):
self.stopped = False
self.timer = QTimer()
self.connect(self.timer, SIGNAL("timeout()"), self.timeout)
self.timer.start(200)
self.exec_()
def stop(self):
with QWriteLocker(self.lock):
self.stopped = True
def timeout(self):
with QReadLocker(self.lock):
if not self.stopped:
sleep(0.4) #400 miliseconds
#Here's actually a piece of code which I can't estimate
#deterministically how long it takes.
else:
self.timer.stop()
class Form(QDialog):
def __init__(self, parent = None):
QDialog.__init__(self, parent)
self.resize(352, 288)
self.thread = Thread(self)
self.thread.start()
def closeEvent(self, event):
self.thread.stop()
self.thread.quit()
event.accept()

The problem is that, even the time-consuming procedure is placed in a thread other than the main (GUI) thread, the program will be frozen after 3-4 seconds in runtime and then you have to go straight to the task manager in order to close it!
As you might have guessed, there's something wrong in this code:


~~> self.timer.start(200)
################
~~> sleep(0.4)

Yeah, the timeout method takes at least 400 milliseconds to run, while we set the interval to 200 milliseconds. Well, I'm not a masochistic kind of person! Actually there is a snippet of code in the timeout method which does the following tasks:


1- Acquiring a frame from a web cam through OpenCV
2- Invoking face detection procedure
3- Calling a C-implemented function to convert IplImage to a format suitable for QImage.

This operation should be run periodically. I can't guess the time it needs to accomplish these tasks while it depends on many factors, like system load and the type of platform. And it may take longer than the period I set as the interval of QTimer.
Yeah I know that I should take another approach, but why the operation which is running in the thread, affects the main thread? Why does it slow down the main thread? And if it was supposed to behave so, what's the point of having a thread?!
Oh, btw, The following is part of 'Programming with Qt' by Matthias Kalle Dalheimer which stands that it's not a good idea to instantiate the QTimer class in any thread other than the main thread which I've found very common among Qt programmers:
http://books.google.com/books?id=1-QBisAaNM4C&lpg=PA344&dq=qthread&pg=PA348#v=onepage&q=&f=false

* Timers and their events always belong in the GUI thread.
* Never create any objects of subclasses of QWidget, QTimer and QSocketNotifier in anything other than the main thread – not even when Qt is locked. Some Operating Systems will allocate the memory for these objects in a way that makes them inaccessible from the other threads.
Sorry for my bad english ;)

caduel
11th August 2009, 08:35
From QTimer's docs (Qt 4.5.2)

In multithreaded applications, you can use QTimer in any thread that has an event loop. To start an event loop from a non-GUI thread, use QThread::exec(). Qt uses the timer's thread affinity to determine which thread will emit the timeout() signal. Because of this, you must start and stop the timer in its thread; it is not possible to start a timer from another thread.
maybe that book is on Qt3?

anyway, just as in the gui thread, if your long running job is running, the threads event loop does not get to process events

nimaweb
11th August 2009, 11:11
From QTimer's docs (Qt 4.5.2)

maybe that book is on Qt3?

anyway, just as in the gui thread, if your long running job is running, the threads event loop does not get to process events

Yeah, you are right. That book covers Qt3. That was very inconsiderate of me. :rolleyes:
But how do you suggest I do this without needing to have a QTimer. I want my timeout method to be called periodically and I can't determine an appropriate value for the QTimer's interval to have a well-behaved thread.
Maybe I shouldn't call QThread::exec() at all. I think I can have a 'forever' loop in the run() method of the thread which calls the sleep() function after every single frame it captures. What do you think?

caduel
11th August 2009, 11:20
lots of possibilities...

Maybe I'd make a slot grabFrame() in the thread class, that slot could emit the grabbed frame by a signal.
Each grabbing could trigger a singleshot timer to trigger the next frame to be captured after some delay.
This design would also feature an event loop.
(An advantage would be that, using signal/slot to communicate with the thread) you would not need to worry about how to pass grabbed frames.)

An altogether different (maybe easier way) would be to do the grabbing with QFuture (read up on Qt Concurrent) and handle the timer class in the gui thread.

HTH

nimaweb
11th August 2009, 14:18
QFuture is not implemented in PyQt. I think i'll stick with the processing-each-frame-triggers-another-frame-processing-after-some-delay idea.
Is there anyone familiar with python and it's GIL(Global Interpreter Lock)? Do you think that it would be related to my problem? Does the code above act similar when it's implemented in C++? Is it wrong to ask such questions here? :o

nimaweb
11th August 2009, 14:56
I forgot to tell you that I've modified the thread and it works just fine :) (thanks to caduel):

class Thread(QThread):
def __init__(self, parent):
QThread.__init__(self, parent)
self.lock = QReadWriteLock()
def run(self):
self.stopped = False
while True:
with QReadLocker(self.lock):
if not self.stopped:
print "Hi there!"
#Here's actually a piece of code which I can't estimate
#deterministically how long it takes.
sleep(0.4) #delay
else:
break
def stop(self):
with QWriteLocker(self.lock):
self.stopped = True
You can dynamically increase or decrease the delay depending on the execution time of the procedure.

caduel
11th August 2009, 15:45
btw, guarding a bool with a mutex in not really necessary:
if it is false, ok; if you set it to true, while the other thread is reading it... what can happen?
either it gets false (which is absolutely ok...you can't influence timing anyway), or it gets the new value... (The idea is that (at least in C++) you can probably assume that a bool changes atomically...)