Python 如何创建一个线程池


Posted in Python onJuly 28, 2020

问题

你创建一个工作者线程池,用来响应客户端请求或执行其他的工作。

解决方案

concurrent.futures 函数库有一个 ThreadPoolExecutor 类可以被用来完成这个任务。 下面是一个简单的TCP服务器,使用了一个线程池来响应客户端:

from socket import AF_INET, SOCK_STREAM, socket
from concurrent.futures import ThreadPoolExecutor

def echo_client(sock, client_addr):
  '''
  Handle a client connection
  '''
  print('Got connection from', client_addr)
  while True:
    msg = sock.recv(65536)
    if not msg:
      break
    sock.sendall(msg)
  print('Client closed connection')
  sock.close()

def echo_server(addr):
  pool = ThreadPoolExecutor(128)
  sock = socket(AF_INET, SOCK_STREAM)
  sock.bind(addr)
  sock.listen(5)
  while True:
    client_sock, client_addr = sock.accept()
    pool.submit(echo_client, client_sock, client_addr)

echo_server(('',15000))

如果你想手动创建你自己的线程池, 通常可以使用一个Queue来轻松实现。下面是一个稍微不同但是手动实现的例子:

from socket import socket, AF_INET, SOCK_STREAM
from threading import Thread
from queue import Queue

def echo_client(q):
  '''
  Handle a client connection
  '''
  sock, client_addr = q.get()
  print('Got connection from', client_addr)
  while True:
    msg = sock.recv(65536)
    if not msg:
      break
    sock.sendall(msg)
  print('Client closed connection')

  sock.close()

def echo_server(addr, nworkers):
  # Launch the client workers
  q = Queue()
  for n in range(nworkers):
    t = Thread(target=echo_client, args=(q,))
    t.daemon = True
    t.start()

  # Run the server
  sock = socket(AF_INET, SOCK_STREAM)
  sock.bind(addr)
  sock.listen(5)
  while True:
    client_sock, client_addr = sock.accept()
    q.put((client_sock, client_addr))

echo_server(('',15000), 128)

使用 ThreadPoolExecutor 相对于手动实现的一个好处在于它使得 任务提交者更方便的从被调用函数中获取返回值。例如,你可能会像下面这样写:

from concurrent.futures import ThreadPoolExecutor
import urllib.request

def fetch_url(url):
  u = urllib.request.urlopen(url)
  data = u.read()
  return data

pool = ThreadPoolExecutor(10)
# Submit work to the pool
a = pool.submit(fetch_url, 'http://www.python.org')
b = pool.submit(fetch_url, 'http://www.pypy.org')

# Get the results back
x = a.result()
y = b.result()

例子中返回的handle对象会帮你处理所有的阻塞与协作,然后从工作线程中返回数据给你。 特别的,a.result() 操作会阻塞进程直到对应的函数执行完成并返回一个结果。

讨论

通常来讲,你应该避免编写线程数量可以无限制增长的程序。例如,看看下面这个服务器:

from threading import Thread
from socket import socket, AF_INET, SOCK_STREAM

def echo_client(sock, client_addr):
  '''
  Handle a client connection
  '''
  print('Got connection from', client_addr)
  while True:
    msg = sock.recv(65536)
    if not msg:
      break
    sock.sendall(msg)
  print('Client closed connection')
  sock.close()

def echo_server(addr, nworkers):
  # Run the server
  sock = socket(AF_INET, SOCK_STREAM)
  sock.bind(addr)
  sock.listen(5)
  while True:
    client_sock, client_addr = sock.accept()
    t = Thread(target=echo_client, args=(client_sock, client_addr))
    t.daemon = True
    t.start()

echo_server(('',15000))

尽管这个也可以工作, 但是它不能抵御有人试图通过创建大量线程让你服务器资源枯竭而崩溃的攻击行为。 通过使用预先初始化的线程池,你可以设置同时运行线程的上限数量。

你可能会关心创建大量线程会有什么后果。 现代操作系统可以很轻松的创建几千个线程的线程池。 甚至,同时几千个线程等待工作并不会对其他代码产生性能影响。 当然了,如果所有线程同时被唤醒并立即在CPU上执行,那就不同了——特别是有了全局解释器锁GIL。 通常,你应该只在I/O处理相关代码中使用线程池。

创建大的线程池的一个可能需要关注的问题是内存的使用。 例如,如果你在OS X系统上面创建2000个线程,系统显示Python进程使用了超过9GB的虚拟内存。 不过,这个计算通常是有误差的。当创建一个线程时,操作系统会预留一个虚拟内存区域来 放置线程的执行栈(通常是8MB大小)。但是这个内存只有一小片段被实际映射到真实内存中。 因此,Python进程使用到的真实内存其实很小 (比如,对于2000个线程来讲,只使用到了70MB的真实内存,而不是9GB)。 如果你担心虚拟内存大小,可以使用 threading.stack_size() 函数来降低它。例如:

