python多任务——进程基础介绍(Process)及多进程操作

一、进程介绍

进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。

广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

1、进程概念

  • 进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。
  • 进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行),它才能成为一个活动的实体,我们称其为进程。
  • 进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。

2、引入进程原因

  • 为了提高资源利用率和系统处理能力,现阶段计算机系统都是多道程序系统,即多道程序并发执行。
  • 优化系统资源,方便计算机调度,避免系统运算紊乱。
  • 进程是一种数据结构,能够清晰的刻画动态系统的内在规律,增加程序运行时的动态性。

3、进程特征

  • 动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
  • 并发性:任何进程都可以同其他进程一起并发执行。
  • 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位。
  • 异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进。

4、进程的状态

二、multiprocessing

python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。Python提供了multiprocessing。

multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。

注:与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内 

不仅如此,Process 类中也提供了一些常用的属性和方法,如下表所示:

属性名或方法名 功能
run() 第 2 种创建进程的方式需要用到,继承类中需要对方法进行重写,该方法中包含的是新进程要执行的代码。
start() 和启动子线程一样,新创建的进程也需要手动启动,该方法的功能就是启动新创建的线程。
join([timeout]) 和 thread 类 join() 方法的用法类似,其功能是在多进程执行过程,其他进程必须等到调用 join() 方法的进程执行完毕(或者执行规定的 timeout 时间)后,才能继续执行;
is_alive() 判断当前进程是否还活着。
terminate() 中断该进程。
name属性 可以为该进程重命名,也可以获得该进程的名称。
daemon 和守护线程类似,通过设置该属性为 True,可将新建进程设置为“守护进程”。
pid 返回进程的 ID 号。大多数操作系统都会为每个进程配备唯一的 ID 号。

1、创建进程

Python multiprocessing 模块提供了 Process 类,该类可用来创建新进程。和使用 Thread 类创建多线程方法类似,使用 Process 类创建多进程也有以下 2 种方式:

  • 直接创建 Process 类的实例对象,由此就可以创建一个新的进程;
  • 通过继承 Process 类的子类,创建实例对象,也可以创建新的进程。注意,继承 Process 类的子类需重写父类的 run() 方法。

(1)Process类

Process([group [, target [, name [, args [, kwargs]]]]])

  • 由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)
  • 强调: 1. 需要使用关键字的方式来指定参数    2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号

参数介绍:

  • group参数未使用,值始终为None
    target表示调用对象,即子进程要执行的任务
    args表示调用对象的位置参数元组,args=(1,2,'anne',)
    kwargs表示调用对象的字典,kwargs={'name':'anne','age':18}
    name为子进程的名称

(2)方法一 :直接创建Process类的实例对象

import multiprocessing
import time

def test1():
    for i in range(5):
        print("hanmh_ test1 -> %d" % i)
        time.sleep(1)

def test2():
    for i in range(5):
        print("hanmh_ test2 -> %d" % i)
        time.sleep(1)


def main():
    p1 = multiprocessing.Process(target=test1)
    p2 = multiprocessing.Process(target=test2)
    p1.start()
    p2.start()
    return None

if __name__ == '__main__':
    main()

(3)方法二:通过继承Process类的子类,创建实例对象

import multiprocessing
import time

class MyProcess(multiprocessing.Process):
    def __init__(self, name):
        super().__init__()
        self.name = name
    def run(self):
        print("%s is running!!!" % self.name)
        time.sleep(2)


def main():
    p1 = MyProcess("hanmh_1")
    p2 = MyProcess("hanmh_2")
    p1.start()
    p2.start()
    return None

if __name__ == '__main__':
    main()

三、进程间通信

虽然可以用文件共享数据实现进程间通信,但问题是:

1)效率低(共享数据基于文件,而文件是硬盘上的数据) 2)需要自己加锁处理

因此我们最好找寻一种解决方案能够兼顾:1)效率高(多个进程共享一块内存的数据)2)帮我们处理好锁问题。

mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。

  • 队列和管道都是将数据存放于内存中
  • 队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来, 我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性

 1. 队列(推荐使用)

创建队列的类(底层就是以管道和锁定的方式实现)

 Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。 

 参数介绍:

 maxsize是队列中允许最大项数,省略则无大小限制。    
import multiprocessing

def main():
    q = multiprocessing.Queue(3)
    q.put("111")
    q.put(222)
    q.put([11, 22, 33])

    print(q.full())

    while not q.empty():
        print(q.get())

    return None

