Python-动态收缩线程池/停止线程

Python-动态收缩线程池/停止线程,python,threadpool,Python,Threadpool,我正在编写一个小型多线程http文件下载程序,希望能够在代码遇到错误时收缩可用线程 这些错误将特定于在web服务器不允许任何更多连接的情况下返回的http错误 如果我设置了一个由5个线程组成的池,那么每个线程都试图打开自己的连接并下载一块文件。服务器可能只允许2个连接,我相信会返回503个错误,我想检测到这一点并关闭一个线程,最终将池的大小限制为服务器允许的2个 我能让线自动停止吗 self.Thread_stop()是否足够 我还需要加入()吗 这是我的worker类,它进行下载,从队列中抓取

我正在编写一个小型多线程http文件下载程序,希望能够在代码遇到错误时收缩可用线程

这些错误将特定于在web服务器不允许任何更多连接的情况下返回的http错误

如果我设置了一个由5个线程组成的池,那么每个线程都试图打开自己的连接并下载一块文件。服务器可能只允许2个连接,我相信会返回503个错误,我想检测到这一点并关闭一个线程,最终将池的大小限制为服务器允许的2个

我能让线自动停止吗

self.Thread_stop()是否足够

我还需要加入()吗

这是我的worker类,它进行下载,从队列中抓取数据进行处理,下载后将结果转储到resultQ中,由主线程保存到文件中

在这里,我想检测http 503并从可用池中停止/杀死/删除一个线程——当然,还要将失败的块重新添加回队列,以便其余线程将处理它

class Downloader(threading.Thread):
    def __init__(self, queue, resultQ, file_name):
        threading.Thread.__init__(self)
        self.workQ = queue
        self.resultQ = resultQ
        self.file_name = file_name

    def run(self):
        while True:
            block_num, url, start, length = self.workQ.get()
            print 'Starting Queue #: %s' % block_num
            print start
            print length

            #Download the file
            self.download_file(url, start, length)

            #Tell queue that this task is done
            print 'Queue #: %s finished' % block_num
            self.workQ.task_done()


    def download_file(self, url, start, length):        

        request = urllib2.Request(url, None, headers)
        if length == 0:
            return None
        request.add_header('Range', 'bytes=%d-%d' % (start, start + length))

        while 1:
            try:
                data = urllib2.urlopen(request)
            except urllib2.URLError, u:
                print "Connection did not start with", u
            else:
                break

        chunk = ''
        block_size = 1024
        remaining_blocks = length

        while remaining_blocks > 0:

            if remaining_blocks >= block_size:
                fetch_size = block_size
            else:
                fetch_size = int(remaining_blocks)

            try:
                data_block = data.read(fetch_size)
                if len(data_block) == 0:
                    print "Connection: [TESTING]: 0 sized block" + \
                        " fetched."
                if len(data_block) != fetch_size:
                    print "Connection: len(data_block) != length" + \
                        ", but continuing anyway."
                    self.run()
                    return

            except socket.timeout, s:
                print "Connection timed out with", s
                self.run()
                return

            remaining_blocks -= fetch_size
            chunk += data_block

        resultQ.put([start, chunk])
下面是我初始化线程池的地方,接下来我将项目放入队列

# create a thread pool and give them a queue
for i in range(num_threads):
    t = Downloader(workQ, resultQ, file_name)
    t.setDaemon(True)
    t.start()

Thread对象通过从run方法返回来终止线程——它不调用stop。如果将线程设置为守护程序模式,则不需要加入,但主线程需要加入。线程通常使用resultq报告它正在退出,而主线程则使用该信息进行连接。这有助于有序地终止流程。如果python仍在处理多个线程,那么在系统退出过程中可能会出现奇怪的错误,最好避开这一步。

您应该使用线程池来控制线程的生命周期:

然后,当线程存在时,您可以向主线程(处理线程池)发送消息,然后更改线程池的大小,并在将清空的堆栈中延迟新请求或失败的请求

Tedelay对于您为线程提供的守护进程状态是绝对正确的。不需要将它们设置为守护进程

基本上,您可以简化代码,您可以执行以下操作:

import threadpool

def process_tasks():
    pool = threadpool.ThreadPool(4)

    requests = threadpool.makeRequests(download_file, arguments)

    for req in requests:
        pool.putRequest(req) 

    #wait for them to finish (or you could go and do something else)
    pool.wait()

