进程
进程
linux 系统如何创建子进程
-
Unix/Linux操作系统提供了一个fork()系统调用,它非常特殊。
普通的函数调用,调用一次,返回一次,但是fork( )调用一次,返回两次;
因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。 -
子进程永远返回0,而父进程返回子进程的ID。
这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid( )就可以拿到父进程的ID。
Python的os模块封装了常见的系统调用,其中就包括fork( ),可以在Python程序中轻松创建子进程:
-
原理:
父进程和子进程: 如果父进程结束, 子进程也随之结束(先有父进程, 再有子进程);
类Linux系统中(redhat,mac):fork函数。 -
常用函数:
os.fork() # 创建子进程 os.getpid() # 获取当前进程的pid (process id) os.getppid() # 获取当前进程的父进程pid (parent process id)
创建子进程:
import os
print('当前进程(pid=%d)正在运行......' %(os.getpid()))
# 在pycharm编写代码, 程序的父进程就是pycharm
print('当前进程的父进程(pid=%d)正在运行......' %(os.getppid()))
print('开始创建子进程......')
pid = os.fork()
if pid == 0:
print('这是子进程返回的,子进程的pid为%d,父进程为%d' %(os.getpid(),os.getppid()))
else:
print('这是父进程返回的,返回值为子进程的pid,为%s' %(pid))
在命令行查看pycharm的进程pid:ps aux | grep charm
multiprocess 跨平台实现多进程
如果你打算编写多进程的服务程序,Unix/Linux无疑是正确的选择。由于Windows没有fork调用,难道在Windows上无法用Python编写多进程的程序?
由于Python是跨平台的,自然也应该提供一个跨平台的多进程支持。
multiprocessing模块就是跨平台版本的多进程模块,multiprocessing模块提供了一个Process类来代表一个进程对象
Process类属性及使用方法
1.属性
Process 类用来描述一个进程对象。
创建子进程的时候,只需要传入一个执行函数和函数的参数即可完成 Process 实例的创建。
- star() 方法启动进程;
- join() 方法实现进程间的同步,等待所有进程退出;
- close() 用来阻止多余的进程涌入进程池 Pool 造成进程阻塞。
2.使用方法
创建子进程:
-
传入一个执行函数和函数的参数,创建一个Process实例;
-
start()方法启动子进程,这样创建进程比fork()还要简单;
-
join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
创建Process 实例:
multiprocessing.Process(group=None, target=None, name=None, args=(),kwargs={}, *, daemon=None)
- target 是函数名字,需要调用的函数
- args 函数需要的参数,以 tuple 的形式传入
类的实例化实现
import multiprocessing
def job():
print('当前子进程的名称%s......' %(multiprocessing.current_process()))
p1 = multiprocessing.Process(target=job,name='我的第一个子进程')
p1.start()
p2 = multiprocessing.Process(target=job,name='我的第二个子进程')
p2.start()
# join方法,等待所有的子进程执行结束,再执行主进程
p1.join()
p2.join()
print('任务执行结束......')
类的继承实现
import multiprocessing
class MyProcess(multiprocessing.Process):
# 重写run方法 ==== start方法默认执行run方法
def run(self):
print('当前子进程的名称%s......' %(multiprocessing.current_process()))
p1 =MyProcess(name = 'first')
p1.start()
p2 =MyProcess(name = 'second')
p2.start()
p1.join()
p2.join()
print('finish......')
多进程案例
import threading
import multiprocessing
from mytimeit import timeit
def job(li):
return sum(li)
@timeit
def use_thread():
li = range(1, 1000001)
threads = []
for i in range(5):
t = threading.Thread(target=job, args=(li,))
t.start()
threads.append(t)
[thread.join() for thread in threads]
@timeit
def use_process():
li = range(1, 1000001)
processes = []
# 1). 开启的进程数是有瓶颈的,取决于CPU个数
# 2). 如果处理的数据比较小,不建议使用多进程,因为创建进程和销毁进程需要时间
# 3). 如果处理数据足够大,0<进程数<cpu个数
# 4). 当进程数>cpu个数时,不会报错,但运行的进程数还是等于cpu的个数
for i in range(5):
p = multiprocessing.Process(target=job, args=(li,))
p.start()
processes.append(p)
[process.join() for process in processes]
@timeit
def no_use():
li = range(1, 1000001)
for i in range(5):
job(li)
if __name__ == '__main__':
use_thread()
use_process()
no_use()
当计算数量越大时,多进程的效率显示越明显:
进程锁
对文件进行操作时,给操作过程加上进程锁
示例:
import multiprocessing
def work(f,item,lock):
# 获得锁
lock.acquire()
try:
with open(f,'a+') as f:
f.write('a task%s\n' %(item))
except Exception as e:
print('产生异常......',e)
finally:
# 释放锁
lock.release()
def main():
# 1). 实例化一个进程锁
lock = multiprocessing.Lock()
filename = 'doc/my.log'
processes = []
for i in range(4):
p = multiprocessing.Process(target=work,args=(filename,i,lock))
p.start()
processes.append(p)
[process.join() for process in processes]
if __name__ == '__main__':
main()
写入文件 my.log 中:
进程池
在使用Python进行系统管理时,特别是同时操作多个文件目录或者远程控制多台主机,并行操作可以节约大量时间,如果操作的对象数目不大时,还可以直接使用Process类动态生成多个进程,几十个尚可,若上百个甚至更多时,手动限制进程数量就显得特别繁琐,此时进程池就显得尤为重要。
进程池Pool类可以提供指定数量的进程供用户调用;
当有新的请求提交至Pool中时:
- 若进程池尚未满,就会创建一个新的进程来执行请求;
- 若进程池中的进程数已经达到规定的最大数量,则该请求就会等待,直到进程池中有进程结束,才会创建新的进程来处理该请求。
实现进程池1
import multiprocessing
def job(id):
print("start %d...." % (id))
print("end %d...." % (id))
# 创建进程池对象
pool = multiprocessing.Pool(processes=4)
# 给进程池分配任务
for i in range(10):
pool.apply_async(job, args=(i + 1,))
pool.close()
# 等待所有的子进程执行结束, 关闭进程池对象
pool.join()
print("所有任务执行结束.....")
实现进程池2(submit)
from concurrent.futures import ProcessPoolExecutor
def job(id):
print("start %d...." % (id))
print("end %d...." % (id))
return '执行结果:%d' %(id)
# 创建进程池对象
pool = ProcessPoolExecutor(max_workers=4)
for id in range(10):
# 分配任务给子进程, 并且返回一个Future对象
f = pool.submit(job,id)
# 判断子进程是否执行结束
print(f.done())
# 查看该子进程执行的结果
print(f.result())
实现进程池3 (map)
from concurrent.futures import ProcessPoolExecutor
def job(id):
print("start %d...." % (id))
print("end %d...." % (id))
# 创建进程池对象
pool = ProcessPoolExecutor(max_workers=4)
pool.map(job, range(10))
进程池应用
多进程拷贝文本文件
拷贝的原理:
1). 读取源文件的内容;
2). 写入新的文件中。
方法一:
import os
import multiprocessing
import time
def copyFileTask(oldFolderName, newFolderName, filename, queue):
"""
import os
# 拼接生成绝对路径
os.path.join('/mnt', 'file')
'/mnt/file'
os.path.join('/mnt/', 'file')
'/mnt/file'
:param oldFolderName:day20
:param newFolderName:day20_backup_2019-01-20
:param filename:file1
:return:
"""
fr = open(os.path.join(oldFolderName, filename), 'rb')
fw = open(os.path.join(newFolderName, filename), 'wb')
with fr, fw:
content = fr.read(1024 * 3)
while content:
fw.write(content)
queue.put(filename)
def main():
# 判断备份目录是否存在
while True:
oldFolderName = input('请输入备份的目录名:')
if os.path.exists(oldFolderName):
break
else:
print('%s目录不存在' %(oldFolderName))
dateName = time.strftime('%Y-%m-%d %H:%M')
newFolderName = oldFolderName + '_backup_' + dateName
if os.path.exists(newFolderName):
os.system('rm -fr %s' %(newFolderName)) # 删除空目录
# 新建备份的目录
os.mkdir(newFolderName)
print('正在创建备份目录%s......' % (newFolderName))
# 获取备份目录中的所有文件名
fileNames = os.listdir(oldFolderName)
# 如果使用进程池,那么就需要使用Manager().Queue()队列才能在各子进程间通信,否则沒用
# 队列,存储已经备份的文件
queue = multiprocessing.Manager().Queue()
pool = multiprocessing.Pool(processes=4)
for name in fileNames:
# 给进程池分配任务
pool.apply_async(copyFileTask, args=(oldFolderName, newFolderName, name, queue))
# 显示备份的进度(百分率) ----> 100个文件, 1个文件 1%
num = 0 # 当前备份的文件数
allNum = len(fileNames) # 总备份的文件数
while num < allNum:
queue.get()
num += 1
copyRate = num / allNum
# \r使得光标不换行
print('\r\r备份进度为%.2f%%' % (copyRate * 100), end='')
pool.close()
pool.join()
print('备份成功!')
if __name__ == '__main__':
main()
方法二:
import os
import multiprocessing
import time
from concurrent.futures import ProcessPoolExecutor
def copyFileTask(oldFolderName, newFolderName, filename, queue):
fr = open(os.path.join(oldFolderName, filename), 'rb')
fw = open(os.path.join(newFolderName, filename), 'wb')
with fr, fw:
content = fr.read(1024 * 3)
while content:
fw.write(content)
queue.put(filename)
def main():
while True:
oldFolderName = input('请输入备份的目录名:')
if os.path.exists(oldFolderName):
break
else:
print('%s目录不存在' %(oldFolderName))
dateName = time.strftime('%Y-%m-%d %H:%M')
newFolderName = oldFolderName + '_backup_' + dateName
if os.path.exists(newFolderName):
os.system('rm -fr %s' %(newFolderName))
os.mkdir(newFolderName)
print('正在创建备份目录%s......' % (newFolderName))
fileNames = os.listdir(oldFolderName)
queue = multiprocessing.Manager().Queue()
pool = ProcessPoolExecutor(max_workers=4)
for name in fileNames:
pool.submit(copyFileTask,oldFolderName,newFolderName,name,queue)
num = 0
allNum = len(fileNames)
while num < allNum:
queue.get()
num += 1
copyRate = num / allNum
print('\r\r备份进度为%.2f%%' % (copyRate * 100), end='')
print('备份成功!')
if __name__ == '__main__':
main()
进程间的通信
两种方式:
- 生产者-消费者模型
- 管道Pipe
生产者-消费者模型
import multiprocessing
import time
class Producer(multiprocessing.Process):
def __init__(self,queue):
super(Producer, self).__init__()
self.queue = queue
def run(self):
# 将需要通信的数据写入队列中
for i in range(10):
self.queue.put(i)
time.sleep(0.1)
print('生产者传递消息,内容为%s' %(i))
class Consumer(multiprocessing.Process):
def __init__(self,queue):
super(Consumer, self).__init__()
self.queue = queue
def run(self):
while True:
time.sleep(0.1)
recvData = self.queue.get()
print('消费者接收到生产者传递的数据:%s' %(recvData))
if __name__ == '__main__':
q = multiprocessing.Queue()
p = Producer(q)
c = Consumer(q)
p.start()
c.start()
p.join()
c.join()
管道Pipe
- Pipe管道,进程间通信的方式,类似于 ls | wc -l;
- Pipe( )返回两个连接对象,分别代表管道的两边;
- 管道通信操作的方法:send( ), recv( );
- 管道间的通信是双向的, 既可以发送,也可以接收;
import multiprocessing
import time
def before(conn):
while True:
data = [42, None, 34, 'hello']
conn.send(data)
print('正在发送数据:%s' % (data))
time.sleep(1)
def after(conn):
while True:
print('接收到数据:', conn.recv())
time.sleep(1)
def main():
before_conn, after_conn = multiprocessing.Pipe()
p1 = multiprocessing.Process(target=before, args=(before_conn,))
p1.start()
p2 = multiprocessing.Process(target=after, args=(after_conn,))
p2.start()
p1.join()
p2.join()
if __name__ == '__main__':
main()
分布式进程
在Thread和Process中,应当优选Process,因为Process更稳定,而且,Process可以分布到多台机器上,而Thread最多只能分布到同一台机器的多个CPU上。
Python的multiprocessing模块不但支持多进程,其中managers子模块还支持把多进程分布到多台机器上。一个服务进程可以作为调度者,将任务分布到其他多个进程中,依靠网络通信。由于managers模块封装很好,不必了解网络通信的细节,就可以很容易地编写分布式多进程程序。
举个例子:如果我们已经有一个通过Queue通信的多进程程序在同一台机器上运行,现在,由于处理任务的进程任务繁重,希望把发送任务的进程和处理任务的进程分布到两台机器上。怎么用分布式进程实现?
我们先看服务进程,服务进程负责启动Queue,把Queue注册到网络上,然后往Queue里面写入任务:
import random
from queue import Queue
from multiprocessing.managers import BaseManager
# 1. 创建存储任务需要的队列
task_queue = Queue()
# 2. 存储任务执行结果的队列
result_queue = Queue()
# 3. 将队列注册到网上(使得其它主机也可以访问)
BaseManager.register('get_task_queue', callable=lambda: task_queue)
BaseManager.register('get_result_queue', callable=lambda: result_queue)
# 绑定ip和端口,并且来个暗号
manager = BaseManager(address=('172.25.254.36', 4000), authkey=b'westos')
# 4. 启动manager对象,开始共享队列
manager.start()
# 5. 通过网络访问共享的Queue对象
# BaseManager.register会注册一个方法,当调用方法时,执行函数lambda :task_queue
task = manager.get_task_queue()
result = manager.get_result_queue()
# 6. 往队列里放执行任务需要的数据
for i in range(1000):
n = random.randint(1,100)
task.put(n)
print('任务列表中加入任务:%d' %(n))
# 7. 从result队列中读取各个机器中任务执行的结果
for i in range(1000):
res = result.get()
print('任务队列执行的结果:%s' %(res))
# 8.关闭manager对象,取消共享的队列
manager.shutdown()
然后,在另一台机器(可有多台机器)上启动任务进程(本机上启动也可以):
from multiprocessing.managers import BaseManager
import time
# 1. 连接Master端,获取共享队列(ip是master端的ip,port也是master端manager进程绑定的port)
slave = BaseManager(address=('172.25.254.36', 4000), authkey=b'westos')
# 2. 注册队列,获取共享的队列内容
BaseManager.register('get_task_queue')
BaseManager.register('get_result_queue')
# 3. 连接master端
slave.connect()
# 4. 通过网络访问共享的队列
task = slave.get_task_queue()
result = slave.get_result_queue()
# 5. 读取管理者共享的内容,并依次执行
for i in range(100):
n = task.get()
print('运行任务 %d ** 2:' % (n))
res = '%d ** 2 = %d' % (n, n ** 2)
time.sleep(1)
# 将任务的运行结果放入队列
result.put(res)
print('执行结束......')
先启动 master.py 服务进程: