进程

进程Process

什么是进程

image-20211204102521781

进程(Process):拥有自己独立的堆和栈,既不共享堆,也不共享栈,进程由操作系统调度;进程切换需要的资源很最大,效率低。

对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。

image-20211205110401975

进程的优缺点

进程的优点:

  1. 可以使用计算机多核,进行任务的并行执行,提高执行效率
  2. 运行不受其他进程影响,创建方便
  3. 空间独立,数据安全

进程的缺点:

  • 进程的创建和删除消耗的系统资源较多

进程的创建方式(方法模式)

Python的标准库提供了个模块:multiprocessing

进程的创建可以通过分为两种方式:

1. 方法包装

2. 类包装

创建进程后,使用start()启动进程

【示例】方法模式创建进程

from multiprocessing import Process
import os
from time import sleep

def func1(name):
  print("当前进程ID:",os.getpid())    #getpid意思是获取进程ID(get process id)
  print("父进程ID:",os.getppid())    #getppid意思是获取父进程ID(get parent process id)
  print(f"Process:{name} start")
  sleep(3)
  print(f"Process:{name} end")

'''
这是一个关于windows上多进程实现的bug。
如果不加__main__限制的话,就会无限递归创建子进程,进而报错。
在windows上,子进程会自动import启动它的这个文件,而在import的时候是会自动执行这些语句的。
于是import的时候使用 __name__ =="__main__" 保护起来就可以了。
'''

if __name__ =="__main__":
  print("当前进程ID:",os.getpid())
  # 创建进程
  p1 = Process(target=func1, args=('p1',))
  p2 = Process(target=func1, args=('p2',))
  p1.start()
  p2.start()

进程的创建方式(继承Process类)

和使用Thread 类创建子线程的方式非常类似,使用 Process 类创建实例化对象,其本质是调用该类的构造方法创建新进程。Process 类的构造方法格式如下:

def __init__(self,group=None,target=None,name=None,args=(),kwargs={})

其中,各个参数的含义为:

group:该参数未进行实现,不需要传参;

target:为新建进程指定执行任务,也就是指定一个函数;

name:为新建进程设置名称;

args:为 target 参数指定的参数传递非关键字参数;

kwargs:为 target 参数指定的参数传递关键字参数。

【示例】类的方式创建进程

from multiprocessing import Process
from time import sleep

class MyProcess(Process):
  def __init__(self,name):
    Process.__init__(self)
    self.name = name

  def run(self):
    print(f"Process:{self.name} start")
    sleep(1)
    print(f"Process:{self.name} end")

if __name__ == '__main__':
  p1 = MyProcess("p1")
  p2 = MyProcess("p2")
  p1.start()
  p2.start()

Queue实现进程间通信

image-20211205112354941

前面讲解了使用 Queue 模块中的 Queue 类实现线程间通信,但要实现进程间通信,需要使用 multiprocessing 模块中的 Queue 类。

简单的理解 Queue 实现进程间通信的方式,就是使用了操作系统给开辟的一个队列空间,各个进程可以把数据放到该队列中,当然也可以从队列中把自己需要的信息取走。

【示例】使用Queue实现进程间通信的经典代码

from multiprocessing import Process, Queue
from time import sleep

class MyProcess(Process):
    def __init__(self,name,mq):
        Process.__init__(self)
        self.name = name
        self.mq = mq
    def run(self):
        print(f"Process:{self.name} start")
        print('主进程存放的数据:', self.mq.get()) #打印主进程存放的数据
        sleep(2)
        # 在子进程里存放数据,可以使用主进程提取
        self.mq.put(f'子进程存放的数据:{self.name}')
        print(f"Process:{self.name} end")

if __name__ == '__main__':  #创建主进程
    mq = Queue()
    '''mq不可以作为普通的全局变量,
    创建子进程时需要作为参数传进子进程,类里面也要写self.mq而不是mq,
    因为mq在当前进程为全局变量,但是mq会与创建的新的进程没有关系,从而导致报错'''
    #在主进程里存放数据,可以使用子进程提取
    mq.put('1')   #当字符串'1'被使用后会消失
    mq.put('2')   #所以下次进程会使用下一条数据字符串'2'
    mq.put('3')
    #进程列表
    p_list = []   #将进程全部存放在列表内
    for i in range(3):
        p = MyProcess(f'p{i}',mq)   #创建子进程
        p_list.append(p)
    for p in p_list:                #启动被存放在列表内的子进程
        p.start()

    #因为要打印主进程里存放的信息,所以使用join让子进程先结束
    for p in p_list:
        p.join()
    '''使用join之后,可以确保join后面的代码在子进程运行(存放数据)结束后运行
    如果不使用join,则join后面的代码可能在子进程运行(存放数据)之前运行,
    数据还没有存放,那么后面就无法打印子进程存放的数据'''
    print(mq.get())    #打印子进程里存放的数据
    print(mq.get())
    print(mq.get())

Pipe实现进程间通信

image-20211205115748323

Pipe 直译过来的意思是“管”或“管道”,和实际生活中的管(管道)是非常类似的。

Pipe方法返回(conn1, conn2)代表一个管道的两个端。

