来自asyncore的文档:https://docs.python.org/2/library/asyncore.html
import asyncore, socket
class HTTPClient(asyncore.dispatcher):
def __init__(self, host, path):
asyncore.dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect( (host, 80) )
self.buffer = 'GET %s HTTP/1.0\r\n\r\n' % path
def handle_connect(self):
pass
def handle_close(self):
self.close()
def handle_read(self):
print self.recv(8192)
def writable(self):
return (len(self.buffer) > 0)
def handle_write(self):
sent = self.send(self.buffer)
self.buffer = self.buffer[sent:]
client = HTTPClient('www.python.org', '/')
asyncore.loop()
现在假设我们有:
def handle_read(self):
data = self.recv(8192)
//SOME REALLY LONG AND COMPLICATED THING
由于asyncore的polling / select方法逻辑,这是否在Asyncore中处理,或者我是否需要这样做:
def handle_read(self):
data = self.recv(8192)
h = Handler(data)
h.start()
class Handler(threading.Thread):
def __init__(self, data):
threading.Thread.__init__(self)
self.data = data
def run():
//LONG AND COMPLICATED THING WITH DATA
如果我确实需要一个帖子,h.join()
之后我想要start
吗?它似乎工作,但自从加入块,我不确定为什么。
答案 0 :(得分:2)
由于asyncore的轮询/选择,这是否在Asyncore中处理 methodlogy?
不,asyncore不能自己处理handle_read()
中的长阻塞任务,因为只有一个线程。该线程正在做一些长时间的工作,它不能被同一个线程中断。
然而,这种阻塞实现是有道理的。唯一的问题是网络传输速度较慢。例如,如果长任务需要1秒,则最大数据传输速率为每秒8192字节。虽然数据速率较慢,但网络连接稳定并且按预期工作。这是由操作系统内核中的TCP协议实现处理的。
......或者我需要做......?如果我确实需要一个帖子,我是否需要
h.join()
开始后?
以上线程用法都没有意义。但是,仍然可以使用辅助线程以最大速率下载数据并并行处理这些数据,请参阅下面的说明。
TCP提供可靠,有序和错误检查的流传输。
流量控制 - 限制发件人传输数据以保证的速率 交货可靠。接收者不断暗示发送者如何 可以接收很多数据(由滑动窗口控制)。当。。。的时候 接收主机的缓冲区填充,下一个确认包含0 in 窗口大小,停止传输并允许缓冲区中的数据 被处理。
...
当接收方通告窗口大小为0时,发送方停止 发送数据并启动持久计时器。使用持久计时器 保护TCP免受可能出现的死锁情况的影响 后续窗口大小更新从接收器丢失,并且 在收到新的窗口大小更新之前,发送方无法发送更多数据 来自接收者。当持久化计时器到期时,TCP发送方 尝试通过发送一个小数据包进行恢复,以便接收器 通过发送包含新窗口的另一个确认来响应 大小
因此,当由于handle_read()
中的长任务而未从套接字读取数据时,套接字缓冲区变满。 TCP连接暂停并且不接收任何新数据包。在收到recv()
个新数据后,将TCP ACK
数据包发送给发件人以更新TCP窗口大小。
当数据传输速率受设置限制时,文件下载程序应用程序可以观察到类似的行为。例如,如果限制设置为1Kb / s,则下载程序可以每秒调用recv(1000)
一次。即使物理网络连接能够发送1Mb / s,也只能接收1Kb / s。在这种情况下,可以通过tcpdump
或Wireshark
TCP零窗口数据包和 TCP窗口更新数据包查看。
虽然应用程序可以使用长阻塞任务,但网络连接通常被视为瓶颈。因此,尽快发布网络可能会更好。
如果长任务需要更长的时间,那么数据下载最简单的解决方案是下载所有内容,然后才处理下载的数据。但是,如果数据下载的时间与数据处理任务的时间相当,则可能是不可接受的。例如,如果下载并行执行处理,则可以在2小时内完成下载1小时+ 2小时处理。
如果在handle_read()
中创建了新线程且主线程没有等待帮助程序线程完成(没有join()
),则应用程序可能会创建大量线程。请注意,handle_read()
可能每秒调用数千次,如果每个长任务需要更多秒,则应用程序可能会创建数百个线程,最后可能会被异常杀死。这种解决方案没有意义,因为无法控制线程数,并且这些线程处理的数据块也是随机的。函数recv(8192)
最多接收8192
个字节,但也可能接收较小的数据块。
创建一个线程并立即阻止join()
执行主线程没有任何意义,因为这样的解决方案并不比没有任何线程的初始解决方案好。
某些辅助线程和后来的join()
可用于并行执行某些操作。例如:
# Start detached thread
h.start()
# Do something in parallel to that thread
# ...
# Wait the thread to finish
h.join()
然而,这不是这种情况。
可以创建一个负责数据处理的持久工作线程(或几个使用所有CPU核心)。它应该在asyncore.loop()
之前启动,例如:
handler = Handler()
asyncore.loop()
现在,一旦处理程序线程准备就绪,它就可以将所有下载的数据用于处理,同时主线程可以继续进行数据下载。当处理程序线程忙时,下载程序会将数据附加到其数据缓冲区。需要注意线程之间的正确同步:
buffer
,则处理程序线程应该等待它才能访问buffer
; buffer
下载程序应该等待它才能追加到buffer
; buffer
为空,则冻结并等待新下载的数据。可以使用threading condition object和生产者 - 消费者示例:
来实现# create a new condition variable on __init__
cv = threading.Condition()
# Consume one item by Handler
cv.acquire()
while not an_item_is_available():
cv.wait()
get_an_available_item()
cv.release()
# DO SOME REALLY LONG AND COMPLICATED THING
# Produce one item by Downloader
cv.acquire()
make_an_item_available()
cv.notify()
cv.release()
此处make_an_item_available()
可能与将下载的数据附加到buffer
和/或设置其他一些共享状态变量(例如在handle_close()
中)有关。处理程序线程应该在cv.release()
之后执行其长任务,因此在该长任务期间,下载程序能够获取锁并将新数据附加到buffer
。
答案 1 :(得分:1)
这与我之前提出的here问题一样。
如果您需要实现LONG AND COMPLICATED THING WITH DATA
,则在事件循环中执行它将阻止事件循环执行任何其他任务,直到您的任务完成。
如果你生成一个线程然后join()
它(join
只是阻止执行直到连接的线程完成),情况也是如此;但是,如果您生成一个工作线程并让它自己运行完成,那么当您的长任务并行完成时,事件循环可以继续处理。
答案 2 :(得分:1)
我发布了自己的答案,因为它的灵感来自Orest Hera的答案,但因为我了解我的工作量,所以这是一个轻微的变化。
我的工作量是这样的,请求可以突然到达,但这些burts是零星的(非静止的)。而且,它们需要按照收到的顺序进行处理。所以,这就是我所做的:
#! /usr/bin/env python3
import asyncore #https://docs.python.org/2/library/asyncore.html
import socket
import threading
import queue
import time
fqueue = queue.Queue()
class Handler(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.keep_reading = True
def run(self):
while self.keep_reading:
if fqueue.empty():
time.sleep(1)
else:
#PROCESS
def stop(self):
self.keep_reading = False
class Listener(asyncore.dispatcher): #http://effbot.org/librarybook/asyncore.htm
def __init__(self, host, port):
asyncore.dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect((host, port))
def handle_read(self):
data = self.recv(40) #pretend it always waits for 40 bytes
fqueue.put(data)
def start(self):
try:
h = Handler()
h.start()
asyncore.loop()
except KeyboardInterrupt:
pass
finally:
h.stop()