if __name__ == '__main__': 
    process_tasks()
其中
参数
取决于您的策略。要么给线程一个队列作为参数,然后清空队列。或者,您可以在process_任务中获取process队列,在池已满时阻塞,并在线程完成但队列不为空时打开新线程。这完全取决于您的需求和下载程序的上下文

资源:

我能让线自动停止吗

不要使用
self.\u Thread\u stop()
。退出线程的
run()
方法就足够了(您可以检查标志或从队列中读取哨兵值以知道何时退出)

在这里,我想检测http 503并从可用池中停止/杀死/删除一个线程——当然,还要将失败的块重新添加回队列,以便其余线程将处理它

class Downloader(threading.Thread):
    def __init__(self, queue, resultQ, file_name):
        threading.Thread.__init__(self)
        self.workQ = queue
        self.resultQ = resultQ
        self.file_name = file_name

    def run(self):
        while True:
            block_num, url, start, length = self.workQ.get()
            print 'Starting Queue #: %s' % block_num
            print start
            print length

            #Download the file
            self.download_file(url, start, length)

            #Tell queue that this task is done
            print 'Queue #: %s finished' % block_num
            self.workQ.task_done()


    def download_file(self, url, start, length):        

        request = urllib2.Request(url, None, headers)
        if length == 0:
            return None
        request.add_header('Range', 'bytes=%d-%d' % (start, start + length))

        while 1:
            try:
                data = urllib2.urlopen(request)
            except urllib2.URLError, u:
                print "Connection did not start with", u
            else:
                break

        chunk = ''
        block_size = 1024
        remaining_blocks = length

        while remaining_blocks > 0:

            if remaining_blocks >= block_size:
                fetch_size = block_size
            else:
                fetch_size = int(remaining_blocks)

            try:
                data_block = data.read(fetch_size)
                if len(data_block) == 0:
                    print "Connection: [TESTING]: 0 sized block" + \
                        " fetched."
                if len(data_block) != fetch_size:
                    print "Connection: len(data_block) != length" + \
                        ", but continuing anyway."
                    self.run()
                    return

            except socket.timeout, s:
                print "Connection timed out with", s
                self.run()
                return

            remaining_blocks -= fetch_size
            chunk += data_block

        resultQ.put([start, chunk])
您可以通过分离责任来简化代码:

  • download_file()
    不应尝试在无限循环中重新连接。如果有错误;让我们看看调用
    download\u file()
    的代码,如果需要,请重新提交它
  • 关于并发连接数的控制可以封装在
    信号量
    对象中。在这种情况下,线程数可能与并发连接数不同
python2.x上的import concurrent.futures:pip安装futures 从线程导入边界信号量 def下载_文件(args): nconcurrent.acquire(timeout=args['timeout'])#如果连接太多,则阻塞 # ... nconcurrent.release()#注意:不要在异常时释放它, #允许调用方处理它 #您可以将其放入字典:服务器->信号量,而不是全局 nconcurrent=BoundedSemaphore(5)#最多从5个并发连接开始 以concurrent.futures.ThreadPoolExecutor(max_workers=NUM_THREADS)作为执行器: future_to_args=dict((executor.submit(下载_文件,args),args) 对于generate_initial_download_tasks()中的参数 而future_to_参数: 对于并发的future.futures.as_完成(dict(**future_to_args)): args=future\u to_args.pop(future) 尝试: result=future.result() 例外情况除外,如e: 打印(“%r”生成异常:%s%%(参数,e)) 如果getattr(e,‘代码’)!=503: #不要减少并发连接的数量 nconcurrent.release() #重新提交 args['timeout']*=2 future_to_args[executor.submit(下载_文件,args)]=args else:#已成功下载`args` 打印('f%r返回%r'(参数,结果))
请参阅。

但正如您所看到的,只要有项目要从workQ中获取,线程就会继续运行,如果一个线程遇到503,我希望将可用线程的数量减少1。。留下剩下的线程来处理WorkQ中剩下的内容非常好的信息,谢谢!我没有看到您如何使用threadpool调整池的大小。。我一定忽略了一些明显的东西?谢谢,我需要更仔细地阅读这篇文章。我最终得出了与您所说的相同的结论,只要退出线程run(),它就会停止尝试并从队列中提取。。我喜欢你的建议,谢谢!