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


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

  • Threading
  • Multiprocessing
  • Gevent

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

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

一.线程

Threading

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

Demo:

  1. import threading
  2. from time import sleep
  3. def A():
  4. '''打印 “A” 五次'''
  5. for x in range(5):
  6. print("AAA")
  7. sleep(1.0)
  8. def B():
  9. for x in range(5):
  10. print("BBB")
  11. sleep(1.0)
  12. def main():
  13. t1 = threading.Thread(target=A)
  14. t2 = threading.Thread(target=B)
  15. t1.start()
  16. t2.start()
  17. if __name__ == '__main__':
  18. main()

打印结果:

  1. AAA
  2. BBB
  3. BBB
  4. AAA
  5. BBB
  6. AAA
  7. BBB
  8. AAA
  9. BBB
  10. AAA
  11. [Finished in 5.3s]

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

  1. AAA
  2. AAA
  3. AAA
  4. AAA
  5. AAA
  6. BBB
  7. BBB
  8. BBB
  9. BBB
  10. BBB
  11. [Finished in 10.3s]

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

Threading 常用方法:

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

不一样的 _Thread

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

Demo:

  1. import _thread
  2. import time
  3. def print_time( threadName, delay):
  4. count = 0
  5. while count < 5:
  6. time.sleep(delay)
  7. count += 1
  8. print ("%s: %s" % ( threadName, time.ctime(time.time()) ))
  9. try:
  10. _thread.start_new_thread( print_time, ("Thread-1", 2, ) )
  11. _thread.start_new_thread( print_time, ("Thread-2", 4, ) )
  12. except:
  13. print ("Error: 无法启动线程")
  14. while True:
  15. pass

打印结果:

  1. Thread-1: Wed Mar 20 15:36:32 2019
  2. Thread-1: Wed Mar 20 15:36:34 2019
  3. Thread-2: Wed Mar 20 15:36:34 2019
  4. Thread-1: Wed Mar 20 15:36:36 2019
  5. Thread-2: Wed Mar 20 15:36:38 2019
  6. Thread-1: Wed Mar 20 15:36:38 2019
  7. [Cancelled]

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

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

二.进程

Multiprocessing

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

Demo:

  1. from multiprocessing import Process
  2. import time
  3. def _proces(name):
  4. print("Process " + name)
  5. time.sleep(1.0)
  6. if __name__ == "__main__":
  7. p1 = Process(target=_proces, args=('A',))
  8. p1.start()
  9. p1.join()
  10. p2 = Process(target=_proces, args=('B',))
  11. p2.start()
  12. p2.join()

输出打印结果如下:

  1. Process A
  2. Process B
  3. [Finished in 3.2s]

Multiprocessing 常用方法:

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

三.协程

Gevent

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

Demo:

  1. from gevent import monkey
  2. monkey.patch_all()
  3. import gevent
  4. import urllib.request
  5. def run(url):
  6. print('run --> %s' % url)
  7. try:
  8. response = urllib.request.urlopen(url)
  9. data = response.read()
  10. print('%d bytes received from %s.' % (len(data), url))
  11. except Exception as e:
  12. print(e)
  13. if __name__ == '__main__':
  14. urls = ['https://www.baidu.com','https://www.jd.com','https://www.cnblogs.com/']
  15. lis = [gevent.spawn(run, url) for url in urls]
  16. gevent.joinall(lis)

输出打印结果如下:

  1. run --> https://www.baidu.com
  2. run --> https://www.jd.com
  3. run --> https://www.cnblogs.com/
  4. 227 bytes received from https://www.baidu.com.
  5. 111917 bytes received from https://www.jd.com.
  6. 47872 bytes received from https://www.cnblogs.com/.
  7. [Finished in 0.7s]

四.实例

