1 简介

参考:https://www.bilibili.com/video/BV1bK411A7tV?spm_id_from=333.999.0.0

python线程池ThreadPoolExecutor与进程池ProcessPoolExecutor - HarvardFly - 博客园 (cnblogs.com)

使用的库:concurrent.futures

官方介绍:

The concurrent.futures module provides a high-level interface for asynchronously executing callables.

The asynchronous execution can be performed with threads, using ThreadPoolExecutor, or separate processes, using ProcessPoolExecutor. Both implement the same interface, which is defined by the abstract Executor class.

1.1 基本知识

多线程:threading,只能利用单核CPU(由于GIL)

多进程:multiprocessing,利用多核CPU

异步IO:asyncio,在单线程中利用CPU和IO同时执行的原理,实现函数异步执行。

使用Lock对资源加锁,防止冲突访问。

使用Queue实现不同线程/进程之间的数据通信。

使用线程池Pool/进程池Pool,简化线程/进程任务提交、等待结果、获取结果。

使用subprocess启动外部程序的进程,并进程输入输出交互。

1.1.1 CPU密集型(CPU-Bound)和IO密集型(IO-Bound)

CPU密集型也叫计算密集型,是指I/O在很短的时间就可以完成,CPU需要大量的计算和处理,特点是CPU占用率很高。例如:压缩解压缩、加密解密、正则表达式搜索。

IO密集型指的是系统运行大部分状况是在等I/O(磁盘/内存)的读写操作,CPU占用率很低。例如:文件处理程序、网络爬虫程序、读写数据程序。

1.1.2 多线程、多进程、多协程的对比

多进程(multiprocessing):

优点:可以利用多核CPU运行

缺点:占用资源多,可启动数目比线程少。

适用于:CPU密集型计算

多线程(threading):

优点:相比进程,更轻量级、占用资源少

缺点:相比进程,多先册亨共你只能并发执行,不能利用多CPU(GIL);相比协程,启动数目有限制,占用内存资源,有线程切换开销。

适用于:I/O密集型计算,同时运行的任务数要求不多。

多协程(asyncio):

优点:内存开销最少,启动协程数量最多。

缺点:支持的库有限制,代码实现复杂。

适用于:IO密集型计算,需要超多任务运行,但有现成库支持的场景。

一个进程中可以启动多个线程,一个线程中可以启动多个协程。

1.1.3 GIL(全局解释器锁,global interpreter lock)

GIL是计算机设计时用于同步线程的一种机制,它使得任何时刻仅有一个线程在执行,即便在多核处理器上,使用GIL的解释器也只允许同一时间执行一个线程。

1.2 python多线程使用

使用threading库:

创建线程使用:threading.Thread,比如t = threading.Thread(target=func, args=())

启动线程使用:t.start()

等待结束使用:t.join()

多线程间通信:

使用queue.Queue,可以用于多线程间的线程安全的数据通信。

导入类库:import queue

创建Queue:q = queue.Queue()

添加元素:q.put(item),阻塞访问

获取元素:item = q.get(),阻塞访问

查看元素的个数:q.qsize()

判断是否为空:q.empty()

判断是否已满:q.full()

线程安全、Lock模块:

多线程对共享资源的访问(比如全局变量)需要互斥访问。

使用方式一:

import threading

lock = threading.Lock()
lock.acquire()
try:
# do something
finally:
lock.release()

使用方式二:

lock = threading.Lock()
with lock:
# do something

线程池:

新建线程需要分配资源、终止线程需要回收资源,使用线程池可以减去新建/终止的开销。

线程一个任务队列以及一个可重用的线程就实现了一个线程池。

线程池的好处:

提升性能:减去了大量创建、终止线程的开销,重用了线程资源。

适用场景:适合处理突发性大量请求或需要大量线程完成任务、但实际任务处理时间较短。

防御功能:能有效避免系统因为创建线程过多,从而导致系统负荷过大相应变慢的问题。

代码优势:使用线程池的语法比自己创建线程更加简洁。

进程池:

多进程,系统中运行了多个python 解释器, 他们真正的只并行计算, 但是也相应的会有一些负担。

对于CPU密集型操作,使用进程池效率比线程池更高。

2 Executor Objects

线程池和进程池都是通过抽象类Executor定义的,其说明如下:

class concurrent.futures.Executor
# An abstract class that provides methods to execute calls asynchronously. It should not be used directly, but through its concrete subclasses.
submit(fn, *args, **kwargs)
map(func, *iterables, timeout=None, chunksize=1)
shutdown(wait=True)

2.1 ThreadPoolExecutor

ThreadPoolExecutor是Executor类的一个子类。

2.1.1 submit函数

通过submit函数提交执行的函数到线程池中,done()判断线程执行的状态:

