Python 多线程、进程、协程上手体验

时间:2022-10-31 20:09:20

浅谈 Python 多线程、进程、协程上手体验


前言:浅谈 Python 很多人都认为 Python 的多线程是垃圾(GIL 说这锅甩不掉啊~);本章节主要给你体验下 Python 的几个库

  • Threading
  • Multiprocessing
  • Gevent

总结:多进程主要用于浮点

备注:Gevent 需要手动安装 [pip install gevent]

一.线程

Threading

Threading 模块建立在 _thread 模块之上。_thread 模块以低级、原始的方式来处理和控制线程,而 threading 模块通过对 thread 进行二次封装,提供了更方便的 api 来处理线程。

Demo:

import threading
from time import sleep def A():
'''打印 “A” 五次'''
for x in range(5):
print("AAA")
sleep(1.0) def B():
for x in range(5):
print("BBB")
sleep(1.0) def main():
t1 = threading.Thread(target=A)
t2 = threading.Thread(target=B)
t1.start()
t2.start() if __name__ == '__main__':
main()

打印结果:

AAA
BBB
BBB
AAA
BBB
AAA
BBB
AAA
BBB
AAA
[Finished in 5.3s]

如果我们按照常规的方式执行 A()、B()方法,将耗时更多,结果如下:

AAA
AAA
AAA
AAA
AAA
BBB
BBB
BBB
BBB
BBB
[Finished in 10.3s]

对比下时间就知道多线程的重要性,简单来说就是花费更少时间做事得到最高的回报。

Threading 常用方法:

t.start() : 激活线程,
t.getName() : 获取线程的名称
t.setName() : 设置线程的名称
t.name : 获取或设置线程的名称
t.is_alive() : 判断线程是否为激活状态
t.isAlive() :判断线程是否为激活状态
t.setDaemon() : 父线程打印内容后便结束了,不管子线程是否执行完毕了
t.isDaemon() : 判断是否为守护线程
t.ident : 获取线程的标识符。线程标识符是一个非零整数,只有在调用了start()方法之后该属性才有效,否则它只返回None。
t.join() : 逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义
t.run() : 线程被cpu调度后自动执行线程对象的run方法

不一样的 _Thread

说明:Python3 通过两个标准库 _thread 和 threading 提供对线程的支持。

Demo:

import _thread
import time def print_time( threadName, delay):
count = 0
while count < 5:
time.sleep(delay)
count += 1
print ("%s: %s" % ( threadName, time.ctime(time.time()) )) try:
_thread.start_new_thread( print_time, ("Thread-1", 2, ) )
_thread.start_new_thread( print_time, ("Thread-2", 4, ) )
except:
print ("Error: 无法启动线程")
while True:
pass

打印结果:

Thread-1: Wed Mar 20 15:36:32 2019
Thread-1: Wed Mar 20 15:36:34 2019
Thread-2: Wed Mar 20 15:36:34 2019
Thread-1: Wed Mar 20 15:36:36 2019
Thread-2: Wed Mar 20 15:36:38 2019
Thread-1: Wed Mar 20 15:36:38 2019
[Cancelled]

_thread 提供了低级别的、原始的线程以及一个简单的锁,它相比于 threading 模块的功能还是比较有限的。

  • _Thread 模块已被废弃,Threading 身为它的接班人

二.进程

Multiprocessing

Multiprocessing 模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块 threading 的编程接口类似。

Demo:

from multiprocessing import Process
import time def _proces(name):
print("Process " + name)
time.sleep(1.0) if __name__ == "__main__":
p1 = Process(target=_proces, args=('A',))
p1.start()
p1.join()
p2 = Process(target=_proces, args=('B',))
p2.start()
p2.join()

输出打印结果如下:

Process A
Process B
[Finished in 3.2s]

Multiprocessing 常用方法:

p.start():启动进程,并调用该子进程中的p.run()
p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
p.is_alive():如果p仍然运行,返回True
p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程
group参数未使用,值始终为None
target表示调用对象,即子进程要执行的任务
args表示调用对象的位置参数元组,args=(1,2,'hexin',)
kwargs表示调用对象的字典,kwargs={'name':'hexin','age':18}
name为子进程的名称

三.协程

Gevent

Gevent 又称微线程,在单线程上执行多个任务,用函数切换,开销极小。不通过操作系统调度,没有进程、线程的切换开销。genvent,monkey.patchall

Demo:

from gevent import monkey
monkey.patch_all()
import gevent
import urllib.request def run(url):
print('run --> %s' % url)
try:
response = urllib.request.urlopen(url)
data = response.read()
print('%d bytes received from %s.' % (len(data), url))
except Exception as e:
print(e) if __name__ == '__main__':
urls = ['https://www.baidu.com','https://www.jd.com','https://www.cnblogs.com/']
lis = [gevent.spawn(run, url) for url in urls]
gevent.joinall(lis)