多线程Demo:

  1. from threading import Thread
  2. from queue import Queue
  3. from lxml import etree
  4. import requests
  5. class douban(Thread):
  6. def __init__(self, url, q):
  7. # 重写写父类的__init__方法
  8. super(douban, self).__init__()
  9. self.url = url
  10. self.q = q
  11. self.headers={}
  12. def run(self):
  13. self.parse_page()
  14. def send_request(self,url):
  15. '''
  16. 用来发送请求的方法
  17. :return: 返回网页源码
  18. '''
  19. # 请求出错时,重复请求3次,
  20. i = 0
  21. while i <= 3:
  22. try:
  23. html = requests.get(url=url,headers=self.headers).content
  24. except Exception as e:
  25. print(u'[DEBUG] %s%s'% (e,url))
  26. i += 1
  27. else:
  28. return html
  29. def parse_page(self):
  30. '''
  31. 解析网站源码,并采用 xpath 提取 电影名称和平分放到队列中
  32. :return:
  33. '''
  34. response = self.send_request(self.url)
  35. html = etree.HTML(response)
  36. # 获取到一页的电影数据
  37. node_list = html.xpath("//div[@class='info']")
  38. for move in node_list:
  39. # 电影名称
  40. title = move.xpath('.//a/span/text()')[0]
  41. # 电影主题
  42. theme = str(move.xpath('.//div[@class="bd"]//span[@class="inq"]/text()'))
  43. # 评分
  44. score = move.xpath('.//div[@class="bd"]//span[@class="rating_num"]/text()')[0]
  45. # 将每一部电影的名称跟评分加入到队列
  46. self.q.put(title + "\t |" + score +"\t" + theme)
  47. def main():
  48. # 创建一个队列用来保存进程获取到的数据
  49. q = Queue()
  50. base_url = 'https://movie.douban.com/top250?start='
  51. # 构造所有 url
  52. url_list = [base_url+str(num) for num in range(0,50+1,25)]
  53. # 保存线程
  54. Thread_list = []
  55. # 创建并启动线程
  56. for url in url_list:
  57. p = douban(url,q)
  58. p.start()
  59. Thread_list.append(p)
  60. # 让主线程等待子线程执行完成
  61. for i in Thread_list:
  62. i.join()
  63. while not q.empty():
  64. print(q.get())
  65. if __name__=="__main__":
  66. main()

输出打印:

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

多进程Demo:

  1. from multiprocessing import Process, Queue
  2. import time
  3. from lxml import etree
  4. import requests
  5. class douban(Process):
  6. def __init__(self, url, q):
  7. # 重写写父类的__init__方法
  8. super(douban, self).__init__()
  9. self.url = url
  10. self.q = q
  11. self.headers = {}
  12. def run(self):
  13. self.parse_page()
  14. def send_request(self,url):
  15. '''
  16. 用来发送请求的方法
  17. :return: 返回网页源码
  18. '''
  19. # 请求出错时,重复请求3次,
  20. i = 0
  21. while i <= 3:
  22. try:
  23. return requests.get(url=url,headers=self.headers).content
  24. except Exception as e:
  25. print(u'[DEBUG] %s%s'% (e,url))
  26. i += 1
  27. def parse_page(self):
  28. '''
  29. 解析网站源码,并采用xpath提取 电影名称和平分放到队列中
  30. :return:
  31. '''
  32. response = self.send_request(self.url)
  33. html = etree.HTML(response)
  34. # 获取到一页的电影数据
  35. node_list = html.xpath("//div[@class='info']")
  36. for move in node_list:
  37. # 电影名称
  38. title = move.xpath('.//a/span/text()')[0]
  39. # 电影主题
  40. theme = str(move.xpath('.//div[@class="bd"]//span[@class="inq"]/text()'))
  41. # 评分
  42. score = move.xpath('.//div[@class="bd"]//span[@class="rating_num"]/text()')[0]
  43. # 将每一部电影的名称跟评分加入到队列
  44. self.q.put(title + "\t |" + score +"\t" + theme)
  45. def main():
  46. # 创建一个队列用来保存进程获取到的数据
  47. q = Queue()
  48. base_url = 'https://movie.douban.com/top250?start='
  49. # 构造所有url
  50. url_list = [base_url+str(num) for num in range(0,50+1,25)]
  51. # 保存进程
  52. Process_list = []
  53. # 创建并启动进程
  54. for url in url_list:
  55. p = douban(url,q)
  56. p.start()
  57. Process_list.append(p)
  58. # 让主进程等待子进程执行完成
  59. for i in Process_list:
  60. i.join()
  61. while not q.empty():
  62. print(q.get())
  63. if __name__=="__main__":
  64. main()

输出打印:

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

