蟒蛇; asyncore handle_read;我需要一个单独的线程吗?

时间:2015-11-12 15:43:05

标签: python asynchronous asyncore

来自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吗?它似乎工作,但自从加入块,我不确定为什么。

3 个答案:

答案 0 :(得分:2)

简答

  

由于asyncore的轮询/选择,这是否在Asyncore中处理   methodlogy?

不,asyncore不能自己处理handle_read()中的长阻塞任务,因为只有一个线程。该线程正在做一些长时间的工作,它不能被同一个线程中断。

然而,这种阻塞实现是有道理的。唯一的问题是网络传输速度较慢。例如,如果长任务需要1秒,则最大数据传输速率为每秒8192字节。虽然数据速率较慢,但网络连接稳定并且按预期工作。这是由操作系统内核中的TCP协议实现处理的。

  

......或者我需要做......?如果我确实需要一个帖子,我是否需要h.join()   开始后?

以上线程用法都没有意义。但是,仍然可以使用辅助线程以最大速率下载数据并并行处理这些数据,请参阅下面的说明。

TCP协议

TCP提供可靠,有序和错误检查的流传输。

Data transfer:

  

流量控制 - 限制发件人传输数据以保证的速率   交货可靠。接收者不断暗示发送者如何   可以接收很多数据(由滑动窗口控制)。当。。。的时候   接收主机的缓冲区填充,下一个确认包含0 in   窗口大小,停止传输并允许缓冲区中的数据   被处理。

     

...

     

当接收方通告窗口大小为0时,发送方停止   发送数据并启动持久计时器。使用持久计时器   保护TCP免受可能出现的死锁情况的影响   后续窗口大小更新从接收器丢失,并且   在收到新的窗口大小更新之前,发送方无法发送更多数据   来自接收者。当持久化计时器到期时,TCP发送方   尝试通过发送一个小数据包进行恢复,以便接收器   通过发送包含新窗口的另一个确认来响应   大小

因此,当由于handle_read()中的长任务而未从套接字读取数据时,套接字缓冲区变满。 TCP连接暂停并且不接收任何新数据包。在收到recv()个新数据后,将TCP ACK数据包发送给发件人以更新TCP窗口大小。

当数据传输速率受设置限制时,文件下载程序应用程序可以观察到类似的行为。例如,如果限制设置为1Kb / s,则下载程序可以每秒调用recv(1000)一次。即使物理网络连接能够发送1Mb / s,也只能接收1Kb / s。在这种情况下,可以通过tcpdumpWireshark 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()