输出打印结果如下:

run --> https://www.baidu.com
run --> https://www.jd.com
run --> https://www.cnblogs.com/
227 bytes received from https://www.baidu.com.
111917 bytes received from https://www.jd.com.
47872 bytes received from https://www.cnblogs.com/.
[Finished in 0.7s]

四.实例

多线程Demo:

from threading import Thread
from queue import Queue
from lxml import etree
import requests class douban(Thread):
def __init__(self, url, q):
# 重写写父类的__init__方法
super(douban, self).__init__()
self.url = url
self.q = q
self.headers={} def run(self):
self.parse_page() def send_request(self,url):
'''
用来发送请求的方法
:return: 返回网页源码
'''
# 请求出错时,重复请求3次,
i = 0
while i <= 3:
try:
html = requests.get(url=url,headers=self.headers).content
except Exception as e:
print(u'[DEBUG] %s%s'% (e,url))
i += 1
else:
return html def parse_page(self):
'''
解析网站源码,并采用 xpath 提取 电影名称和平分放到队列中
:return:
'''
response = self.send_request(self.url)
html = etree.HTML(response)
# 获取到一页的电影数据
node_list = html.xpath("//div[@class='info']")
for move in node_list:
# 电影名称
title = move.xpath('.//a/span/text()')[0]
# 电影主题
theme = str(move.xpath('.//div[@class="bd"]//span[@class="inq"]/text()'))
# 评分
score = move.xpath('.//div[@class="bd"]//span[@class="rating_num"]/text()')[0]
# 将每一部电影的名称跟评分加入到队列
self.q.put(title + "\t |" + score +"\t" + theme) def main():
# 创建一个队列用来保存进程获取到的数据
q = Queue()
base_url = 'https://movie.douban.com/top250?start='
# 构造所有 url
url_list = [base_url+str(num) for num in range(0,50+1,25)] # 保存线程
Thread_list = []
# 创建并启动线程
for url in url_list:
p = douban(url,q)
p.start()
Thread_list.append(p) # 让主线程等待子线程执行完成
for i in Thread_list:
i.join() while not q.empty():
print(q.get()) if __name__=="__main__":
main()

输出打印:

肖申克的救赎    |9.6  ['希望让人*。']
霸王别姬 |9.6 ['风华绝代。']
这个杀手不太冷 |9.4 ['怪蜀黍和小萝莉不得不说的故事。']
阿甘正传 |9.4 ['一部美国近现代史。']
美丽人生 |9.5 ['最美的谎言。']
泰坦尼克号 |9.3 ['失去的才是永恒的。 ']
千与千寻 |9.3 ['最好的宫崎骏,最好的久石让。 ']
辛德勒的名单 |9.5 ['拯救一个人,就是拯救整个世界。']
..........(省略)
..........(省略)
[Finished in 0.9s]

多进程Demo:

from multiprocessing import Process, Queue
import time
from lxml import etree
import requests class douban(Process):
def __init__(self, url, q):
# 重写写父类的__init__方法
super(douban, self).__init__()
self.url = url
self.q = q
self.headers = {} def run(self):
self.parse_page() def send_request(self,url):
'''
用来发送请求的方法
:return: 返回网页源码
'''
# 请求出错时,重复请求3次,
i = 0
while i <= 3:
try:
return requests.get(url=url,headers=self.headers).content
except Exception as e:
print(u'[DEBUG] %s%s'% (e,url))
i += 1 def parse_page(self):
'''
解析网站源码,并采用xpath提取 电影名称和平分放到队列中
:return:
'''
response = self.send_request(self.url)
html = etree.HTML(response)
# 获取到一页的电影数据
node_list = html.xpath("//div[@class='info']")
for move in node_list:
# 电影名称
title = move.xpath('.//a/span/text()')[0]
# 电影主题
theme = str(move.xpath('.//div[@class="bd"]//span[@class="inq"]/text()'))
# 评分
score = move.xpath('.//div[@class="bd"]//span[@class="rating_num"]/text()')[0] # 将每一部电影的名称跟评分加入到队列
self.q.put(title + "\t |" + score +"\t" + theme) def main():
# 创建一个队列用来保存进程获取到的数据
q = Queue()
base_url = 'https://movie.douban.com/top250?start='
# 构造所有url
url_list = [base_url+str(num) for num in range(0,50+1,25)] # 保存进程
Process_list = []
# 创建并启动进程
for url in url_list:
p = douban(url,q)
p.start()
Process_list.append(p) # 让主进程等待子进程执行完成
for i in Process_list:
i.join() while not q.empty():
print(q.get()) if __name__=="__main__":
main()