Pipe方法有duplex参数,如果duplex参数为True(默认值),那么这个参数是全双工模式,也就是说conn1和conn2均可收发。若duplex为False,conn1只负责接收消息,conn2只负责发送消息。send和recv方法分别是发送和接受消息的方法。例如,在全双工模式下,可以调用conn1.send发送消息,conn1.recv接收消息。如果没有消息可接收,recv方法会一直阻塞。如果管道已经被关闭,那么recv方法会抛出EOFError。

【示例】使用Pipe管道实现进程间通信

#coding=utf-8
import multiprocessing
from time import sleep

def func1(conn1):
  sub_info = "Hello!"
  print(f"进程1--{multiprocessing.current_process().pid}发送数据:{sub_info}")
  sleep(1)
  conn1.send(sub_info)  #发送进程1的数据
  print(f"来自进程2:{conn1.recv()}")
  sleep(1)
def func2(conn2):
  sub_info = "你好!"
  print(f"进程2--{multiprocessing.current_process().pid}发送数据:{sub_info}")
  sleep(1)
  conn2.send(sub_info)  #发送进程2的数据
  print(f"来自进程1:{conn2.recv()}")
  sleep(1)

if __name__ == '__main__':
  #创建管道
  conn1,conn2 = multiprocessing.Pipe()
  # 创建子进程
  process1 = multiprocessing.Process(target=func1,args=(conn1,))
  process2 = multiprocessing.Process(target=func2,args=(conn2,))
  # 启动子进程
  process1.start()
  process2.start()

Manager管理器

管理器提供了一种创建共享数据的方法,从而可以在不同进程中共享。

【示例】管理器Manager实现进程通信

#coding=utf-8
from multiprocessing import Process,current_process
from multiprocessing import Manager

def fun1(name,m_list,m_dict):
  m_list.append('你好')      #子进程往列表里添加数据
  m_dict['name'] = '尚学堂'  #子进程往字典里添加数据

if __name__ == "__main__":
  #使用后需要关闭管理器,所以用with自动关闭
  with Manager() as mgr:
    m_list = mgr.list()
    m_dict = mgr.dict()
    m_list.append('Hello!!')  #主进程里往列表增加信息
    #两个进程不能直接互相使用对象,需要互相传递
    p1 = Process(target=fun1,args=('p1',m_list,m_dict))
    p1.start()
    p1.join()  #等p1进程结束,主进程继续执行
    print(m_list)
    print(m_dict)

进程池(Pool)

Python提供了更好的管理多个进程的方式,就是使用进程池。

进程池可以提供指定数量的进程给用户使用,即当有新的请求提交到进程池中时,如果池未满,则会创建一个新的进程用来执行该请求;反之,如果池中的进程数已经达到规定最大值,那么该请求就会等待,只要池中有进程空闲下来,该请求就能得到执行。

使用进程池的优点:

  1. 提高效率,节省开辟进程和开辟内存空间的时间及销毁进程的时间
  2. 节省内存空间
类/方法功能参数
Pool(processes)创建进程池对象processes表示进程池中有多少进程
pool.apply_async(func,args,kwds)异步执行 ;将事件放入到进程池队列func 事件函数 args 以元组形式给func传参kwds 以字典形式给func传参 返回值:返回一个代表进程池事件的对象,通过返回值的get方法可以得到事件函数的返回值
pool.apply(func,args,kwds)同步执行;将事件放入到进程池队列func 事件函数 args 以元组形式给func传参 kwds 以字典形式给func传参
pool.close()关闭进程池
pool.join()回收进程池
pool.map(func,iter)类似于python的map函数,将要做的事件放入进程池func 要执行的函数 iter 迭代对象

【示例】进程池使用案例

#coding=utf-8
from multiprocessing import Pool
import os
from time import sleep

def func1(name):
  print(f"当前进程的ID:{os.getpid()},{name}")
  sleep(2)
  return name #返回数据,可以传给callback里的func2

def func2(args):
  print(args)

if __name__ == "__main__":
  pool = Pool(5)      #表示进程池中有5个进程

  pool.apply_async(func = func1,args=('sxt1',),callback=func2)  
  #callback表示把func1返回的数据传给func2
  pool.apply_async(func = func1,args=('sxt2',),callback=func2)
  pool.apply_async(func = func1,args=('sxt3',),callback=func2)
  pool.apply_async(func = func1,args=('sxt4',))
  pool.apply_async(func = func1,args=('sxt5',))
  pool.apply_async(func = func1,args=('sxt6',))  #从第5个进程开始循环使用进程池里的进程
  pool.apply_async(func = func1,args=('sxt7',))
  pool.apply_async(func = func1,args=('sxt8',))

  pool.close()  #进程池使用完需要关闭
  pool.join()   #保证子进程在主进程结束前运行完

 【示例】使用with管理进程池

#coding=utf-8
from multiprocessing import Pool
import os
from time import sleep

def func1(name):
  print(f"当前进程的ID:{os.getpid()},{name}")
  sleep(2)
  return name

if __name__ == "__main__":
  with Pool(5) as pool:
    args = pool.map(func1,('sxt1,','sxt2,','sxt3,','sxt4,','sxt5,','sxt6,','sxt7,','sxt8,'))
    for a in args:
      print(a)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值