Python Twisted中LoopingCall和callInThread的区别

1 投票
2 回答
4183 浏览
提问于 2025-04-16 01:06

我正在尝试弄清楚在Twisted中,task.LoopingCall和reactor.callInThread之间的区别。

在LoopingCall中的所有self.sendLine操作都是立即执行的。而在callInThread中的操作却不是。它们只有在LoopingCall中的操作完成后才会发送。尽管我发送的是正确的分隔符。

这是为什么呢?它们有什么区别?难道它们不是都在不同的线程中吗?

这是服务器的代码:


from twisted.internet import reactor, protocol, task
from twisted.protocols import basic
from twisted.python import log
import sys
import time
import threading
import Queue

class ServerProtocol(basic.LineOnlyReceiver):
    delimiter = '\0'
    clientReady = 1

    def __init__(self):
        print 'New client has logged on. Waiting for initialization'

    def lineReceived(self, line):
        if line.startswith('I'):
            print 'Data started with I: '+line
            user = dict(uid=line[1:6], x=line[6:9], y=line[9:12])
            self.factory.users[user['uid']] = user
            log.msg(repr(self.factory.users))
            self.startUpdateClient(user)
            reactor.callInThread(self.transferToClient)
            self.sendLine(user['uid'] + ' - Beginning - Initialized')
            print user['uid'] + ' - Beginning - Initialized'
        elif line.startswith('P'):
            print 'Ping!'
        elif line[0:3] == 'ACK':
            print 'Received ACK'
            self.clientReady = 1
        #else:
            #self.transport.loseConnection()

    def _updateClient(self, user):
        if self._running == 0:
            self._looper.stop()
            return
        self._running -= 1
        self._test += 1
        print user['uid'] + ' Sending test data' + str(self._test)
        self.sendLine(user['uid'] + ' Test Queue Data #%d' % (self._test,) + '\0')

    def startUpdateClient(self, user):
        self._running, self._test = 25, 0
        self._looper = task.LoopingCall(self._updateClient, user)
        self._looper.start(1, now=False)
        print user['uid'] + ' - Startupdateclient'

    def transferToClient(self):
        test = 20
        while test > 0:
            if self.clientReady == 1:
                test = test-1
                print 'Reactor test ' + str(test) + ' - ' + str(time.time())
                self.clientReady = 0
                self.sendLine('This is reactortest ' + str(test) + ' - ' + str(time.time()) +' \0')

class Server(protocol.ServerFactory):
    protocol = ServerProtocol
    def __init__(self):
        self.users = {}

if __name__ == '__main__':
    log.startLogging(sys.stderr)
    reactor.listenTCP(2000, Server())
    reactor.run()

这是客户端的代码:


#!/usr/bin/env python

import socket
import time

host = 'localhost'
port = 2000
size = 1024
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
s.send('I12345070060\0')
running = 1

while running:
    s.send('ACK\0')
    data = s.recv(size)
    if data:
        print 'Received:', data 
    else:
        print 'Closing'
        s.close()
        running=0

2 个回答

1

你有没有看过关于 LoopingCall文档?它不涉及线程——它是在主线程上运行的,也就是说,通常是在反应器的线程上运行(就像你每秒调用它的 start 方法那样)。而 callInThread 是这两个方法中唯一一个会让函数在单独的线程上运行的。

5

为什么会这样呢?它们有什么区别?难道它们不是线程吗?

其实不是。LoopingCall 使用了 callLater;它是在反应器中运行调用的。

我在 LoopingCall 中的所有 self.sendLine 都是立即执行的。

没错,正是这样。

而 callInThread 中的则不是。

其实并不是说它们没有被执行,而是因为你在一个线程中调用了反应器的 API,而你是 绝对不允许这样做的,这会让你的程序进入一种 完全崩溃的状态,永远无法恢复。之后的每一个 API 调用都可能产生奇怪的、错误的结果,或者根本没有结果,甚至会随机崩溃。

这就是多线程程序的正常工作方式 ;-)。

再强调一遍:在 twisted 中,除了 callFromThread 这个 API 之外,所有的 API 都是 不安全的。不幸的是,如果对每个 API 都加上警告,那将会让代码维护变得非常麻烦,所以很多用户都是通过调用 API 后发现问题的,和你遇到的情况一样。

如果你有一些代码在一个线程中运行,需要调用反应器的 API,使用 callFromThreadblockingCallFromThread,这样可以把调用转发到反应器线程,确保一切正常运行。不过,对于定时调用之类的情况,其实根本不需要使用线程,这样只会让你的程序变得复杂。

撰写回答