输出打印:

肖申克的救赎    |9.6  ['希望让人*。']
霸王别姬 |9.6 ['风华绝代。']
这个杀手不太冷 |9.4 ['怪蜀黍和小萝莉不得不说的故事。']
阿甘正传 |9.4 ['一部美国近现代史。']
美丽人生 |9.5 ['最美的谎言。']
泰坦尼克号 |9.3 ['失去的才是永恒的。 ']
千与千寻 |9.3 ['最好的宫崎骏,最好的久石让。 ']
辛德勒的名单 |9.5 ['拯救一个人,就是拯救整个世界。']
盗梦空间 |9.3 ['诺兰给了我们一场无法盗取的梦。']
忠犬八公的故事 |9.3 ['永远都不能忘记你所爱的人。']
机器人总动员 |9.3 ['小瓦力,大人生。']
..........(省略)
..........(省略)
[Finished in 2.4s]

协程Demo:

from queue import Queue
import time
from lxml import etree
import requests
import gevent from gevent import monkey
monkey.patch_all() class douban(object):
def __init__(self):
# 创建一个队列用来保存进程获取到的数据
self.q = Queue()
self.headers = {} def run(self,url):
self.parse_page(url) def send_request(self,url):
'''
用来发送请求的方法
:return: 返回网页源码
'''
# 请求出错时,重复请求3次,
i = 0
while i <= 3:
try:
html = requests.get(url=url,headers=self.headers).content
except Exception as e:
print(u'[DEBUG] %s%s'% (e,url))
i += 1
else:
return html def parse_page(self,url):
'''
解析网站源码,并采用xpath提取 电影名称和平分放到队列中
:return:
'''
response = self.send_request(url)
html = etree.HTML(response)
# 获取到一页的电影数据
node_list = html.xpath("//div[@class='info']")
for move in node_list:
# 电影名称
title = move.xpath('.//a/span/text()')[0]
# 电影主题
theme = str(move.xpath('.//div[@class="bd"]//span[@class="inq"]/text()'))
# 评分
score = move.xpath('.//div[@class="bd"]//span[@class="rating_num"]/text()')[0] # 将每一部电影的名称跟评分加入到队列
self.q.put(title + "\t |" + score +"\t" + theme) def main(self):
base_url = 'https://movie.douban.com/top250?start='
# 构造所有url
url_list = [base_url+str(num) for num in range(0,225+1,25)]
# 创建协程并执行
job_list = [gevent.spawn(self.run,url) for url in url_list]
# 让线程等待所有任务完成,再继续执行。
gevent.joinall(job_list) while not self.q.empty():
print(self.q.get()) if __name__=="__main__":
douban = douban()
douban.main()

输出打印:

肖申克的救赎    |9.6  ['希望让人*。']
霸王别姬 |9.6 ['风华绝代。']
这个杀手不太冷 |9.4 ['怪蜀黍和小萝莉不得不说的故事。']
阿甘正传 |9.4 ['一部美国近现代史。']
美丽人生 |9.5 ['最美的谎言。']
泰坦尼克号 |9.3 ['失去的才是永恒的。 ']
千与千寻 |9.3 ['最好的宫崎骏,最好的久石让。 ']
辛德勒的名单 |9.5 ['拯救一个人,就是拯救整个世界。']
盗梦空间 |9.3 ['诺兰给了我们一场无法盗取的梦。']
忠犬八公的故事 |9.3 ['永远都不能忘记你所爱的人。']
机器人总动员 |9.3 ['小瓦力,大人生。']
三傻大闹宝莱坞 |9.2 ['英俊版憨豆,高情商版谢耳朵。']
..........(省略)
..........(省略)
[Finished in 1.0s]

总结

  • 多进程:计算密集型,需要充分使用服务器多核CPU资源,计算大量的并发请求时候,推荐 multiprocessing (多进程)
    缺陷:多个进程之间通信成本高,切换开销大,反正就是占用硬件资源高就对了。

  • 多线程\协程:I/O密集型(网络I/O、磁盘I/O、数据库I/O、爬虫)比较合适多线程。推荐 threading.Thread、gevent、multiprocessing.dummy (多线程)
    缺陷:因 GIL锁挟持之下不能使用 CPU 多核心并行运算,也就是说你的代码永远只有一个核心在跑,不能做到高并行,但是可以做到高并发。
    解决方式:要么换编译器、要么换开发语言实现多线程

想要追求更有效率,多进程加异步速度会很快

下次更新各种细节与 Python 高级用法

