Search code examples
python-3.xmultithreadingsleep

using time.sleep() in Thread python3


Im trying to make a simple thread in python3 where the test1 will run until a certain amount of number and then sleep while the test2 will still be running and also when it reaches a certain number it will go to sleep. My code goes like this:

def test2(count):
    if count == 8:
        print("sleep for 4 sec")
        time.sleep(3.0)
    print("test2 thread = {}".format(count))

def test1(count):
    if count == 5:
        print("sleep for 5 sec")
        time.sleep(3.0)
    print("test1 thread = {}".format(count))

for num in range(0,10):
    t1 = threading.Thread(target=test1, args=(num,))
    t2 = threading.Thread(target=test2, args=(num,))
    t1.start()
    t2.start()

Also, i been coding python before but without using thread and now i wanted to have a go on it and hope this will end well :) ohh, and additionally the output doesn't matter if they overlap.


Solution

  • The threading.Thread() creates new thread and t1.start() just dispatch it.

    This code:

    for num in range(0,10):
        t1 = threading.Thread(target=test1, args=(num,))
        t2 = threading.Thread(target=test2, args=(num,))
        t1.start()
        t2.start()
    

    actually creates and start 2 new threads per iteration. At the end you have 20 threads + master thread.

    Also when you start thread you should wait until it ends or run it as daemon thread. With daemon thread you are saying I don't care what you do and when you end.

    Basic thread usage can looks like this:

    import threading
    
    def do_stuff():
        print("Stuff on thread {}".format(threading.get_ident()))
    
    print("Main thread {}".format(threading.get_ident()))
    t = threading.Thread(target=do_stuff) # Specify what should be running in new thread
    t.start() # Dispatch thread
    t.join() # Wait until the thread is done
    

    Note: threading.get_ident() gives you unique identifier of the thread where this function is called.

    Now from you example if you want start 2 independent threads you can do this:

    import threading
    import time
    
    def test2():
        for count in range(0, 10):
            if count == 8:
                print("test2: sleep for 4 sec")
                time.sleep(3.0)
            print("test2: thread = {}".format(count))
    
    def test1():
        for count in range(0, 10):
            if count == 5:
                print("test 1: sleep for 5 sec")
                time.sleep(3.0)
            print("test1: thread = {}".format(count))
    
    
    t1 = threading.Thread(target=test1)
    t2 = threading.Thread(target=test2)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    

    But you might want to synchronize those threads and send them some item at the "same" time.

    import threading
    
    # Create threads
    t1 = threading.Thread(target=test1) 
    t2 = threading.Thread(target=test2)
    
    # Run threads
    t1.start()
    t2.start()
    
    # Go through some list or whatever
    for num in range(0,10):
        # send num to t1
        # send num to t2
        # wait for t1 and t2
        pass
    
     # Wait until threads are finished with their jobs
     t1.join()
     t2.join()
    

    For sending value to other thread we can user queue.Queue. You can safely put there value in one thread and second thread can read it or wait until there is something (or multiple thread can write and multiple thread can read).

    import threading
    import time
    import queue
    
    def test2(q):
        while True:
            count = q.get() # Get data from the q2 queue
            if count == 8:
                print("test2: sleep for 4 sec")
                time.sleep(3.0)
            print("test2: thread = {}".format(count))
    
    def test1(q):
        while True:
            count = q.get() # Get data from the q1 queue
            if count == 5:
                print("test 1: sleep for 5 sec")
                time.sleep(3.0)
            print("test1: thread = {}".format(count))
    
    # Creates queues
    q1 = queue.Queue()
    q2 = queue.Queue()
    
    # Create threads
    t1 = threading.Thread(target=test1, args=(q1, ))
    t2 = threading.Thread(target=test2, args=(q2, ))
    
    # Run threads
    t1.start()
    t2.start()
    
    # Go through some list or whatever
    for num in range(0, 10):
        # send num to t1
        q1.put(num)
        # send num to t2
        q2.put(num)
        # wait for t1 and t2
        # ???
    
    # Wait until threads are finished with their jobs
    t1.join()
    t2.join()
    

    Oh wait... how can we know that threads are done with their work and we can send another value? Well we can use Queue again. Create new pair and sending e.g. True at the end of the test? function and then wait read in main loop from those queues. But for sending state information we should use threading.Event.

    import threading
    import time
    import queue
    
    def test2(q, e):
        while True:
            count = q.get() # Get data from the q2 queue
            if count == 8:
                print("test2: sleep for 4 sec")
                time.sleep(3.0)
            print("test2: thread = {}".format(count))
            e.set() # Inform master the processing of given value is done
    
    def test1(q, e):
        while True:
            count = q.get() # Get data from the q1 queue
            if count == 5:
                print("test 1: sleep for 5 sec")
                time.sleep(3.0)
            print("test1: thread = {}".format(count))
            e.set() # Inform master the processing of given value is done
    
    # Creates queues
    q1 = queue.Queue()
    q2 = queue.Queue()
    
    # Create events
    e1 = threading.Event()
    e2 = threading.Event()
    
    # Create threads
    t1 = threading.Thread(target=test1, args=(q1, e1))
    t2 = threading.Thread(target=test2, args=(q2, e2))
    
    # Run threads
    t1.start()
    t2.start()
    
    # Go through some list or whatever
    for num in range(0, 10):
        # send num to t1
        q1.put(num)
        # send num to t2
        q2.put(num)
        # wait for t1
        e1.wait()
        # wait for t2
        e2.wait()
    
    # Wait until threads are finished with their jobs
    t1.join()
    t2.join()
    

    Now we are almost there but the script never ends. It's because the test? functions (threads) waits in infinite loop for data (from queues q1/q2). We need some way how to tell them "Ok, that's all folks". For that we can say None value in queues means end. The result following:

    import threading
    import time
    import queue
    
    def test2(q, e):
        while True:
            count = q.get() # Get data from the q2 queue
            if count is None: # Exit on None value
                return
            if count == 8:
                print("test2: sleep for 4 sec")
                time.sleep(3.0)
            print("test2: thread = {}".format(count))
            e.set() # Inform master the processing of given value is done
    
    def test1(q, e):
        while True:
            count = q.get() # Get data from the q1 queue
            if count is None: # Exit on None value
                return
            if count == 5:
                print("test 1: sleep for 5 sec")
                time.sleep(3.0)
            print("test1: thread = {}".format(count))
            e.set() # Inform master the processing of given value is done
    
    # Creates queues
    q1 = queue.Queue()
    q2 = queue.Queue()
    
    # Create events
    e1 = threading.Event()
    e2 = threading.Event()
    
    # Create threads
    t1 = threading.Thread(target=test1, args=(q1, e1))
    t2 = threading.Thread(target=test2, args=(q2, e2))
    
    # Run threads
    t1.start()
    t2.start()
    
    # Go through some list or whatever
    for num in range(0, 10):
        # send num to t1
        q1.put(num)
        # send num to t2
        q2.put(num)
        # wait for t1
        e1.wait()
        # wait for t2
        e2.wait()
    
    # Inform threads to exit
    q1.put(None)
    q2.put(None)
    
    # Wait until threads are finished with their jobs
    t1.join()
    t2.join()
    

    Note: instead of using parameters in threads "main" functions you can use global variables, because global variables or class attributes are shared across all threads. But usually it is bad practice.


    Be aware of gotchas coming with threading, for example exception handling is not so easy. Imagine that function test1 raises exception before calling e.set(). Then the master thread never ends waiting on e1.wait().

    Also CPython (the most common implementation of the Python) has something called GIL, which basically (with some exceptions) allows running only 1 thread at a time and the others are sleeping.


    Threading documentation

    Queue documentation