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 相关文章推荐
Python操作列表的常用方法分享
Feb 13 Python
使用XML库的方式,实现RPC通信的方法(推荐)
Jun 14 Python
Python3 操作符重载方法示例
Nov 23 Python
对python中的six.moves模块的下载函数urlretrieve详解
Dec 19 Python
用python实现英文字母和相应序数转换的方法
Sep 18 Python
Python求解正态分布置信区间教程
Nov 20 Python
python+tifffile之tiff文件读写方式
Jan 13 Python
在python里创建一个任务(Task)实例
Apr 25 Python
python异步Web框架sanic的实现
Apr 27 Python
利用python+request通过接口实现人员通行记录上传功能
Jan 13 Python
Python采集股票数据并制作可视化柱状图
Apr 04 Python
Python读取和写入Excel数据
Apr 20 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
PHP.MVC的模板标签系统(三)
2006/09/05 PHP
一个php作的文本留言本的例子(三)
2006/10/09 PHP
如何写php程序?
2006/12/08 PHP
PHP中str_replace函数使用小结
2008/10/11 PHP
对于Laravel 5.5核心架构的深入理解
2018/02/22 PHP
PHP连接MySQL数据库三种实现方法
2020/12/10 PHP
PHP7变量处理机制修改
2021/03/09 PHP
PHP filter_var() 函数, 验证判断EMAIL,URL等
2021/03/09 PHP
基于jQuery的input输入框下拉提示层(自动邮箱后缀名)
2012/06/14 Javascript
JS实现很酷的水波文字特效实例
2015/02/26 Javascript
JavaScript中pop()方法的使用教程
2015/06/09 Javascript
JS针对浏览器窗口关闭事件的监听方法集锦
2016/06/24 Javascript
JavaScript实现网页头部进度条刷新
2017/04/16 Javascript
JavaScript创建对象的七种方式(推荐)
2017/06/26 Javascript
vue中$refs的用法及作用详解
2018/04/24 Javascript
Vue.JS实现垂直方向展开、收缩不定高度模块的JS组件
2018/06/19 Javascript
微信小程序 自定义复选框实现代码实例
2019/09/04 Javascript
JS eval代码快速解密实例解析
2020/04/23 Javascript
vue 出现data-v-xxx的原因及解决
2020/08/04 Javascript
js实现带有动画的返回顶部
2020/08/09 Javascript
[28:57]EG vs VGJ.T 2018国际邀请赛小组赛BO2 第二场 8.16
2018/08/16 DOTA
简单介绍Python的Django框架的dj-scaffold项目
2015/05/30 Python
python 截取 取出一部分的字符串方法
2017/03/01 Python
Android 兼容性问题:java.lang.UnsupportedOperationException解决办法
2017/03/19 Python
Python使用openpyxl读写excel文件的方法
2017/06/30 Python
python 调用钉钉机器人的方法
2019/02/20 Python
查看端口并杀进程python脚本代码
2019/12/17 Python
Spartoo葡萄牙鞋类网站:线上销售鞋履与时尚配饰
2017/01/11 全球购物
欧洲最大的品牌水上运动服装和设备在线零售商:Wuituit Outlet
2018/05/05 全球购物
临床医学大学生求职信
2013/09/28 职场文书
团员的自我评价
2013/12/01 职场文书
我们的节日清明节活动总结
2014/04/30 职场文书
好的旅游活动方案
2014/08/19 职场文书
无刑事犯罪记录证明范本
2014/09/29 职场文书
2015年科协工作总结
2015/05/19 职场文书
2019个人年度目标制定攻略!
2019/07/12 职场文书