Python Twisted中LoopingCall和callInThread的区别
我正在尝试弄清楚在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 个回答
你有没有看过关于 LoopingCall
的 文档?它不涉及线程——它是在主线程上运行的,也就是说,通常是在反应器的线程上运行(就像你每秒调用它的 start
方法那样)。而 callInThread 是这两个方法中唯一一个会让函数在单独的线程上运行的。
为什么会这样呢?它们有什么区别?难道它们不是线程吗?
其实不是。LoopingCall
使用了 callLater
;它是在反应器中运行调用的。
我在 LoopingCall 中的所有 self.sendLine 都是立即执行的。
没错,正是这样。
而 callInThread 中的则不是。
其实并不是说它们没有被执行,而是因为你在一个线程中调用了反应器的 API,而你是 绝对不允许这样做的,这会让你的程序进入一种 完全崩溃的状态,永远无法恢复。之后的每一个 API 调用都可能产生奇怪的、错误的结果,或者根本没有结果,甚至会随机崩溃。
这就是多线程程序的正常工作方式 ;-)。
再强调一遍:在 twisted 中,除了 callFromThread
这个 API 之外,所有的 API 都是 不安全的。不幸的是,如果对每个 API 都加上警告,那将会让代码维护变得非常麻烦,所以很多用户都是通过调用 API 后发现问题的,和你遇到的情况一样。
如果你有一些代码在一个线程中运行,需要调用反应器的 API,使用 callFromThread
或 blockingCallFromThread
,这样可以把调用转发到反应器线程,确保一切正常运行。不过,对于定时调用之类的情况,其实根本不需要使用线程,这样只会让你的程序变得复杂。