协程Demo:

  1. from queue import Queue
  2. import time
  3. from lxml import etree
  4. import requests
  5. import gevent
  6. from gevent import monkey
  7. monkey.patch_all()
  8. class douban(object):
  9. def __init__(self):
  10. # 创建一个队列用来保存进程获取到的数据
  11. self.q = Queue()
  12. self.headers = {}
  13. def run(self,url):
  14. self.parse_page(url)
  15. def send_request(self,url):
  16. '''
  17. 用来发送请求的方法
  18. :return: 返回网页源码
  19. '''
  20. # 请求出错时,重复请求3次,
  21. i = 0
  22. while i <= 3:
  23. try:
  24. html = requests.get(url=url,headers=self.headers).content
  25. except Exception as e:
  26. print(u'[DEBUG] %s%s'% (e,url))
  27. i += 1
  28. else:
  29. return html
  30. def parse_page(self,url):
  31. '''
  32. 解析网站源码,并采用xpath提取 电影名称和平分放到队列中
  33. :return:
  34. '''
  35. response = self.send_request(url)
  36. html = etree.HTML(response)
  37. # 获取到一页的电影数据
  38. node_list = html.xpath("//div[@class='info']")
  39. for move in node_list:
  40. # 电影名称
  41. title = move.xpath('.//a/span/text()')[0]
  42. # 电影主题
  43. theme = str(move.xpath('.//div[@class="bd"]//span[@class="inq"]/text()'))
  44. # 评分
  45. score = move.xpath('.//div[@class="bd"]//span[@class="rating_num"]/text()')[0]
  46. # 将每一部电影的名称跟评分加入到队列
  47. self.q.put(title + "\t |" + score +"\t" + theme)
  48. def main(self):
  49. base_url = 'https://movie.douban.com/top250?start='
  50. # 构造所有url
  51. url_list = [base_url+str(num) for num in range(0,225+1,25)]
  52. # 创建协程并执行
  53. job_list = [gevent.spawn(self.run,url) for url in url_list]
  54. # 让线程等待所有任务完成,再继续执行。
  55. gevent.joinall(job_list)
  56. while not self.q.empty():
  57. print(self.q.get())
  58. if __name__=="__main__":
  59. douban = douban()
  60. douban.main()

输出打印:

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

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

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

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

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

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

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

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

  6. Python学习笔记整理总结【网络编程】【线程/进程/协程/IO多路模型/select/poll/epoll/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学习笔记-(十四)进程&协程

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

随机推荐

  1. 20145223 杨梦云 《网络对抗》 Web安全基础实践

    20145223 杨梦云 <网络对抗> Web安全基础实践 1.实验后回答问题 (1)SQL注入攻击原理,如何防御 **原理**:SQL注入攻击是通过构建特殊的输入作为参数传入web应用程 ...

  2. 跟着大神学Mongo,Mongodb主从复制本机简单操作总结

    原文链接:http://www.cnblogs.com/huangxincheng/archive/2012/03/04/2379755.html 本机安装MongoDB不在介绍,本文Mongo小菜鸟 ...

  3. js 实现分享功能

    分享功能初步测试,title为当前页面的title. 其他详见注释!!! <!doctype html> <html> <head> <meta http-e ...

  4. module.exports与exports的联系与区别

    首先说明他们是啥? 在CommonJS规范中,exports和module.exports这两个对象是把某一模块化文件中的属性和方法暴露给外部模块的接口(说法可能不准确),外部模块通过require引 ...

  5. linux中删除文件内空白行的几种方法。

    linux中删除文件内空白行的几种方法 有时你可能需要在 Linux 中删除某个文件中的空行.如果是的,你可以使用下面方法中的其中一个.有很多方法可以做到,但我在这里只是列举一些简单的方法. 你可能已 ...

  6. Shell中的${}、##和%%使用范例

    假设定义了一个变量为,代码如下: file=/dir1/dir2/dir3/my.file.txt 可以用${ }分别替换得到不同的值: ${file#*/}: 删掉第一个 / 及其左边的字符串:di ...

  7. vue-cli3 vue.config.js 配置

    // cli_api配置地址 https://cli.vuejs.org/zh/config/ module.exports = { baseUrl: './', // 部署应用包时的基本 URL o ...

  8. swoole学习(二)----搭建server和client

    1.搭建server 1.1搭建server.php 1.搭建websocket服务器,首先建立 server.php 文件, <?php $server = new swoole_websoc ...

  9. thinkphp3.2.3 HTML 页面跳转

    1.   http://域名/index.php(入口文件)/模块/控制器/方法 2.{:U('控制器/方法')}

  10. ubuntu下的python请求库的安装——Selenium,ChromeDriver,GeckoDriver,PhantomJS,aiohttp

    Selenium安装: pip3 install selenium ChromeDriver安装: 在这链接下载对应版本:https://chromedriver.storage.googleapis ...