进程

进程

linux 系统如何创建子进程

  1. Unix/Linux操作系统提供了一个fork()系统调用,它非常特殊。
    普通的函数调用,调用一次,返回一次,但是fork( )调用一次,返回两次;
    因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。

  2. 子进程永远返回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
  1. Pipe管道,进程间通信的方式,类似于 ls | wc -l;
  2. Pipe( )返回两个连接对象,分别代表管道的两边;
  3. 管道通信操作的方法:send( ), recv( );
  4. 管道间的通信是双向的, 既可以发送,也可以接收;

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 服务进程:

在这里插入图片描述
master.py 进程发送完任务后,开始等待result队列的结果。现在启动 slave.py 进程:

在这里插入图片描述
slave.py 进程结束,在 master.py 进程中会打印出 slave.py 返回的结果:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值