Twisted:如何实现非阻塞代码

20 投票
1 回答
7329 浏览
提问于 2025-04-16 18:16

我对如何在Python/Twisted中编写异步代码有点困惑。假设我有一个函数,它会接收一个数字,并返回这个数字是否是质数(返回True或False),大概是这样的:


def IsPrime(numberin):
    for n in range(2,numberin):
        if numberin % n == 0: return(False)
    return(True)

(只是为了说明这个例子)。

现在假设有一个网络服务器需要根据提交的值来调用这个IsPrime函数。如果输入的数字很大,这个计算会花费很长时间。

如果与此同时,另一个用户请求一个小数字的质数判断,有没有办法使用reactor/deferreds架构异步运行这两个函数调用,这样短时间的计算结果可以在长时间计算结果之前返回?

我知道如果IsPrime的功能来自于其他网络服务器,我的服务器可以通过deferred的getPage来处理,但如果它只是一个本地函数呢?

也就是说,Twisted能否在这两个IsPrime调用之间进行时间共享,还是需要明确调用一个新线程?

或者,IsPrime的循环需要被分成一系列更小的循环,以便可以快速将控制权交还给reactor?

或者还有其他的解决办法?

1 个回答

27

我觉得你现在的理解基本上是对的。Twisted只是一个Python库,你用它写的Python代码会像你预期的那样正常执行:如果你只有一个线程(和一个进程),那么一次只能做一件事情。Twisted提供的几乎没有API会创建新的线程或进程,所以在正常情况下,你的代码是顺序执行的;isPrime在第一次执行完之前,不能再执行第二次。

在只考虑一个线程(和一个进程)的情况下,Twisted的所有“并发”或“并行”都来自于它不使用阻塞的网络输入输出(I/O)操作(以及其他某些阻塞操作),而是提供了一些工具,让这些操作以非阻塞的方式进行。这让你的程序可以继续执行其他工作,而不会因为等待一个阻塞的I/O操作(比如从套接字读取或写入)而卡住。

通过把事情拆分成小块,并让事件处理程序在这些小块之间运行,可以实现“异步”。这种方法有时很有用,前提是这种拆分不会让代码变得太难理解和维护。Twisted提供了一个帮助工具来调度这些工作小块,cooperate。使用这个工具是有好处的,因为它可以根据不同的工作来源做调度决策,并确保有足够的时间来处理事件源,而不会增加太多的延迟(换句话说,你添加的工作越多,每个工作的时间就越少,这样反应器可以继续完成它的工作)。

Twisted也提供了几个API来处理线程和进程。如果不太清楚如何把一个任务拆分成小块,这些API会很有用。你可以使用deferToThread在一个线程池中运行一个(线程安全的!)函数。方便的是,这个API会返回一个Deferred,最终会返回这个函数的返回值(如果函数抛出异常,则返回Failure)。这些Deferred看起来和其他的没什么不同,就代码使用它们的角度来看,它也可以像调用getPage那样返回——这个函数不使用额外的线程,只是非阻塞的I/O和事件处理程序。

由于Python并不特别适合在单个进程中运行多个CPU密集型线程,Twisted还提供了一个非阻塞的API来启动和与子进程通信。你可以把计算任务分配给这些进程,以利用额外的CPU或核心,而不必担心全局解释器锁(GIL)会拖慢速度,这是拆分策略和线程方法都无法做到的。处理这些进程的最低级API是reactor.spawnProcess。还有一个叫Ampoule的包,它会为你管理一个进程池,并提供一个类似于deferToThread的进程处理方式,deferToAMPProcess

撰写回答