线程同时打印导致文本输出混乱

8 投票
4 回答
10149 浏览
提问于 2025-05-01 00:17

我在一个应用程序中使用了4个线程,这些线程会返回一些文本,我想把这些文本打印给用户看。因为我不想让这些线程各自独立地打印文本,所以我创建了一个类来管理这个过程。

我不知道自己哪里做错了,但现在还是不管用。

下面是你们可以看到的代码:

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

这里是你们可以看到的输出混乱的情况:

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

你们有没有什么想法可以解决这个问题?

暂无标签

4 个回答

3

用'logging'模块代替print函数。logging是线程安全的,这样每个线程都会按照你预期的方式完成写入。

这里有关于如何使用logging的解释:来自Python每周模块的logging介绍

你可以在这里看到它是线程安全的:来自Python文档

4

比起信号量,重入锁稍微好一点。

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

重入锁的好处在于它可以和 with 语句一起使用。也就是说,如果在使用锁的时候发生了异常,你可以放心,它会在 with 块结束时自动释放。要用信号量做到这一点,你就得记得写一个 try-finally 块。

值得注意的是,当你访问和修改你的生物体(Creatures)的属性时,也应该使用信号量或锁。这是因为有多个线程在修改这些属性的值。所以就像一个打印输出被另一个打印打断,导致输出混乱一样,你的属性也会变得混乱。

考虑以下情况:

线程 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

线程 B

# health is 110 and resistance is 20
opponent.health -= resistance
# health is now 90.
# Thread B is interrupted and Thread A starts executing
8

使用信号量。一个例子是:

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

现在每当你的程序想要写东西时,它会:

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

关于信号量的更多信息可以在这里(官方文档)和这里(Laurent Luce的一篇很棒的文章)找到。

15

使用一个 threading.Semaphore 来确保不会发生冲突:

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

然后,在你调用 echo.message 之前,插入这一行代码,以获得输出的权限:

screenlock.acquire()

在你调用完 echo.message 之后,再插入这一行代码,以便允许其他线程进行打印:

screenlock.release()

撰写回答