简体   繁体   中英

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

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?

Thanks!

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()

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).

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM