Pythpn并发编程——多线程与协程

1. 进程与线程

1.1 概念上
  • 对于操作系统来说,一个任务就是一个进程Process,在一个进程内部,要同时干很多事情,就需要同时运行多个子任务,进程内的这些子任务就称为线程Thread
  • 操作系统是让各个任务交替执行实现支持多任务的,真正地同时执行多任务需要多核CPU才能实现
  • 线程是最小的执行单元,一个进程至少有一个线程,如何调读进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间
1.2 多进程与多线程——同时执行多个任务

要实现多任务,设计Master-Worker模式,Master负责分配任务,Worker负责执行任务

  • 多进程模式

    • 启动多个进程,每个进程只有一个线程,多个进程可以一块执行多个任务
    • 最大的优点:稳定性高,一个子进程崩溃了,不会影响主进程和其他子进程
    • 缺点:创建进程的开销大
  • 多线程模式
    • 启动一个进程,在一个进程内启动多个线程,多个线程也可以一块执行多个任务
    • 致命缺点:任何一个线程挂掉都可能造成整个进程崩溃,因为所有线程共享进程的内存
  • 多进程+多线程模式
    • 实际很少采用

2. 并发和并行

并发:不是指同一时刻有多个操作同时进行,实际上,在某个特定时刻,只允许有一个操作发生,线程/任务之间互相切换,直到完成,threadingasyncio

  • 通常应用于I/O操作频繁的场景,例如从网站上下载多个文件

并行:同一时刻,同时发生,multi-processing,m个处理器,开m个进程

3. Python多线程——futures

3.1 多线程用法
  • 1.导入future模块,Python中的future模块,位于concurrent.futures 和 asyncio 中
  • 2.创建线程池,函数ThreadPoolExecutor(max_workers=5),max_workers设置线程个数
  • 3.调用,map()函数
import concurrent.futures
import requests
import threading
import time def download_one(url):
resp = requests.get(url)
print('Read {} from {}'.format(len(resp.content), url)) def download_all(sites):
# 并发模式,创建了一个线性池,总共有5个线性可以分配使用
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
# 对sites中的每个元素,并发地调用函数download_one
executor.map(download_one, sites)
# 并行模式,创建进程池,系统自动返回CPU的数量作为可以调用的进程数
# with concurrent.futures.ProcessPoolExecutor() as executor: # # 另一种写法
# def download_all(sites):
# with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
# to_do =[] #
# for site in sites:
# future = executor.submit(download_one, site)
# to_do.append(future)
# for future in concurrent.futures.as_completed(to_do):
# future.result() def main():
sites = [
'https://en.wikipedia.org/wiki/Portal:Arts',
'https://en.wikipedia.org/wiki/Portal:History',
'https://en.wikipedia.org/wiki/Portal:Society',
'https://en.wikipedia.org/wiki/Portal:Biography'
]
start_time = time.perf_counter()
download_all(sites)
end_time = time.perf_counter()
print(f'Download {len(sites)} sites in {end_time - start_time} seconds') if __name__ == '__main__':
main()
Read 182102 from https://en.wikipedia.org/wiki/Portal:Arts
Read 245181 from https://en.wikipedia.org/wiki/Portal:Society
Read 206928 from https://en.wikipedia.org/wiki/Portal:History
Read 336222 from https://en.wikipedia.org/wiki/Portal:Biography
Download 4 sites in 0.22546189799322747 seconds
3.2. 为什么多线程每次只允许只能有一个线程执行?

​ 全局解释器锁的存在,GIL(Global Interpreter Lock)

3.3 多线程的缺点
  • 多线程运行过程容易被打断,因此有可能出现 race condition 的情况
  • 线程切换本身存在一定的损耗,线程数不能无限增加

4. python协程——asyncio

4.1 概念

单线程的异步编程模型称为协程。在执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行,注意,这不是函数调用。

Async异步:不同操作间可以相互交替执行,如果其中的某个操作被block了,程序并不会等待,而是找出可执行的操作继续执行

sync同步:指操作一个接一个地执行,下一个操作必须等上一个操作完成后才能执行

4.2 Asyncio原理

event loop对象维护两个任务列表,预备状态和等待状态,选取预备状态的一个任务,使其运行,一直到这个任务把控制权交还给event loop为止,当任务把控制权交还给event loop时,如果任务完成,它则将其放到预备状态的列表,否则,放在等待状态的列表,然后遍历等待状态的列表,查看它们是否完成,而原先在预备状态列表的任务位置仍旧不变,因为它们还未运行。当所有任务被重新放置在合适的列表后,新一轮的循环又开始了。