import time
from concurrent.futures import ThreadPoolExecutor def get_thread_time(times):
time.sleep(times)
return times # 创建线程池 指定最大容纳数量为4
executor = ThreadPoolExecutor(max_workers=4)
# 通过submit提交执行的函数到线程池中,分别sleep 1s 2s 3s 4s
task1 = executor.submit(get_thread_time, (1))
task2 = executor.submit(get_thread_time, (2))
task3 = executor.submit(get_thread_time, (3))
task4 = executor.submit(get_thread_time, (4))
print("task1:{} ".format(task1.done()))
print("task2:{}".format(task2.done()))
print("task3:{} ".format(task3.done()))
print("task4:{}".format(task4.done()))
time.sleep(2.5)
print('after 2.5s {}'.format('-'*20)) done_map = {
"task1":task1.done(),
"task2":task2.done(),
"task3":task3.done(),
"task4":task4.done()
}
# 2.5秒之后,线程的执行状态
for task_name, done in done_map.items():
if done:
print("{}:completed".format(task_name))

执行结果:

task1:False
task2:False
task3:False
task4:False
after 2.5s --------------------
task1:completed
task2:completed

2.1.2 wait函数

通过wait()判断线程执行的状态:

# wait接受3个参数,fs表示执行的task序列;
# timeout表示等待的最长时间,超过这个时间即使线程未执行完成也将返回;
# return_when表示wait返回结果的条件,默认为ALL_COMPLETED全部执行完成再返回:
wait(fs, timeout=None, return_when=ALL_COMPLETED)
# 通过wait()判断线程执行的状态
def get_thread_time(times):
time.sleep(times)
return times start = time.time()
executor = ThreadPoolExecutor(max_workers=4)
task_list = [executor.submit(get_thread_time, times) for times in [1, 2, 3, 4]]
i = 1
for task in task_list:
print("task{}:{}".format(i, task))
i += 1
print(wait(task_list, timeout=2.5))

执行结果:

task1:<Future at 0x2c85919c5f8 state=running>
task2:<Future at 0x2c859278da0 state=running>
task3:<Future at 0x2c85927d2e8 state=running>
task4:<Future at 0x2c85927d4a8 state=running>
DoneAndNotDoneFutures(done={<Future at 0x2c859278da0 state=finished returned int>, <Future at 0x2c85919c5f8 state=finished returned int>}, not_done={<Future at 0x2c85927d4a8 state=running>, <Future at 0x2c85927d2e8 state=running>})

2.1.3 map函数

通过map返回线程的执行结果,map的返回是有序的,它会根据第二个参数的顺序返回执行的结果:

# 第一个参数fn是线程执行的函数;
# 第二个参数接受一个可迭代对象;
# 第三个参数timeout跟wait()的timeout一样,但由于map是返回线程执行的结果,如果timeout小于线程执行时间会抛异常TimeoutError。
map(fn, *iterables, timeout=None)
def get_thread_time(times):
time.sleep(times)
return times executor = ThreadPoolExecutor(max_workers=4) i = 1
for result in executor.map(get_thread_time,[2,3,1,4]):
print("task{}:{}".format(i, result))
i += 1

执行结果:

task1:2
task2:3
task3:1
task4:4

2.1.4 as_completed函数

as_completed返回的顺序是线程执行结束的顺序,最先执行结束的线程最早返回。

# 第一个是执行的线程列表;
# 第二个参数timeout与map的timeout一样,当timeout小于线程执行时间会抛异常TimeoutError。
as_completed(fs, timeout=None)
def get_thread_time(times):
time.sleep(times)
return times start = time.time()
executor = ThreadPoolExecutor(max_workers=4)
task_list = [executor.submit(get_thread_time, times) for times in [2, 3, 1, 4]]
task_to_time = OrderedDict(zip(["task1", "task2", "task3", "task4"],[2, 3, 1, 4]))
task_map = OrderedDict(zip(task_list, ["task1", "task2", "task3", "task4"])) for result in as_completed(task_list):
task_name = task_map.get(result)
print("{}:{}".format(task_name,task_to_time.get(task_name)))

执行结果:

task3:1
task1:2
task2:3
task4:4

2.2 ProcessPoolExecutor

ProcessPoolExecutor是Executor类的一个子类。

对于频繁的cpu操作,由于GIL锁的原因,多个线程只能用一个cpu,这时多进程的执行效率要比多线程高。

import math
import time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor PRIMES = [112272535095293] * 20 def is_prime(n):
if n == 1:
return False
if n == 2:
return True
if n % 2 == 0:
return False
sqrt_n = int(math.floor(math.sqrt(n)))
for i in range(3, sqrt_n + 1, 2):
if n % 2 == 0:
return False
return True def single_thread():
for number in PRIMES:
is_prime(number) def multi_thread():
with ThreadPoolExecutor() as pool:
pool.map(is_prime, PRIMES) def multi_processor():
with ProcessPoolExecutor() as pool:
pool.map(is_prime, PRIMES) if __name__ == '__main__':
start = time.time()
single_thread()
end = time.time()
print("single thread cost: ", end-start, "seconds") start = time.time()
multi_thread()
end = time.time()
print("multi thread cost: ", end-start, "seconds") start = time.time()
multi_processor()
end = time.time()
print("multi processor cost: ", end-start, "seconds") ########## 测试结果 ##########
# single thread cost: 11.16333556175232 seconds
# multi thread cost: 10.30142068862915 seconds
# multi processor cost: 3.722104549407959 seconds

