Python中的多进程与多线程(一)
一、背景
最近在Azkaban的测试工作中,需要在测试环境下模拟线上的调度场景进行稳定性测试。故而重操python旧业,通过python编写脚本来构造类似线上的调度场景。在脚本编写过程中,碰到这样一个需求:要在测试环境创建10000个作业流。
最开始的想法是在一个azkaban project下循环调用10000次create job接口(每个Flow只包含一个job)。由于azkaban它本身没有增加/删除作业流的接口,所有的作业流修改、增加、删除其实都是通过重新上传项目zip包实现的,相应地每次调猛犸前端的create job接口,实际上是在猛犸端对zip包的内容进行了重新的整合后再重新上传zip包到azkaban,整个过程可以拆解成如下过程:解压zip包获得zip包内容,变更zip包内的文件内容,重新打包zip包,上传到azkaban。因此,随着循环次数越往后,zip包包含的内容会越多,接口执行一次的时间就越长。实践发现,第一次调该接口的时间大致不到1秒,到循环1000次的时候接口调用一次的时间就达到了将近3秒。因此,如果指望一个循环10000次来构造该场景,显然要耗费巨大的时间。
在此背景下, 自然而然地就想到用多进程/多线程的方式来处理该问题。
二、“多任务”的操作系统基础
大家都知道,操作系统可以同时运行多个任务。比如你一边听音乐,一边聊IM,一边写博客等。现在的cpu大都是多核的,但即使是过去的单核cpu也是支持多任务并行执行。
单核cpu执行多任务的原理:操作系统交替轮流地执行各个任务。先让任务1执行0.01秒,然后切换到任务2执行0.01秒,再切换到任务3执行0.01秒...这样往复地执行下去。由于cpu的执行速度非常快,所以使用者的主观感受就是这些任务在并行地执行。
多核cpu执行多任务的原理:由于实际应用中,任务的数量往往远超过cpu的核数,所以操作系统实际上是把这些多任务轮流地调度到每个核心上执行。
对于操作系统来说,一个应用就是一个进程。比如打开一个浏览器,它是一个进程;打开一个记事本,它是一个进程。每个进程有它特定的进程号。他们共享系统的内存资源。进程是操作系统分配资源的最小单位。
而对于每一个进程而言,比如一个视频播放器,它必须同时播放视频和音频,就至少需要同时运行两个“子任务”,进程内的这些子任务就是通过线程来完成。线程是最小的执行单元。一个进程它可以包含多个线程,这些线程相互独立,同时又共享进程所拥有的资源。
三、Python多进程编程
1. multiprocessing
multiprocessing是Python提供的一个跨平台的多进程模块,通过它可以很方便地编写多进程程序,在不同的平台(Unix/Linux, Windows)都可以执行。
下面就是使用multiprocessing编写多进程程序的代码:
- #!/usr/bin/python
- # -*- coding: utf-8 -*
- __author__ = 'zni.feng'
- import sys
- reload (sys)
- sys.setdefaultencoding('utf-8')
- from multiprocessing import Process
- import os
- import time
- #子进程fun
- def child_projcess_fun(name):
- print 'Child process %s with processId %s starts.' % (name, os.getpid())
- time.sleep(3)
- print 'Child process %s with processId %s ends.' % (name, os.getpid())
- if __name__ == "__main__":
- print 'Parent processId is: %s.' % os.getpid()
- p = Process(target = child_projcess_fun, args=('zni',))
- print 'Process starts'
- p.start() #开始进程
- p.join() #等待子进程结束后再继续往下执行
- print 'Process ends.'
程序的输出:
- Parent processId is: 11076.
- Process starts
- Child process zni with processId 11077 starts.
- Child process zni with processId 11077 ends.
- Process ends.
- [Finished in 3.1s]
2. Pool
某些情况下,我们希望批量创建多个子进程,或者给定子进程数的上限,避免无限地消耗系统的资源。通过Pool(进程池)的方式,就可以完成这项工作,下面是使用Pool的代码:
- #!/usr/bin/python
- # -*- coding: utf-8 -*
- __author__ = 'zni.feng'
- import sys
- reload (sys)
- sys.setdefaultencoding('utf-8')
- from multiprocessing import Pool
- import os, time
- def child_process_test(name, sleep_time):
- print 'Child process %s with processId %s starts.' % (name, os.getpid())
- time.sleep(sleep_time)
- print 'Child process %s with processId %s ends.' % (name, os.getpid())
- if __name__ == "__main__":
- print 'Parent processId is: %s.' % os.getpid()
- p = Pool() #进程池默认大小是cpu的核数
- #p = Pool(10) #生成一个容量为10的进程池,即最大同时执行10个子进程
- for i in range(5):
- p.apply_async(child_process_test, args=('zni_'+str(i), i+1,)) #p.apply_async向进程池提交目标请求
- print 'Child processes are running.'
- p.close()
- p.join() #用来等待进程池中的所有子进程结束再向下执行代码,必须在p.close()或者p.terminate()之后执行
- print 'All Processes end.'
程序的输出:
- Parent processId is: 5050.
- Child processes are running.
- Child process zni_0 with processId 5052 starts.
- Child process zni_1 with processId 5053 starts.
- Child process zni_2 with processId 5054 starts.
- Child process zni_3 with processId 5055 starts.
- Child process zni_0 with processId 5052 ends.
- Child process zni_4 with processId 5052 starts.
- Child process zni_1 with processId 5053 ends.
- Child process zni_2 with processId 5054 ends.
- Child process zni_3 with processId 5055 ends.
- Child process zni_4 with processId 5052 ends.
- All Processes end.
- [Finished in 6.2s]
close()方法和terminate()方法的区别:
close:关闭进程池,使之不能再添加新的进程。已经执行的进程会等待继续执行直到结束。
terminate:强制终止线程池,正在执行的进程也会被强制终止。
3. 进程间通信
Python的multiprocessing模块提供了多种进程间通信的方式,如Queue、Pipe等。
3.1 Queue、Lock
Queue是multiprocessing提供的一个模块,它的数据结构就是"FIFO——first in first out"的队列,常用的方法有:put(object)入队;get()出队;empty()判断队列是否为空。
Lock:当多个子进程对同一个queue执行写操作时,为了避免并发操作产生冲突,可以通过加锁的方式使得某个子进程对queue拥有唯一的写权限,其他子进程必须等待该锁释放后才能再开始执行写操作。
下面就是使用Queue进行进程间通信的代码:在父进程里创建两个子进程,分别实现对queue的读和写操作
- #!/usr/bin/python
- # -*- coding: utf-8 -*
- __author__ = 'zni.feng'
- import sys
- reload (sys)
- sys.setdefaultencoding('utf-8')
- from multiprocessing import Process, Queue, Lock
- import os, time, random
- #写数据进程
- def write(q, lock, name):
- print 'Child Process %s starts' % name
- #获得锁
- lock.acquire()
- for value in ['A' , 'B', 'C']:
- print 'Put %s to queue...' % value
- q.put(value)
- time.sleep(random.random())
- #释放锁
- lock.release()
- print 'Child Process %s ends' % name
- #读数据进程
- def read(q, lock, name):
- print 'Child Process %s starts' % name
- while True: #持续地读取q中的数据
- value =q.get()
- print 'Get %s from queue.' % value
- print 'Child Process %s ends' % name
- if __name__ == "__main__":
- #父进程创建queue,并共享给各个子进程
- q= Queue()
- #创建锁
- lock = Lock()
- #创建第一个“写”子进程
- pw = Process(target = write , args=(q, lock, 'WRITE', ))
- #创建“读”进程
- pr = Process(target = read, args=(q,lock, 'READ',))
- #启动子进程pw,写入:
- pw.start()
- #启动子进程pr,读取:
- pr.start()
- #等待pw结束:
- pw.join()
- #pr是个死循环,通过terminate杀死:
- pr.terminate()
- print 'Test finish.'
程序的输出结果为:
- Child Process WRITE starts
- Put A to queue...
- Child Process READ starts
- Get A from queue.
- Put B to queue...
- Get B from queue.
- Put C to queue...
- Get C from queue.
- Child Process WRITE ends
- Test finish.
- [Finished in 2.0s]
3.2 Pipe
Pipe是另一种进程间通信的方式,俗称“管道”。它由两端组成,一端往管道里写入数据,另一端从管道里读取数据。
下面就是使用Pipe通信的代码:
- #!/usr/bin/python
- # -*- coding: utf-8 -*
- __author__ = 'zni.feng'
- import sys
- reload (sys)
- sys.setdefaultencoding('utf-8')
- from multiprocessing import Process, Pipe
- import os, time, random
- #发送数据进程
- def send(child_pipe, name):
- print 'Child Process %s starts' % name
- child_pipe.send('This is Mr.Ni')
- child_pipe.close()
- time.sleep(random.random())
- print 'Child Process %s ends' % name
- #接收数据进程
- def recv(parent_pipe, name):
- print 'Child Process %s starts' % name
- print parent_pipe.recv()
- time.sleep(random.random())
- print 'Child Process %s ends' % name
- if __name__ == "__main__":
- #创建管道
- parent,child = Pipe()
- #创建send进程
- ps = Process(target=send, args=(child, 'SEND'))
- #创建recv进程
- pr = Process(target=recv, args=(parent, 'RECEIVE'))
- #启动send进程
- ps.start()
- #等待send进程结束
- ps.join()
- #启动recv进程
- pr.start()
- #等待recv进程结束
- pr.join()
- print 'Test finish.'
程序的输出结果如下:
- Child Process SEND starts
- Child Process SEND ends
- Child Process RECEIVE starts
- This is Mr.Ni
- Child Process RECEIVE ends
- Test finish.
- [Finished in 1.8s]
Python中的多进程与多线程(一)的更多相关文章
- 聊聊Python中的多进程和多线程
今天,想谈一下Python中的进程和线程. 最近在学习Django的时候,涉及到了多进程和多线程的知识点,所以想着一下把Python中的这块知识进行总结,所以系统地学习了一遍,将知识梳理如下. 1. ...
- 深入浅析python中的多进程、多线程、协程
深入浅析python中的多进程.多线程.协程 我们都知道计算机是由硬件和软件组成的.硬件中的CPU是计算机的核心,它承担计算机的所有任务. 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资源 ...
- Python中的多进程与多线程(二)
在上一章中,学习了Python多进程编程的一些基本方法:使用跨平台多进程模块multiprocessing提供的Process.Pool.Queue.Lock.Pipe等类,实现子进程创建.进程池(批 ...
- 学习笔记--python中使用多进程、多线程加速文本预处理
一.任务描述 最近尝试自行构建skip-gram模型训练word2vec词向量表.其中有一步需要统计各词汇的出现频率,截取出现频率最高的10000个词汇进行保留,形成常用词词典.对于这个问题,我建立了 ...
- python中的多进程与多线程(二)
1.使用多线程可以有效利用CPU资源,线程享有相同的地址空间和内存,这些线程如果同时读写变量,导致互相干扰,就会产生并发问题,为了避免并发问题,绝不能让多个线程读取或写入相同的变量,因此python中 ...
- Python中的多进程、多线程和协程
本文中的内容来自我的笔记.撰写过程中参考了胡俊峰老师<Python程序设计与数据科学导论>课程的内容. 并发处理:多进程和多线程 前置 概念: 并发:一段时间内同时推进多个任务,但不一定要 ...
- Python中的多进程与多线程/分布式该如何使用
在批评Python的讨论中,常常说起Python多线程是多么的难用.还有人对 global interpreter lock(也被亲切的称为“GIL”)指指点点,说它阻碍了Python的多线程程序同时 ...
- python中的多进程与多线程(一)
进程是一个执行中的程序,每个进程有自己的地址空间.内存.数据栈以及其他用于跟踪执行的辅助数据.操作系统管理其上所有进程,并合理分配时间. 进程也可以通过fork或spawn派生新的进程,每个新进程有自 ...
- Python中使用多进程来实现并行处理的方法小结
进程和线程是计算机软件领域里很重要的概念,进程和线程有区别,也有着密切的联系,先来辨析一下这两个概念: 1.定义 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和 ...
随机推荐
- [ 高并发]Java高并发编程系列第二篇--线程同步
高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...
- 如何一步一步用DDD设计一个电商网站(二)—— 项目架构
阅读目录 前言 六边形架构 终于开始建项目了 DDD中的3个臭皮匠 CQRS(Command Query Responsibility Segregation) 结语 一.前言 上一篇我们讲了DDD的 ...
- Linux CentOS 配置Tomcat环境
一.下载Tomcat 下载Tomcat方式也有两种,可以参考我的前一篇博文Linux CentOS配置JDK环境,这边就不再赘述. 二.在Linux处理Tomcat包 1.创建tomcat文件夹 mk ...
- PowerDesigner-VBSrcipt-自动设置主键,外键名等(SQL Server)
在PowerDesigner中的设计SQL Server 数据表时,要求通过vbScript脚本实现下面的功能: 主键:pk_TableName 外键:fk_TableName_ForeignKeyC ...
- C++中的命名空间
一,命名空间(namespace)的基本概念以及由来 1.什么是标识符: 在C++中,标识符可以是基本的变量,类,对象,结构体,函数,枚举,宏等. 2.什么是命名空间: 所谓的命名空间是指标识符的可见 ...
- 博客使用BOS上传图片
1.博客平台的选定 从大学开始做个人主页算起,最开始是使用html,CSSS写简单的页面,后面大学毕业之后接触到了WordPress,就开始用WordPress搭建网站.现在还维护着一个农村网站.ht ...
- [C#] C# 知识回顾 - 学会使用异常
学会使用异常 在 C# 中,程序中在运行时出现的错误,会不断在程序中进行传播,这种机制称为“异常”. 异常通常由错误的代码引发,并由能够更正错误的代码进行 catch. 异常可由 .NET 的 CLR ...
- 【NLP】干货!Python NLTK结合stanford NLP工具包进行文本处理
干货!详述Python NLTK下如何使用stanford NLP工具包 作者:白宁超 2016年11月6日19:28:43 摘要:NLTK是由宾夕法尼亚大学计算机和信息科学使用python语言实现的 ...
- 一款开源免费的.NET文档操作组件DocX(.NET组件介绍之一)
在目前的软件项目中,都会较多的使用到对文档的操作,用于记录和统计相关业务信息.由于系统自身提供了对文档的相关操作,所以在一定程度上极大的简化了软件使用者的工作量. 在.NET项目中如果用户提出了相关文 ...
- 个人网站对xss跨站脚本攻击(重点是富文本编辑器情况)和sql注入攻击的防范
昨天本博客受到了xss跨站脚本注入攻击,3分钟攻陷--其实攻击者进攻的手法很简单,没啥技术含量.只能感叹自己之前竟然完全没防范. 这是数据库里留下的一些记录.最后那人弄了一个无限循环弹出框的脚本,估计 ...