Python多线程、多进程编程
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多线程、多进程编程的更多相关文章
- Python多线程多进程那些事儿看这篇就够了~~
自己以前也写过多线程,发现都是零零碎碎,这篇写写详细点,填一下GIL和Python多线程多进程的坑~ 总结下GIL的坑和python多线程多进程分别应用场景(IO密集.计算密集)以及具体实现的代码模块 ...
- Python多线程多进程
一.线程&进程 对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程, ...
- python多线程网络编程
背景 使用过flask框架后,我对request这个全局实例非常感兴趣.它在客户端发起请求后会保存着所有的客户端数据,例如用户上传的表单或者文件等.那么在很多客户端发起请求时,服务器是怎么去区分不同的 ...
- Python的多进程编程
Python在2.6引入了多进程的机制,并提供了丰富的组件及api以方便编写并发应用.multiprocessing包的组件Process, Queue, Pipe, Lock等组件提供了与多线程类似 ...
- python学习笔记(十六)-Python多线程多进程
一.线程&进程 对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程, ...
- python 多线程网络编程 ( 二 )
背景 我在[第一篇文章中]已经介绍了如何实现一个多线程的todo应用,接下来我将会研究如何使这个服务器完成下面这几个功能. 1.使用正则表达式解析用户发送的请求数据: 2.使用ThreadLocal技 ...
- python多线程/多进程
thread和threading的区别 threading相对与thread是更高级别的线程管理模块 thread和threading模块中的一些属性会有冲突 thread模块拥有的同步原因实际上只有 ...
- python中的多线程和多进程编程
注意:多线程和多线程编程是不同的!!! 第一点:一个进程相当于一个要执行的程序,它会开启一个主线程,多线程的话就会再开启多个子线程:而多进程的话就是一个进程同时在多个核上进行: 第二点:多线程是一种并 ...
- Python多进程编程
转自:Python多进程编程 阅读目录 1. Process 2. Lock 3. Semaphore 4. Event 5. Queue 6. Pipe 7. Pool 序. multiproces ...
- 多线程&多进程解析:Python、os、sys、Queue、multiprocessing、threading
当涉及到操作系统的时候,免不了要使用os模块,有时还要用到sys模块. 设计到并行程序,一般开单独的进程,而不是线程,原因是python解释器的全局解释器锁GIL(global interpreter ...
随机推荐
- 【Oracle】使用exit,return,continue跳出循环
[Oracle]使用exit,return,continue跳出循环 exit是结束循环,但还会继续执行程序包中其他的内容 return则是直接中断整个程序 continue的作用是终止本次循环,开始 ...
- 力扣233(java)-数字1的个数(困难)
题目: 给定一个整数 n,计算所有小于等于 n 的非负整数中数字 1 出现的个数. 示例 1: 输入:n = 13输出:6示例 2: 输入:n = 0输出:0 提示: 0 <= n <= ...
- 【pytorch学习】之数据操作
1 数据操作 为了能够完成各种数据操作,我们需要某种方法来存储和操作数据.通常,我们需要做两件重要的事:(1)获取数据: (2)将数据读入计算机后对其进行处理.如果没有某种方法来存储数据,那么获取数据 ...
- 如何用一个插件解决 Serverless 灰度发布难题?
简介: 我们可以发现相比使用控制台进行灰度发布,使用 FC-Canary 插件免去了用户手动创建版本.发布别名.关联触发器和管理自定义域名的麻烦,使用起来非常方便. 作者:长淇 导读 本文适合: 想了 ...
- 基于容器服务 ACK 发行版打造 CNStack 社区版
简介:本文将介绍如何使用 ACK Distro 作为基础镜像打造 CNStack 社区版以及CNStack 社区版中的容器服务 ACK 敏捷版产品如何帮助用户更好的使用容器平台能力. 作者:临石 C ...
- 最佳实践丨三种典型场景下的云上虚拟IDC(私有池)选购指南
简介:业务上云常态化,业务在云上资源的选购.弹性交付.自助化成为大趋势.不同行业的不同客户,业务发展阶段不一样,云上资源的成本投入在业务整体成本占比也不一样,最小化成本投入.最大化业务收益始终是不同 ...
- Flink 最佳实践之使用 Canal 同步 MySQL 数据至 TiDB
简介: 本文将介绍如何将 MySQL 中的数据,通过 Binlog + Canal 的形式导入到 Kafka 中,继而被 Flink 消费的案例. 一. 背景介绍 本文将介绍如何将 MySQL 中的数 ...
- [FAQ] uni-app 运行微信小程序 main.wxss 报错 unexpected token "$"
检查一下你是否在 App.vue 中有手动操作引入过 uni.scss,比如下面的 import: <style lang="scss"> @import url(&q ...
- [Go] CORS 支持多个 origin 访问的思路 (Access-Control-Allow-Origin 部分)
以下为局部伪代码,仅供参考: var allowOrigin string allowOrigins := config.AppConf.Get("middleware.cors.allow ...
- dotnet 使用 XWT 构建跨平台客户端 入门篇
本文告诉大家如何入门开始开发一个基于 mono 组织开源的 XWT 跨平台客户端 UI 框架的应用,本文的 xwt 是在 GitHub 上完全开源的,基于 MIT 协议的,底层采用 GTK# 的 UI ...