Threads are printing at the same time messing up the text output

Question:

I am using 4 threads in an application which return text I would like to print to the user.
Since I would like to avoid the threads to independently print those texts, I have created a class to administrate it.

I don’t know what I am doing wrong here, but it is still not working.

The code you can see below:

from threading import Thread
import time
import random

class Creature:
    def __init__(self, name, melee, shielding, health, mana):
        self.name = name
        self.melee = melee
        self.shielding = shielding
        self.health = health
        self.mana = mana

    def attack(self, attacker, opponent, echo):
        while 0 != 1:
            time.sleep(1)
            power = random.randint(1, attacker.melee)
            resistance = random.randint(1, opponent.shielding)
            resultant = power - resistance
            if resistance > 0:
                opponent.health -= resistance
                if opponent.health < 0:
                    msg = opponent.name, " is dead"
                    echo.message(msg)
                    quit()
                else:
                    msg = opponent.name, " lost ", resistance, " hit points due to an attack by ", attacker.name
                    echo.message(msg)

    def healing(self, healed, echo):
        while 0 != 1:
            time.sleep(1)
            if self.mana >= 25:
                if healed.health >= 0:
                    if healed.health < 50:
                        life = random.randint(1, 50)
                        self.mana -= 25
                        healed.health += life
                        if healed.health > 100:
                            healed.health = 100
                        msg = healed.name, " has generated himself and now has ", self.health, " hit points"
                        echo.message(msg)
                else:
                    quit()

class echo:
    def message(self, msg):
        print msg

myEcho = echo()

Monster = Creature("Wasp", 30, 15, 100, 100)
Player = Creature("Knight", 25, 20, 100, 100)

t1 = Thread(target = Player.attack, args = (Monster, Player, myEcho))
t1.start()
t2 = Thread(target = Monster.attack, args = (Player, Monster, myEcho))
t2.start()
t3 = Thread(target=Player.healing(Player, myEcho), args=())
t3.start()
t4 = Thread(target=Monster.healing(Monster, myEcho), args=())
t4.start()

Here you can see the messed up outputs:

*('Wasp'('Knight', ' l, ' lost ', ost 13, ' hit points ', 4, due to an attack by '' hi, 'Waspt poi')nts d
ue to an attack by ', 'Knight')
('Wasp', ' lost ', 12, ' hit points due to an attack by ', 'Knight')
('Knight', ' lost ', 17, ' hit points due to an attack by ', 'Wasp')
('Wasp', ' lost ', 6, ' hit points due to an attack by ', 'Knight'('Knight')
, ' lost ', 1, ' hit points due to an attack by ', 'Wasp')
('Wasp', ' lost ', 5, ' hit points due to an attack by ', 'Knight')
('Knight', ' lost ', 13, ' hit points due to an attack by ', 'Wasp')
(('Wa'Knighsp't', , ' los' lostt ' ', , 32, ' hit points due to an attack by ', 'Knight')
, ' hit points due to an attack by ', 'Wasp')*

Do you guys have any idea how to fix this issue?

Asked By: user1688175

||

Answers:

Use a threading.Semaphore to ensure that there won’t be any conflicts:

screenlock = Semaphore(value=1)   # You'll need to add this to the import statement.

Then, before you call echo.message, insert this line in order to acquire the right to output:

screenlock.acquire()

and then this line after you call echo.message so as to allow another thread to print:

screenlock.release()
Answered By: anon582847382

Use a semaphore. An example would be:

from threading import *
screen_lock = Semaphore(value=1)

Now every time your process wants to write something, it would:

screen_lock.acquire()
print("Something!")
screen_lock.release()

More about semas here (official documentation) and here (a great article by Laurent Luce).

Answered By: Chris vCB

Slightly better than a semaphore is a re-entrant lock.

from threading import RLock

class SynchronizedEcho(object):

    print_lock = RLock()

    def __init__(self, global_lock=True):
        if not global_lock:
            self.print_lock = RLock()

    def __call__(self, msg):
        with self.print_lock:
            print(msg)

echo = SynchronizedEcho()   
echo("Test")

The benefit of a re-entrant lock is it can be used with a with statement. That is, if any exceptions are thrown whilst using the lock you can be assured it will be released at the end of the with block. To do the same with a semaphore you would have to remember to write a try-finally block.

It’s worth noting that you should also be using a semaphore or a lock when accessing and modifying the attributes of your Creatures. This is because you have multiple threads modiying the values of the attributes. So in the same way one print was interrupted by another print and the output was garbled, so to will your attributes become garbled.

Consider the following:

Thread A

# health starts as 110
if healed.health > 100:
    # Thread A is interrupted and Thread B starts executing
    # health is now 90
    healed.health = 100
    # health is now set to 100 -- ignoring the 20 damage that was done

Thread B

# health is 110 and resistance is 20
opponent.health -= resistance
# health is now 90.
# Thread B is interrupted and Thread A starts executing
Answered By: Dunes

use the ‘logging’ module instead of print.
logging is thread safe, so each thread will finish to write as you expected

here you can find explanation how to use logging
about logging from python module of the week

here you can see that it is thread safe
from python doc

Answered By: nirselickter
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.