4.3 如何使用?
  1. 导入内置库 asyncio

  2. async 修饰词声明异步函数,调用异步函数,便可得到一个协程对象

  3. 协程的执行:

    • 通过await来调用,执行效果和正常执行一样,会阻塞在这里,进入被调用的协程函数,执行完毕返回后再继续
  • asynio.creat_task(调用异步函数) 创建任务

    • asynio.run(main())作为主程序的入口函数,触发运行
import asyncio

async def crawl_page(url):
print('crawling {}'.format(url))
sleep_time = int(url.split('_')[-1])
await asyncio.sleep(sleep_time) # 从当前任务切出,事件调读器开始调度
print('OK {}'.format(url)) # 任务完成后,从事件循环中退出 async def main(urls):
tasks = [asyncio.create_task(crawl_page(url)) for url in urls] # 列表生成式
for task in tasks: # 多个任务被创建,进入事件循环等待运行
await task # 执行,用户选择从当前主任务中切出,事件调度器开始调度
# await asyncio.gather(*tasks) asyncio.run(main(['url_1', 'url_2', 'url_3', 'url_4'])) ########## 输出 ########## crawling url_1
crawling url_2
crawling url_3
crawling url_4
OK url_1
OK url_2
OK url_3
OK url_4
import asyncio

async def worker_1():
await asyncio.sleep(1) # 4.从当前任务切出,事件调读器开始调度任务2
return 1 # 7.1秒后,事件调读器将控制权重新传给任务1,返回1,任务1完成,从事件循环中退出,并把控制器传给主任务 async def worker_2(): # 协程运行时出现错误
await asyncio.sleep(2) #5.从当前任务切出,事件调读器开始调度任务3,
return 2 / 0 # 8.2秒后,事件调读器将控制器重新传给任务2,运行出错,从事件循环中退出,控制器传给主任务 async def worker_3():
await asyncio.sleep(3) # 6.从当前任务中切出,事件调读器暂停调度
return 3 # 9.触发限定运行规则,任务3被取消,退出事件循环 async def main():
task_1 = asyncio.create_task(worker_1()) # 2.任务123被创建,并进入事件循环等待运行
task_2 = asyncio.create_task(worker_2())
task_3 = asyncio.create_task(worker_3()) await asyncio.sleep(2) # 给任务3限定运行时间,一旦超时就取消
task_3.cancel() res = await asyncio.gather(task_1, task_2, task_3, return_exceptions=True) # 3.执行任务,用户选择从当前的主任务中切出,事件调读器开始调度任务1
print(res) # 10.主任务输出res,协程任务结束,事件循环结束 asyncio.run(main()) # 1.程序进入main()函数,事件循环开启 ########## 输出 ########## [1, ZeroDivisionError('division by zero'), CancelledError()]
4.4. 协程的优点
  • 协程是单线程的,但其内部 event loop 的机制,可以让它并发地运行多个不同的任务,并且比多线程享有更大的自主控制权。
  • Asyncio 中的任务,在运行过程中不会被打断,因此不会出现 race condition 的情况
  • 子程序切换不是线程切换,而是由程序自身控制,在哪些地方交出控制权,切换到下一个任务,因此没有线程切换的开销,具有极高的执行效率
  • 协程的写法更加清晰简洁,把async/await 语法和 create_task结合来用,对于中小级别的并发需求已经毫无压力
  • 很多情况下,使用 Asyncio 需要特定第三方库的支持(缺点),例如不支持requests,但可以用aiohttp

6. 选择多线程还是协程

  • 如果是 I/O bound,并且 I/O 操作很慢,需要很多任务 / 线程协同实现,那么使用 Asyncio 更合适。
  • 如果是 I/O bound,但是 I/O 操作很快,只需要有限数量的任务 / 线程,那么使用多线程就可以了。
  • 如果是 CPU bound,则需要使用多进程来提高程序运行效率