Python多线程、多进程编程的更多相关文章

  1. Python多线程多进程那些事儿看这篇就够了~~

    自己以前也写过多线程,发现都是零零碎碎,这篇写写详细点,填一下GIL和Python多线程多进程的坑~ 总结下GIL的坑和python多线程多进程分别应用场景(IO密集.计算密集)以及具体实现的代码模块 ...

  2. Python多线程多进程

    一.线程&进程 对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程, ...

  3. python多线程网络编程

    背景 使用过flask框架后,我对request这个全局实例非常感兴趣.它在客户端发起请求后会保存着所有的客户端数据,例如用户上传的表单或者文件等.那么在很多客户端发起请求时,服务器是怎么去区分不同的 ...

  4. Python的多进程编程

    Python在2.6引入了多进程的机制,并提供了丰富的组件及api以方便编写并发应用.multiprocessing包的组件Process, Queue, Pipe, Lock等组件提供了与多线程类似 ...

  5. python学习笔记(十六)-Python多线程多进程

    一.线程&进程 对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程, ...

  6. python 多线程网络编程 ( 二 )

    背景 我在[第一篇文章中]已经介绍了如何实现一个多线程的todo应用,接下来我将会研究如何使这个服务器完成下面这几个功能. 1.使用正则表达式解析用户发送的请求数据: 2.使用ThreadLocal技 ...

  7. python多线程/多进程

    thread和threading的区别 threading相对与thread是更高级别的线程管理模块 thread和threading模块中的一些属性会有冲突 thread模块拥有的同步原因实际上只有 ...

  8. python中的多线程和多进程编程

    注意:多线程和多线程编程是不同的!!! 第一点:一个进程相当于一个要执行的程序,它会开启一个主线程,多线程的话就会再开启多个子线程:而多进程的话就是一个进程同时在多个核上进行: 第二点:多线程是一种并 ...

  9. Python多进程编程

    转自:Python多进程编程 阅读目录 1. Process 2. Lock 3. Semaphore 4. Event 5. Queue 6. Pipe 7. Pool 序. multiproces ...

  10. 多线程&多进程解析:Python、os、sys、Queue、multiprocessing、threading

    当涉及到操作系统的时候,免不了要使用os模块,有时还要用到sys模块. 设计到并行程序,一般开单独的进程,而不是线程,原因是python解释器的全局解释器锁GIL(global interpreter ...

随机推荐

  1. ASP.NET CORE 框架揭秘读书笔记系列——命令行程序的创建(一)

    一.dotnet --info 查看本机开发环境 dotnet --info  会显示本机安装的SDK版本.运行时环境.运行时版本 二.利用命令行创建.NET项目 我们不仅可以利用脚手架模版创建各种类 ...

  2. javascript:eval()的用法

    eval() 是 JavaScript 中的一个全局函数,它可以计算或执行参数.如果参数是表达式,则 eval() 计算表达式:如果参数是一个或多个 JavaScript 语句,则 eval() 执行 ...

  3. ping的常用方法

    ping的常用方法 ping +ip tcping +ip+端口号(例如 tcping 127.0.0.1 8080) telnet +ip+端口号 nc -nzv +ip+端口号(linux用)

  4. 通过ORPO技术微调 llama3大模型(Fine-tune Llama 3 with ORPO)

    1f45bd1e8577af66a05f5e3fadb0b29 通过ORPO对llama进行微调 前言 ORPO是一种新颖的微调技术,它将传统的监督微调和偏好对齐阶段整合到一个过程中.这减少了训练所需 ...

  5. Serverless在游戏运营行业进行数据采集分析的最佳实践

    简介: 这个架构不光适用于游戏运营行业,其实任何大数据采集传输的场景都是适用的,目前也已经有很多客户正在基于Serverless的架构跑在生产环境,或者正走在改造Serverless 架构的路上. 众 ...

  6. dotnet 警惕 C# 的 is var 写法

    本文将和大家介绍 C# 语言设计里面,我认为比较坑的一个语法.通过 is var 的写法,会让开发者误以为 null 是不被包含的,然而事实是在这里的 var 是被赋予含义的,将被允许 null 通过 ...

  7. Git基础使用指南-命令详解

    Software is like sex: it's better when it's free. -- Linus Torvalds 前情须知 -O- 工作流程 首先要明确的是Git的工作流程,你使 ...

  8. Google高精度的搜索技巧

    利用"关键字",完全匹配搜索(双引号精准搜索). 利用"关键字:档案类型",搜寻特定档案类型. 例如:"简历:doc"."钢铁侠: ...

  9. Codeforces Round 932 (Div. 2) ABCD

    A. Entertainment in MAC 题意:给定字符串 \(S\),有两种操作,每次操作其中之一: 把 \(S\) 变为 \(S\) 的翻转 \(T\). 把 \(S\) 变为 \(S + ...

  10. 详解csrf(跨站请求伪造)

    1.什么是csrf (csrf攻击原理)? 用户正常访问A网站,A网站设置cookie被用户浏览器保存 用户不关闭浏览器,直接访问恶意网站,该恶意网站内隐藏式内嵌了A网站接口的请求链接 触发该请求链接 ...