Passing a variable through threaded classes - python

In the test code below,
class1.stop_callback() sets class1.stop = True
therefore class2.stop = True
therefore class3.stop should be True but it isn't.
class1.stop_callback() should stop the program but it doesn't do that.
What am I doing wrong?
You can test the code on repl.it https://repl.it/#bahtsiz_bedevi/classtest
import threading
import time
class Class1(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.stop = False
def stop_callback(self):
self.stop = True
def run(self):
class2 = Class2()
class2.stop = self.stop
class2.start()
while True:
time.sleep(1)
print("{} stop status: {}".format(self.__class__, "True" if self.stop else "False"))
if self.stop:
break
class Class2(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.stop = False
def run(self):
class3 = Class3()
class3.stop = self.stop
while True:
time.sleep(1)
print("{} stop status: {}".format(self.__class__, "True" if self.stop else "False"))
if self.stop:
break
class3.foo()
class Class3:
def __init__(self):
self.stop = False
def foo(self):
while True:
time.sleep(1)
print("{} stop status: {}".format(self.__class__, "True" if self.stop else "False"))
if self.stop:
break
class1 = Class1()
class1.start()
for i in range(10):
time.sleep(1)
class1.stop_callback()

In Python, variables are names for objects. By assigning False to class1.stop and class1.stop to class2.stop, you are assigning False to class2.stop, nothing more.
What you seem to want is a reference to class1.stop instead, however this is not how assignment works in Python. One way to get around this would be to use a list. If you keep the list the same and only change the value at the first index, you can achieve what you want:
stop1 = [False]
stop2 = stop1
assert stop2[0] == False
stop1[0] = True
assert stop2[0] == True

Since Class3 is not a Thread-like class (despite not running in the main thread) you cannot change the value of class3.stop until class3.foo() returns. Since class3.foo() doesn't return until the value of class3.stop changes, there is no way to stop the process and it runs forever.
I would suggest basing Class3 on Thread so that you can call methods on it while it is running. If this is too much overhead, or you will be running it more than once per instance of class2, you could always just define foo and then run it within the Class2.run method.
EDIT: I was going to mention Florian's point, but since - as in his proposed solution - mutable objects do carry across during assignments, I wasn't sure if you had already thought this part through.
Below is revised code; note
the use of threading.Lock to prevent those weird print statements that were happening on the same line
the use of while not self.stop rather than if statements with breaks
the use of threading in Class3
import threading
import time
printLock = threading.Lock()
p = print
def print(*a, **b):
with printLock:
p(*a, **b)
class Class1(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.stop = False
def stopMe(self):
self.stop = True
def run(self):
class2 = Class2()
class2.start()
while not self.stop:
time.sleep(1)
print("{} stop status:{:6}".format(self.__class__, str(self.stop)))
class2.stopMe()
class Class2(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.stop = False
def stopMe(self):
self.stop = True
def run(self):
class3 = Class3()
class3.start()
while not self.stop:
time.sleep(1)
print("{} stop status:{:6}".format(self.__class__, str(self.stop)))
class3.stopMe()
class Class3(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.stop = False
def stopMe(self):
self.stop = True
def run(self):
while not self.stop:
time.sleep(1)
print("{} stop status:{:6}".format(self.__class__, str(self.stop)))
class1 = Class1()
class1.start()
time.sleep(10)
class1.stopMe()

Related

What is the best practice of 'restarting' a thread? [duplicate]

How can I start and stop a thread with my poor thread class?
It is in loop, and I want to restart it again at the beginning of the code. How can I do start-stop-restart-stop-restart?
My class:
import threading
class Concur(threading.Thread):
def __init__(self):
self.stopped = False
threading.Thread.__init__(self)
def run(self):
i = 0
while not self.stopped:
time.sleep(1)
i = i + 1
In the main code, I want:
inst = Concur()
while conditon:
inst.start()
# After some operation
inst.stop()
# Some other operation
You can't actually stop and then restart a thread since you can't call its start() method again after its run() method has terminated. However you can make one pause and then later resume its execution by using a threading.Condition variable to avoid concurrency problems when checking or changing its running state.
threading.Condition objects have an associated threading.Lock object and methods to wait for it to be released and will notify any waiting threads when that occurs. Here's an example derived from the code in your question which shows this being done. In the example code I've made the Condition variable a part of Thread subclass instances to better encapsulate the implementation and avoid needing to introduce additional global variables:
from __future__ import print_function
import threading
import time
class Concur(threading.Thread):
def __init__(self):
super(Concur, self).__init__()
self.iterations = 0
self.daemon = True # Allow main to exit even if still running.
self.paused = True # Start out paused.
self.state = threading.Condition()
def run(self):
self.resume()
while True:
with self.state:
if self.paused:
self.state.wait() # Block execution until notified.
# Do stuff...
time.sleep(.1)
self.iterations += 1
def pause(self):
with self.state:
self.paused = True # Block self.
def resume(self):
with self.state:
self.paused = False
self.state.notify() # Unblock self if waiting.
class Stopwatch(object):
""" Simple class to measure elapsed times. """
def start(self):
""" Establish reference point for elapsed time measurements. """
self.start_time = time.time()
return self
#property
def elapsed_time(self):
""" Seconds since started. """
try:
return time.time() - self.start_time
except AttributeError: # Wasn't explicitly started.
self.start_time = time.time()
return 0
MAX_RUN_TIME = 5 # Seconds.
concur = Concur()
stopwatch = Stopwatch()
print('Running for {} seconds...'.format(MAX_RUN_TIME))
concur.start()
while stopwatch.elapsed_time < MAX_RUN_TIME:
concur.resume()
# Can also do other concurrent operations here...
concur.pause()
# Do some other stuff...
# Show Concur thread executed.
print('concur.iterations: {}'.format(concur.iterations))
This is David Heffernan's idea fleshed-out. The example below runs for 1 second, then stops for 1 second, then runs for 1 second, and so on.
import time
import threading
import datetime as DT
import logging
logger = logging.getLogger(__name__)
def worker(cond):
i = 0
while True:
with cond:
cond.wait()
logger.info(i)
time.sleep(0.01)
i += 1
logging.basicConfig(level=logging.DEBUG,
format='[%(asctime)s %(threadName)s] %(message)s',
datefmt='%H:%M:%S')
cond = threading.Condition()
t = threading.Thread(target=worker, args=(cond, ))
t.daemon = True
t.start()
start = DT.datetime.now()
while True:
now = DT.datetime.now()
if (now-start).total_seconds() > 60: break
if now.second % 2:
with cond:
cond.notify()
The implementation of stop() would look like this:
def stop(self):
self.stopped = True
If you want to restart, then you can just create a new instance and start that.
while conditon:
inst = Concur()
inst.start()
#after some operation
inst.stop()
#some other operation
The documentation for Thread makes it clear that the start() method can only be called once for each instance of the class.
If you want to pause and resume a thread, then you'll need to use a condition variable.

Simple Python Pause/Resume

I was trying to pause/resume a task using hotkey, wrote the program bellow which is working fine while hitting Pause hotkey, but resume is not working. I guess I did some logical errors and need your expert advice to overcome that. Here is the script I wrote
import keyboard
class Test:
def __init__(self):
self.run = True
keyboard.add_hotkey("ctrl+alt+p", self.set_run)
keyboard.add_hotkey("ctrl+alt+r", self.set_run_r)
def set_run(self):
self.run = False
def set_run_r(self):
self.run = True
def start(self):
val = 1
while self.run:
val += 1
print("running ", val)
keyboard.wait("esc")
Test().start()
Try this
import keyboard
import sys
class Test:
def __init__(self):
self.val=1
self.run = True
keyboard.add_hotkey("ctrl+alt+p", self.set_run)
keyboard.add_hotkey("ctrl+alt+r", self.set_run_r)
def set_run(self):
self.run = False
def set_run_r(self):
self.run = True
def start(self):
self.val += 1
print(self.val)
return
test= Test()
try:
while True:
if test.run:
test.start()
else:
pass
except KeyboardInterrupt:
sys.exit()

Why doesn't SIGVTALRM trigger inside time.sleep()?

I'm trying to use SIGVTALRM to snapshot profile my Python code, but it doesn't seem to be firing inside blocking operations like time.sleep() and socket operations.
Why is that? And is there any way to address that, so I can collect samples while I'm inside blocking operations?
I've also tried using ITIMER_PROF/SIGPROF and ITIMER_REAL/SIGALRM and both seem to produce similar results.
The code I'm testing with follows, and the output is something like:
$ python profiler-test.py
<module>(__main__:1);test_sampling_profiler(__main__:53): 1
<module>(__main__:1);test_sampling_profiler(__main__:53);busyloop(__main__:48): 1509
Note that the timesleep function isn't shown at all.
Test code:
import time
import signal
import collections
class SamplingProfiler(object):
def __init__(self, interval=0.001, logger=None):
self.interval = interval
self.running = False
self.counter = collections.Counter()
def _sample(self, signum, frame):
if not self.running:
return
stack = []
while frame is not None:
formatted_frame = "%s(%s:%s)" %(
frame.f_code.co_name,
frame.f_globals.get('__name__'),
frame.f_code.co_firstlineno,
)
stack.append(formatted_frame)
frame = frame.f_back
formatted_stack = ';'.join(reversed(stack))
self.counter[formatted_stack] += 1
signal.setitimer(signal.ITIMER_VIRTUAL, self.interval, 0)
def start(self):
if self.running:
return
signal.signal(signal.SIGVTALRM, self._sample)
signal.setitimer(signal.ITIMER_VIRTUAL, self.interval, 0)
self.running = True
def stop(self):
if not self.running:
return
self.running = False
signal.signal(signal.SIGVTALRM, signal.SIG_IGN)
def flush(self):
res = self.counter
self.counter = collections.Counter()
return res
def busyloop():
start = time.time()
while time.time() - start < 5:
pass
def timesleep():
time.sleep(5)
def test_sampling_profiler():
p = SamplingProfiler()
p.start()
busyloop()
timesleep()
p.stop()
print "\n".join("%s: %s" %x for x in sorted(p.flush().items()))
if __name__ == "__main__":
test_sampling_profiler()
Not sure about why time.sleep works that way (could it be using SIGALRM for itself to know when to resume?) but Popen.wait does not block signals so worst case you can call out to OS sleep.
Another approach is to use a separate thread to trigger the sampling:
import sys
import threading
import time
import collections
class SamplingProfiler(object):
def __init__(self, interval=0.001):
self.interval = interval
self.running = False
self.counter = collections.Counter()
self.thread = threading.Thread(target=self._sample)
def _sample(self):
while self.running:
next_wakeup_time = time.time() + self.interval
for thread_id, frame in sys._current_frames().items():
if thread_id == self.thread.ident:
continue
stack = []
while frame is not None:
formatted_frame = "%s(%s:%s)" % (
frame.f_code.co_name,
frame.f_globals.get('__name__'),
frame.f_code.co_firstlineno,
)
stack.append(formatted_frame)
frame = frame.f_back
formatted_stack = ';'.join(reversed(stack))
self.counter[formatted_stack] += 1
sleep_time = next_wakeup_time - time.time()
if sleep_time > 0:
time.sleep(sleep_time)
def start(self):
if self.running:
return
self.running = True
self.thread.start()
def stop(self):
if not self.running:
return
self.running = False
def flush(self):
res = self.counter
self.counter = collections.Counter()
return res
def busyloop():
start = time.time()
while time.time() - start < 5:
pass
def timesleep():
time.sleep(5)
def test_sampling_profiler():
p = SamplingProfiler()
p.start()
busyloop()
timesleep()
p.stop()
print "\n".join("%s: %s" %x for x in sorted(p.flush().items()))
if __name__ == "__main__":
test_sampling_profiler()
When doing it this way the result is:
$ python profiler-test.py
<module>(__main__:1);test_sampling_profiler(__main__:62);busyloop(__main__:54): 2875
<module>(__main__:1);test_sampling_profiler(__main__:62);start(__main__:37);start(threading:717);wait(threading:597);wait(threading:309): 1
<module>(__main__:1);test_sampling_profiler(__main__:62);timesleep(__main__:59): 4280
Still not totally fair, but better than no samples at all during sleep.
The absence of SIGVTALRM during a sleep() doesn't surprise me, since ITIMER_VIRTUAL "runs only when the process is executing."
(As an aside, CPython on non-Windows platforms implements time.sleep() in terms of select().)
With a plain SIGALRM, however, I expect a signal interruption and indeed I observe one:
<module>(__main__:1);test_sampling_profiler(__main__:62);busyloop(__main__:54): 4914
<module>(__main__:1);test_sampling_profiler(__main__:62);timesleep(__main__:59): 1
I changed the code somewhat, but you get the idea:
class SamplingProfiler(object):
TimerSigs = {
signal.ITIMER_PROF : signal.SIGPROF,
signal.ITIMER_REAL : signal.SIGALRM,
signal.ITIMER_VIRTUAL : signal.SIGVTALRM,
}
def __init__(self, interval=0.001, timer = signal.ITIMER_REAL): # CHANGE
self.interval = interval
self.running = False
self.counter = collections.Counter()
self.timer = timer # CHANGE
self.signal = self.TimerSigs[timer] # CHANGE
....

Externally stop a running while loop

I have a cmd.Cmd class command line interpreter that, for example, initializes a self.counter = Counter().
After calling 'start', do_start() will call self.counter.start() and self.counter starts a while loop that counts from 0 to infinity.
Pseudocode example of Counter:
class Counter(object):
def __init__(self):
self.number = 0
self.running = False
def start():
self.running = True
while self.running:
self.number += 1
def status():
return self.number
def stop():
self.running = False
How can I call 'status' in my cmd.Cmd class (which calls do_status()) to get self.counter.status() which will give the current number that has been incremented?
And how can I call 'stop' in my cmd.Cmd class to get self.counter.stop() to stop the while loop.
If you want to do something in parallel you must use threads or multiple processes like this:
import threading
from time import sleep
class Counter(object):
def __init__(self):
self.number = 0
self.running = False
def start(self):
self.running = True
while self.running:
self.number += 1
# add sleep to prevent blocking main thread by this loop
sleep(0.1)
def status(self):
return self.number
def stop(self):
self.running = False
class Cmd(object):
t = None
counter = None
def start(self):
self.counter = Counter()
self.t = threading.Thread(target=self.counter.start)
self.t.start()
def do_status(self):
return self.counter.status()
def stop(self):
self.counter.stop()
# waiting while thread with Counter will finish
self.t.join()
if __name__ == "__main__":
cmd = Cmd()
print "Starting counter"
cmd.start()
sleep(5)
print cmd.do_status()
sleep(2)
print cmd.do_status()
cmd.stop()
print "Counter was stopped"
Output will be:
Starting counter
50
70
Counter was stopped
But if you want to be able communicate with Counter from different application then you must learn about sockets .
if cmd is an instance of Cmd and your using an instance method:
Send the instance to Counter:
def __init__(self, cmd):
self.number = 0
# self.running = False # removed - use self.cmd.status() for control
self.cmd = cmd
Control while using self.cmd:
def start():
while self.cmd.status():
self.number += 1
I expect self.cmd.status() to be blocking (expecting user input, or something like that).

How to start and stop a thread

How can I start and stop a thread with my poor thread class?
It is in loop, and I want to restart it again at the beginning of the code. How can I do start-stop-restart-stop-restart?
My class:
import threading
class Concur(threading.Thread):
def __init__(self):
self.stopped = False
threading.Thread.__init__(self)
def run(self):
i = 0
while not self.stopped:
time.sleep(1)
i = i + 1
In the main code, I want:
inst = Concur()
while conditon:
inst.start()
# After some operation
inst.stop()
# Some other operation
You can't actually stop and then restart a thread since you can't call its start() method again after its run() method has terminated. However you can make one pause and then later resume its execution by using a threading.Condition variable to avoid concurrency problems when checking or changing its running state.
threading.Condition objects have an associated threading.Lock object and methods to wait for it to be released and will notify any waiting threads when that occurs. Here's an example derived from the code in your question which shows this being done. In the example code I've made the Condition variable a part of Thread subclass instances to better encapsulate the implementation and avoid needing to introduce additional global variables:
from __future__ import print_function
import threading
import time
class Concur(threading.Thread):
def __init__(self):
super(Concur, self).__init__()
self.iterations = 0
self.daemon = True # Allow main to exit even if still running.
self.paused = True # Start out paused.
self.state = threading.Condition()
def run(self):
self.resume()
while True:
with self.state:
if self.paused:
self.state.wait() # Block execution until notified.
# Do stuff...
time.sleep(.1)
self.iterations += 1
def pause(self):
with self.state:
self.paused = True # Block self.
def resume(self):
with self.state:
self.paused = False
self.state.notify() # Unblock self if waiting.
class Stopwatch(object):
""" Simple class to measure elapsed times. """
def start(self):
""" Establish reference point for elapsed time measurements. """
self.start_time = time.time()
return self
#property
def elapsed_time(self):
""" Seconds since started. """
try:
return time.time() - self.start_time
except AttributeError: # Wasn't explicitly started.
self.start_time = time.time()
return 0
MAX_RUN_TIME = 5 # Seconds.
concur = Concur()
stopwatch = Stopwatch()
print('Running for {} seconds...'.format(MAX_RUN_TIME))
concur.start()
while stopwatch.elapsed_time < MAX_RUN_TIME:
concur.resume()
# Can also do other concurrent operations here...
concur.pause()
# Do some other stuff...
# Show Concur thread executed.
print('concur.iterations: {}'.format(concur.iterations))
This is David Heffernan's idea fleshed-out. The example below runs for 1 second, then stops for 1 second, then runs for 1 second, and so on.
import time
import threading
import datetime as DT
import logging
logger = logging.getLogger(__name__)
def worker(cond):
i = 0
while True:
with cond:
cond.wait()
logger.info(i)
time.sleep(0.01)
i += 1
logging.basicConfig(level=logging.DEBUG,
format='[%(asctime)s %(threadName)s] %(message)s',
datefmt='%H:%M:%S')
cond = threading.Condition()
t = threading.Thread(target=worker, args=(cond, ))
t.daemon = True
t.start()
start = DT.datetime.now()
while True:
now = DT.datetime.now()
if (now-start).total_seconds() > 60: break
if now.second % 2:
with cond:
cond.notify()
The implementation of stop() would look like this:
def stop(self):
self.stopped = True
If you want to restart, then you can just create a new instance and start that.
while conditon:
inst = Concur()
inst.start()
#after some operation
inst.stop()
#some other operation
The documentation for Thread makes it clear that the start() method can only be called once for each instance of the class.
If you want to pause and resume a thread, then you'll need to use a condition variable.

Categories

Resources