Python并发编程——多线程与协程的更多相关文章

  1. python并发编程之线程/协程

    python并发编程之线程/协程 part 4: 异步阻塞例子与生产者消费者模型 同步阻塞 调用函数必须等待结果\cpu没工作input sleep recv accept connect get 同 ...

  2. Python并发编程系列之协程

    1 引言 协程是近几年并发编程的一个热门话题,与Python多进程.多线程相比,协程在很多方面优势明显.本文从协程的定义和意义出发,结合asyncio模块详细讲述协程的使用. 2 协程的意义 2.1 ...

  3. 15.python并发编程(线程--进程--协程)

    一.进程:1.定义:进程最小的资源单位,本质就是一个程序在一个数据集上的一次动态执行(运行)的过程2.组成:进程一般由程序,数据集,进程控制三部分组成:(1)程序:用来描述进程要完成哪些功能以及如何完 ...

  4. python并发编程之asyncio协程(三)

    协程实现了在单线程下的并发,每个协程共享线程的几乎所有的资源,除了协程自己私有的上下文栈:协程的切换属于程序级别的切换,对于操作系统来说是无感知的,因此切换速度更快.开销更小.效率更高,在有多IO操作 ...

  5. python单线程,多线程和协程速度对比

    在某些应用场景下,想要提高python的并发能力,可以使用多线程,或者协程.比如网络爬虫,数据库操作等一些IO密集型的操作.下面对比python单线程,多线程和协程在网络爬虫场景下的速度. 一,单线程 ...

  6. python并发编程之gevent协程(四)

    协程的含义就不再提,在py2和py3的早期版本中,python协程的主流实现方法是使用gevent模块.由于协程对于操作系统是无感知的,所以其切换需要程序员自己去完成. 系列文章 python并发编程 ...

  7. python并发编程&多线程(二)

    前导理论知识见:python并发编程&多线程(一) 一 threading模块介绍 multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性 官网链 ...

  8. python并发编程&多线程(一)

    本篇理论居多,实际操作见:  python并发编程&多线程(二) 一 什么是线程 在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程 线程顾名思义,就是一条流水线工作的过程,一 ...

  9. python 并发编程 多线程 目录

    线程理论 python 并发编程 多线程 开启线程的两种方式 python 并发编程 多线程与多进程的区别 python 并发编程 多线程 Thread对象的其他属性或方法 python 并发编程 多 ...

随机推荐

  1. JFinal 源码解析-MVC部分

    首先从请求入口看起,应用初始化时加载web.xml的JFinalFilter,和configClass 从这段配置可以看出jfinal和spring mvc入口类似,通过一个实现Servlet Fil ...

  2. filebeat v6.3 多行合并的步骤 多个表达式同时匹配

    配置文件位于/etc/filebeat/filebeat.yml,就是filebeat的主配置文件打开文件filebeat.yml,搜索multiline:,默认是注释的,常用的有如下三个配置: mu ...

  3. 循环语句&编码了解

    循环语句&编码了解 用户交互 input: input接收的内容是str 循环语句 if语句 语法规则:        if 条件判断:            代码块1        else ...

  4. PyQt5中QTableView函数讲解

    如果想熟悉QTableWidget,请参考PyQt5高级界面控件之QTableWidget(四) setSpan(int, int, int, int)四个参数分别代表,起始行,列,合并的行数,全并的 ...

  5. 线上服务的FGC问题排查,看这篇就够了!

    线上服务的GC问题,是Java程序非常典型的一类问题,非常考验工程师排查问题的能力.同时,几乎是面试必考题,但是能真正答好此题的人并不多,要么原理没吃透,要么缺乏实战经验. 过去半年时间里,我们的广告 ...

  6. springboot整合oss

    原文链接:https://blog.csdn.net/weixin_42370891/article/details/99102508 登录阿里云,进入到控制台 创建Bucket 导入如下依赖 < ...

  7. vue学习第二天:Vue跑马灯效果制作

    分析: 1. 给开始按钮绑定一个点击事件 2.在按钮的事件处理函数中,写相关的业务代码 3.拿到msg字符串 4.调用字符串的substring来进行字符串的截取操作 5.重新赋值利用vm实例的特性来 ...

  8. 深入理解Java闭包概念

    闭包又称词法闭包 闭包最早定义为一种包含<环境成分>和<控制成分>的实体. 解释一:闭包是引用了自由变量的函数,这个被引用的变量将和这个函数一同存在. 解释二:闭包是函数和相关 ...

  9. 一文告诉你Linux如何配置KVM虚拟化--安装篇

    KVM全称"Kernel-based Virtual Machine",即基于内核的虚拟机,在linux内启用kvm需要硬件,内核和软件(qemu)支持,这篇文章教你如何配置并安装 ...

  10. Redis系列之简介和Linux部署教程

    ##Redis介绍##Redis如今已经成为Web开发社区最火热的内存数据库之一,随着Web2.0的快速发展,再加上半结构数据比重加大,网站对高效性能的需求也越来越多.而且大型网站一般都有几百台或者更 ...