import threading
threading.stack_size(65536)

如果你加上这条语句并再次运行前面的创建2000个线程试验, 你会发现Python进程只使用到了大概210MB的虚拟内存,而真实内存使用量没有变。 注意线程栈大小必须至少为32768字节,通常是系统内存页大小(4096、8192等)的整数倍。

以上就是Python 如何创建一个线程池的详细内容,更多关于Python 创建线程池的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
采用Psyco实现python执行速度提高到与编译语言一样的水平
Oct 11 Python
Python类属性与实例属性用法分析
May 09 Python
简单介绍Python中的round()方法
May 15 Python
浅析Python中的join()方法的使用
May 19 Python
Python文件及目录操作实例详解
Jun 04 Python
PyCharm使用教程之搭建Python开发环境
Jun 07 Python
Python中str is not callable问题详解及解决办法
Feb 10 Python
Windows下安装python2和python3多版本教程
Mar 30 Python
神经网络(BP)算法Python实现及应用
Apr 16 Python
django中使用POST方法获取POST数据
Aug 20 Python
python之生成多层json结构的实现
Feb 27 Python
使用Keras预训练模型ResNet50进行图像分类方式
May 23 Python
matplotlib subplot绘制多个子图的方法示例
Jul 28 #Python
python爬虫用mongodb的理由
Jul 28 #Python
python爬虫数据保存到mongoDB的实例方法
Jul 28 #Python
Python3爬虫带上cookie的实例代码
Jul 28 #Python
python实现批处理文件
Jul 28 #Python
Python3安装模块报错Microsoft Visual C++ 14.0 is required的解决方法
Jul 28 #Python
python爬虫请求头设置代码
Jul 28 #Python
You might like
Discuz! 5.0.0论坛程序中加入一段js代码,让会员点击下载附件前自动弹出提示窗口
2007/04/18 PHP
PHP实现手机号码中间四位用星号(*)隐藏的自定义函数分享
2014/09/27 PHP
PHP实现mysqli批量执行多条语句的方法示例
2017/07/22 PHP
Yii框架小部件(Widgets)用法实例详解
2020/05/15 PHP
非常好的js代码
2006/06/27 Javascript
javascript 解析后的xml对象的读取方法细解
2009/07/25 Javascript
javascript 处理事件绑定的一些兼容写法
2009/12/24 Javascript
js在输入框屏蔽按键,只能键入数字的示例代码
2014/01/03 Javascript
JavaScript中的console.time()函数详细介绍
2014/12/29 Javascript
javascript使用smipleChart实现简单图表
2015/01/02 Javascript
详解javascript函数的参数
2015/11/10 Javascript
基于Jquery实现焦点图淡出淡入效果
2015/11/30 Javascript
获取阴历(农历)和当前日期的js代码
2016/02/15 Javascript
JS实现简单的二元方程计算器功能示例
2017/01/03 Javascript
Js利用console计算代码运行时间的方法示例
2017/09/24 Javascript
vue使用keep-alive实现数据缓存不刷新
2017/10/21 Javascript
浅析Vue中method与computed的区别
2018/03/06 Javascript
浅谈React 服务器端渲染的使用
2018/05/08 Javascript
vue实现搜索功能
2019/05/28 Javascript
VUE实现强制渲染,强制更新
2019/10/29 Javascript
Vue记住滚动条和实现下拉加载的完美方法
2020/07/31 Javascript
python 实现删除文件或文件夹实例详解
2016/12/04 Python
SQLite3中文编码 Python的实现
2017/01/11 Python
Python操作MongoDB详解及实例
2017/05/18 Python
pandas 将list切分后存入DataFrame中的实例
2018/07/03 Python
让代码变得更易维护的7个Python库
2018/10/09 Python
详解python列表生成式和列表生成式器区别
2019/03/27 Python
python中的decimal类型转换实例详解
2019/06/26 Python
Python发送邮件封装实现过程详解
2020/05/09 Python
缓解脚、腿和背部疼痛:Z-CoiL鞋
2019/03/12 全球购物
SQL Server面试题
2013/04/04 面试题
优秀团员个人的自我评价
2013/10/02 职场文书
小学生竞选班长演讲稿
2014/04/24 职场文书
2015年庆祝国庆节66周年演讲稿
2015/07/30 职场文书
文艺部部长竞选稿
2015/11/21 职场文书
Java死锁的排查
2022/05/11 Java/Android