Python threading.Thread : random occurrences of missing final newline of print()

Question:

I am just in a process of learning how to use the Python threading module and come up with following code which should help me to understand how threading works:

from time import sleep
from threading import Thread
closeAllThreads = False
threadCounter = 0
def runMeAsThread():
    global closeAllThreads, threadCounter
    threadCounter += 1
    sleepValue = threadCounter
    while not closeAllThreads:
        print(f'runMeAsThread(): {threadCounter = } {sleepValue = }', flush=True)
        sleep(sleepValue)
firstThreadObj = Thread(None,runMeAsThread)
firstThreadObj.start()
secondThreadObj = Thread(None,runMeAsThread)
secondThreadObj.start()
thirdThreadObj = Thread(None,runMeAsThread)
thirdThreadObj.start()
sleep(5)
closeAllThreads = True

What is wrong with my expectation that all of the printed lines should be printed in a separate line?

I am getting randomly occuring cases where two lines are printed on one line like:

runMeAsThread(): threadCounter = 1 sleepValue = 1
runMeAsThread(): threadCounter = 2 sleepValue = 2
runMeAsThread(): threadCounter = 3 sleepValue = 3
runMeAsThread(): threadCounter = 3 sleepValue = 1
runMeAsThread(): threadCounter = 3 sleepValue = 2runMeAsThread(): threadCounter = 3 sleepValue = 1

runMeAsThread(): threadCounter = 3 sleepValue = 3runMeAsThread(): threadCounter = 3 sleepValue = 1

runMeAsThread(): threadCounter = 3 sleepValue = 2
runMeAsThread(): threadCounter = 3 sleepValue = 1

I have already included the , flush=True print option to get rid of this behavior, but it didn’ help. How does it come? Could it happen that also the line contents get ‘mixed’?

Asked By: Claudio

||

Answers:

The built in print function is not thread safe, which basically means you risk corrupting the printed messages if you use it in multiple threads simultaneously.

What you can do to prevent this is either use a mutex or a queue. Below are examples on how to use both.

Typically if a function is thread safe it will be documented as such, it’s probably best to assume that a function or method is not thread safe unless it explicitly states that it is.

For the queue example:

import queue
from time import sleep
from threading import Thread

closeAllThreads = False
threadCounter = 0

def printingThread(my_queue):
    while not closeAllThreads:
        try:
            msg = my_queue.get(timeout=1)
            print(msg)
        except queue.Empty:
            pass

def runMeAsThread(my_queue):
    global closeAllThreads, threadCounter
    threadCounter += 1
    sleepValue = threadCounter
    while not closeAllThreads:
        my_queue.put(f'runMeAsThread(): {threadCounter = } {sleepValue = }')
        sleep(sleepValue)


my_queue = queue.Queue()
firstThreadObj = Thread(None,runMeAsThread, args=(my_queue,))
firstThreadObj.start()

secondThreadObj = Thread(None,runMeAsThread, args=(my_queue,))
secondThreadObj.start()

thirdThreadObj = Thread(None,runMeAsThread, args=(my_queue,))
thirdThreadObj.start()

fourthThreadObj = Thread(None, printingThread, args=(my_queue,))
fourthThreadObj.start()

sleep(5)
closeAllThreads = True

Or you can use a lock object:

from time import sleep
from threading import Thread
from threading import Lock


closeAllThreads = False
threadCounter = 0
def runMeAsThread(lock):
    global closeAllThreads, threadCounter
    threadCounter += 1
    sleepValue = threadCounter
    while not closeAllThreads:
        with lock:  # only print while the thread has acquired the lock
            print(f'runMeAsThread(): {threadCounter = } {sleepValue = }')
        sleep(sleepValue)


lock = Lock()  #  all threads share the same lock object
firstThreadObj = Thread(None,runMeAsThread, args=(lock,))
firstThreadObj.start()

secondThreadObj = Thread(None,runMeAsThread, args=(lock,))
secondThreadObj.start()

thirdThreadObj = Thread(None,runMeAsThread, args=(lock,))
thirdThreadObj.start()

sleep(5)
closeAllThreads = True
Answered By: Alexander
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.