18

I'm trying to create a thread, that does stuff in the background. I need to be able to effectively 'pause' it when I need to and 'resume' it again later. Also, if the thread is in the middle of doing something when I 'pause' it, it should make the calling thread wait until it finishes what it's doing.

I'm pretty new to Multithreading in Python, so I haven't gotten all that far.

What I have pretty much does everything except make the calling thread wait if pause is called while my thread is doing something.

Here's the outline of what I'm trying to achieve in code:

import threading, time

class Me(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        #flag to pause thread
        self.paused = False

    def run(self):
        while True:
            if not self.paused:
                #thread should do the thing if
                #not paused
                print 'do the thing'
                time.sleep(5)

    def pause(self):
        self.paused = True
        #this is should make the calling thread wait if pause() is
        #called while the thread is 'doing the thing', until it is
        #finished 'doing the thing'

    #should just resume the thread
    def resume(self):
        self.paused = False

I think I basically need a locking mechanism, but within the same thread?

martineau
  • 119,623
  • 25
  • 170
  • 301
mickzer
  • 5,958
  • 5
  • 34
  • 57

2 Answers2

13

Conditions can be used for this.

Here's an example filling in your skeleton:

class Me(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        #flag to pause thread
        self.paused = False
        # Explicitly using Lock over RLock since the use of self.paused
        # break reentrancy anyway, and I believe using Lock could allow
        # one thread to pause the worker, while another resumes; haven't
        # checked if Condition imposes additional limitations that would 
        # prevent that. In Python 2, use of Lock instead of RLock also
        # boosts performance.
        self.pause_cond = threading.Condition(threading.Lock())

    def run(self):
        while True:
            with self.pause_cond:
                while self.paused:
                    self.pause_cond.wait()

                #thread should do the thing if
                #not paused
                print 'do the thing'
            time.sleep(5)

    def pause(self):
        self.paused = True
        # If in sleep, we acquire immediately, otherwise we wait for thread
        # to release condition. In race, worker will still see self.paused
        # and begin waiting until it's set back to False
        self.pause_cond.acquire()

    #should just resume the thread
    def resume(self):
        self.paused = False
        # Notify so thread will wake after lock released
        self.pause_cond.notify()
        # Now release the lock
        self.pause_cond.release()

Hope that helps.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • Unfortunately, the resume function doesn't seem to be working. Doesn't notify - notify the thread? So the thread is notifying itself which isn't doing anything? Maybe my understanding is wrong? :) – mickzer Nov 10 '15 at 23:25
  • @mickzer: You wouldn't call `pause` or `resume` from the same thread that was doing work; if the thread is blocked waiting on the condition, it's not doing anything else. The thread's `run` is the only thing that actually runs in the specified thread, another thread is pausing (waiting for the worker to actually be idle), and resuming at some later point. – ShadowRanger Nov 10 '15 at 23:38
  • My bad, the line self.pause_cond.release() was being hidden by stackoverflow - the
     element wasn't scrolling. My apologies - And awesome answer
    – mickzer Nov 11 '15 at 00:07
  • IMO answer by mguijarr below which uses Events instead of conditions is better suited for the purpose. – rsjethani Nov 22 '16 at 19:15
  • @ShadowRanger If multiple threads simultaneously runs the "run" function, only 1 will print 'do the thing' while the others waits. Is this correct? – Bob Ebert Jun 30 '20 at 06:23
  • @BobEbert: As written, it's a little finicky; if you have multiple threads blocked, only one would be released by a `resume`. But if it's not paused, all of them would print until it was paused. Switching to `notify_all` would make all of them wake up at once (probably best for consistency). If you wanted to only let one thread do the thing at once, you'd go with a different design. – ShadowRanger Nov 24 '20 at 00:14
  • @ShadowRanger can I pause this thread right after the print so that other threads start and then resume it ? – BAKYAC Jan 07 '22 at 15:47
  • How would I modify pause() to allow the worker to pause itself? I'm trying to fix a thread starting the next loop cycle dealing with blocking input before it realizes that the main thread's stop flag has been set. – AwesomeCronk Jun 14 '22 at 03:34
10

Use threading.Event instead of a boolean variable, and add another event for busy state:

def __init__(self):
    ...
    self.can_run = threading.Event()
    self.thing_done = threading.Event()
    self.thing_done.set()
    self.can_run.set()    

def run(self):
    while True:
        self.can_run.wait()
        try:
            self.thing_done.clear()
            print 'do the thing'
        finally:
            self.thing_done.set()

def pause(self):
    self.can_run.clear()
    self.thing_done.wait()

def resume(self):
    self.can_run.set()

edit: previous answer was wrong, I fixed it and changed variable names to be clear

mguijarr
  • 7,641
  • 6
  • 45
  • 72
  • what if I had to pause right after print('do the thing') so that other threads start and then resume this thread ? – BAKYAC Jan 07 '22 at 15:44