Python 多线程、进程、协程上手体验的更多相关文章

  1. python 线程 进程 协程 学习

    转载自大神博客:http://www.cnblogs.com/aylin/p/5601969.html 仅供学习使用···· python 线程与进程简介 进程与线程的历史 我们都知道计算机是由硬件和 ...

  2. 12&period;python进程&bsol;协程&bsol;异步IO

    进程 创建进程 from multiprocessing import Process import time def func(name): time.sleep(2) print('hello', ...

  3. 也说性能测试,顺便说python的多进程&plus;多线程、协程

    最近需要一个web系统进行接口性能测试,这里顺便说一下性能测试的步骤吧,大概如下 一.分析接口频率 根据系统的复杂程度,接口的数量有多有少,应该优先对那些频率高,数据库操作频繁的接口进行性能测试,所以 ...

  4. Python并发编程二(多线程、协程、IO模型)

    1.python并发编程之多线程(理论) 1.1线程概念 在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程 线程顾名思义,就是一条流水线工作的过程(流水线的工作需要电源,电源就相当于 ...

  5. python 多进程,多线程,协程

    在我们实际编码中,会遇到一些并行的任务,因为单个任务无法最大限度的使用计算机资源.使用并行任务,可以提高代码效率,最大限度的发挥计算机的性能.python实现并行任务可以有多进程,多线程,协程等方式. ...

  6. Python学习笔记整理总结【网络编程】【线程&sol;进程&sol;协程&sol;IO多路模型&sol;select&sol;poll&sol;epoll&sol;selector】

    一.socket(单链接) 1.socket:应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socke ...

  7. 深入浅析python中的多进程、多线程、协程

    深入浅析python中的多进程.多线程.协程 我们都知道计算机是由硬件和软件组成的.硬件中的CPU是计算机的核心,它承担计算机的所有任务. 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资源 ...

  8. Python并发编程——多线程与协程

    Pythpn并发编程--多线程与协程 目录 Pythpn并发编程--多线程与协程 1. 进程与线程 1.1 概念上 1.2 多进程与多线程--同时执行多个任务 2. 并发和并行 3. Python多线 ...

  9. python学习笔记-(十四)进程&amp&semi;协程

    一. 进程 1. 多进程multiprocessing multiprocessing包是Python中的多进程管理包,是一个跨平台版本的多进程模块.与threading.Thread类似,它可以利用 ...

随机推荐

  1. angularjs provider 供应商服务

    今天学习了angularjs的provider的供应商服务,写了个例子(自定义供应商服务,也可使用angularjs内部提供的服务) var starterApp = angular.module(' ...

  2. Android loader 详解

    装载器从android3.0开始引进.它使得在activity或fragment中异步加载数据变得简单.装载器具有如下特性: 它们对每个Activity和Fragment都有效. 他们提供了异步加载数 ...

  3. Newtonsoft&period;Json&period;dll

    代码 using System; DoNet2.0 需要借助于Newtonsoft.Json.dll using System.IO; using System.Text; using Newtons ...

  4. 关于TransactionScope出错:&OpenCurlyDoubleQuote;与基础事务管理器的通信失败”的解决方法总结

    遇到此问题先需确认几个问题: 1)MS DTC是否设置正确? 2)是否启用了防火墙?是否对DTC做了例外? 3)是否做了hosts映射?是否跨网域通信? 开发分布式事务,碰到一个错误“与基础事务管理器 ...

  5. 教您如何使用MySQL group&lowbar;concat函数

    MySQL group_concat函数是典型的字符串连接函数,下面就为您介绍MySQL group_concat的语法,希望对您学习MySQL group_concat函数有所帮助. MySQL g ...

  6. Echarts使用随笔(2)-Echarts中mapType and data

    本文出处:http://blog.csdn.net/chenxiaodan_danny/article/details/39081071  series : [                {   ...

  7. 详解一下网络广告cpc、cpm、cpl、cpa、cps、cpr的计费方法是什么

    CPC(Cost per click)按照 广告 点击数 计费 ,限定一个IP在24小时内只能点击一次.CPM(Cost per mille)按照广告显示次数来计算广告费,可在短时间内为 网站 带来巨 ...

  8. Django----模板层

    一.模板层:           python的模板:HTML代码+模板语法      模版包括在使用时会被值替换掉的 变量,和控制模版逻辑的 标签. import datetime t=dateti ...

  9. httpclient的get带参不带参post带参不带参的简单应用

    一,基础的的应用 1.1,get的无参请求 @Test public void doGet() throws Exception { //创建一个httpclient对象 CloseableHttpC ...

  10. Activiti开发案例之activiti-app工作流导出图片

    前言 自从 Activiti 和 JBPM4 分家以后,Activiti 目前已经发展到了版本7,本着稳定性原则我们最终选择了6,之前还有一个版本5. 问题 在开发使用的过程中发现 Activiti ...