Python threading.Condition.notify_all() 不触发下一个线程

标签 python multithreading locking python-multithreading

我正在尝试解决这个 LeetCode 问题,Print Zero Even Odd :

enter image description here enter image description here

我尝试使用 threading.Condition 执行以下解决方案对象:

import threading
from typing import Callable, Optional


class ZeroEvenOdd:
    def __init__(self, n: int):
        self.n = n
        self.i = 0
        self.last_printed: Optional[int] = None
        self.condition = threading.Condition()

    def zero(self, printNumber: Callable[[int], None]) -> None:
        with self.condition:
            self.condition.wait_for(lambda: self.last_printed is None or self.last_printed > 0)
            if self.done:
                return
            printNumber(0)
            self.last_printed = 0
            self.i += 1
            self.condition.notify_all()

    def even(self, printNumber: Callable[[int], None]) -> None:
        with self.condition:
            self.condition.wait_for(lambda: self.last_printed == 0 and self.i % 2 == 0)
            if self.done:
                return
            self._print_and_notify()

    def odd(self, printNumber: Callable[[int], None]) -> None:
        with self.condition:
            self.condition.wait_for(lambda: self.last_printed == 0 and self.i % 2 == 1)
            if self.done:
                return
            self._print_and_notify()

    def _print_and_notify(self) -> None:
        printNumber(self.i)
        self.last_printed = self.i
        self.condition.notify_all()

    @property
    def done(self) -> bool:
        if self.last_printed is not None and self.last_printed >= self.n:
            self.condition.release()
            self.condition.notify_all()
            return True
        return False


def printNumber(x: int) -> None:
    print(x)


zero_even_odd = ZeroEvenOdd(n=5)
threadA = threading.Thread(target=zero_even_odd.zero, args=(printNumber,))
threadB = threading.Thread(target=zero_even_odd.even, args=(printNumber,))
threadC = threading.Thread(target=zero_even_odd.odd, args=(printNumber,))


if __name__ == "__main__":
    threadA.start()
    threadB.start()
    threadC.start()

但是,当我运行它时,我发现它打印 0,然后打印 1,然后无限期挂起:

> python print_zero_even_odd.py
0
1
^CException ignored in: <module 'threading' from '/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/threading.py'>
Traceback (most recent call last):
  File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/threading.py", line 1308, in _shutdown
    lock.acquire()
KeyboardInterrupt

我有点困惑为什么在第一次调用 odd() 后,zero() 不再被调用。毕竟,在 odd() 中打印数字 1 后,self.last_printed 被设置为 1,这应触发 zero() 方法的 wait_for() 条件,self.last_printed > 0

知道为什么这个程序没有按预期工作吗?

最佳答案

正如 Sraw 所指出的,我的代码中没有循环,因此没有理由期望再次调用 zero() 。我最终使用 threading.Lock 对象以不同的方式解决了这个问题:

import threading
from typing import Callable, Optional


class ZeroEvenOdd:
    def __init__(self, n: int):
        self.n = n
        self.i = 0

        self.lock_zero = threading.Lock()
        self.lock_odd = threading.Lock()
        self.lock_even = threading.Lock()

        self.lock_odd.acquire()
        self.lock_even.acquire()

    def zero(self, printNumber: Callable[[int], None]) -> None:
        for _ in range(self.n):
            self.lock_zero.acquire()
            printNumber(0)

            if self.i % 2 == 0:
                self.lock_odd.release()
            else:
                self.lock_even.release()

    def even(self, printNumber: Callable[[int], None]) -> None:
        for i in range(2, self.n + 1, 2):
            self.lock_even.acquire()
            printNumber(i)
            self.i = i
            self.lock_zero.release()

    def odd(self, printNumber: Callable[[int], None]) -> None:
        for i in range(1, self.n + 1, 2):
            self.lock_odd.acquire()
            printNumber(i)
            self.i = i
            self.lock_zero.release()


def printNumber(x: int) -> None:
    print(x)


zero_even_odd = ZeroEvenOdd(n=5)
threadA = threading.Thread(target=zero_even_odd.zero, args=(printNumber,))
threadB = threading.Thread(target=zero_even_odd.even, args=(printNumber,))
threadC = threading.Thread(target=zero_even_odd.odd, args=(printNumber,))


if __name__ == "__main__":
    threadA.start()
    threadB.start()
    threadC.start()

这将打印所需的输出:

> python print_zero_even_odd.py
0
1
0
2
0
3
0
4
0
5

并以相当快速且节省内存的方式解决 LeetCode 问题:

enter image description here

关于Python threading.Condition.notify_all() 不触发下一个线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57550973/

相关文章:

python - 为什么 set 不计算我的唯一整数?

Python 继承,类级变量,__init__ 不带参数

multithreading - CPU时间片的可调度单位是进程还是线程?

objective-c - NSLocking 的使用是否应该始终包含在@try/@finally 中?

c# - 如何通过缓存键锁定?

python - 如何将带有换行符的列标题读入 Pandas?

python - 从 1-word 字符串中提取数字

c# - 使用单例多线程 C# 制作程序

multithreading - 如何处理 Isolates 中抛出的异常?

mysql - Django 模型创建时的竞争条件