if __name__ == '__main__':
    main()

Queue的加入,可以体现解耦的思想,在做任务时可以多开,一个用来收数据,一个用来处理数据。

2、多进程之间通过Queue来实现数据共享

import multiprocessing

def download_from_web(myQueue):
    """模拟从网上下载的数据"""
    data = [11, 22, 33, 44, 55]
    #向队列中写入数据
    for temp in data:
        myQueue.put(temp)
    print("下载器已经下载完了数据并且存入到了队列中.........")

def analysis_data(myQueue):
    """模拟数据处理"""
    waitting_data = list()
    while not myQueue.empty():
        temp_data = myQueue.get()
        waitting_data.append(temp_data)
    print(waitting_data)
    print("目前已经完成数据处理..........")

def main():
    #1、创建一个队列
    myQueue = multiprocessing.Queue()

    #2、创建多个进程,将队列的引用当作实参进行传递
    p1 = multiprocessing.Process(target=download_from_web, args=(myQueue,))
    p2 = multiprocessing.Process(target=analysis_data, args=(myQueue,))
    p1.start()
    p2.start()
    return None

if __name__ == '__main__':
    main()

四、进程池Pool

在利用Python进行系统管理的时候,特别是同时操作多个文件目录,或者远程控制多台主机,并行操作可以节约大量的时间。多进程是实现并发的手段之一,需要注意的问题是:

(1)很明显需要并发执行的任务通常要远大于核数

(2)一个操作系统不可能无限开启进程,通常有几个核就开几个进程

(3)进程开启过多,效率反而会下降(开启进程是需要占用系统资源的,而且开启多余核数目的进程也无法做到并行)

当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,十几个还好,但如果是上百个,上千个。。。手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。

我们就可以通过维护一个进程池来控制进程数目,比如httpd的进程模式,规定最小进程数和最大进程数... 

对于远程过程调用的高级应用程序而言,应该使用进程池,Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,就重用进程池中的进程。

1、创建进程池的类

如果指定numprocess为3,则进程池会从无到有创建三个进程,然后自始至终使用这三个进程去执行所有任务,不会开启其他进程

Pool([numprocess  [,initializer [, initargs]]]):创建进程池 

 参数介绍:

numprocess:要创建的进程数,如果省略,将默认使用cpu_count()的值
initializer:是每个工作进程启动时要执行的可调用对象,默认为None
initargs:是要传给initializer的参数组

主要方法:

1、p.apply(func [, args [, kwargs]])在一个池工作进程中执行func(*args,**kwargs),然后返回结果。
需要强调的是:此操作并不会在所有池工作进程中并执行func函数。如果要通过不同参数并发地执行func函数,必须从不同线程调用p.apply()函数或者使用p.apply_async()

2、p.apply_async(func [, args [, kwargs]]):
在一个池工作进程中执行func(*args,**kwargs),然后返回结果。
此方法的结果是AsyncResult类的实例,callback是可调用对象,接收输入参数。当func的结果变为可用时,将理解传递给callback。callback禁止执行任何阻塞操作,否则将接收其他异步操作中的结果。
   
3、p.close():关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成

4、P.jion():等待所有工作进程退出。此方法只能在close()或teminate()之后调用

2、简单使用

import multiprocessing
import time, os, random

def worker(msg):
    t_start = time.time()
    print("%s 开始执行, 进程号为:%d" % (msg, os.getpid()))
    #random.random()随机生成0-1之间的浮点数
    time.sleep(random.random() * 4)
    t_stop = time.time()
    print(msg, " 执行完毕,耗时%0.2f" & (t_stop-t_start))

def main():
    #定义一个进程池,最大进程数3
    myPool = multiprocessing.Pool(3)
    for i in range(0, 10):
        #Pool().apply_async(要调用的目标,(传递给目标的参数元组,))
        #每次循环将会用空闲出来的子进程去调用目标
        myPool.apply_async(worker, (i,))
    print("-------start------")
    #关闭进程池,关闭后pool不再接收新的请求
    myPool.close()
    #等待pool中所有子进程执行完成,必须放在close语句之后
    myPool.join()
    print("-------end-------")

    return None

if __name__ == '__main__':
    main()

猜你喜欢

转载自blog.csdn.net/weixin_42067873/article/details/111754722