多进程编程

多进程

  python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。Python提供了非常好用的多进程包multiprocessing,只需要定义一个函数,Python会完成其他所有事情。借助这个包,可以轻松完成从单进程到并发执行的转换。multiprocessing支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。

  multiprocessing是一个包,它支持使用类似于线程模块的API来生成进程。multiprocessing包提供本地和远程并发,通过使用子进程而不是线程有效地旁路全局解释器锁。因此,multiprocessing模块允许编程人员充分利用给定机器上的多个处理器。它可以在Unix和Windows上运行。

为什么python下想要充分利用多核CPU,就用多进程?
  因为每个进程有各自独立的GIL,互不干扰,这样就可以真正意义上的并行执行,所以在python中,多进程的执行效率优于多线程(仅仅针对多核CPU而言)

1.创建一个多进程

import multiprocessing
import time

def hello(name):
    print("Hello %s"%name)
    time.sleep(1)

if __name__=='__main__':
    for i in range(10):
        i=multiprocessing.Process(target=hello,args=('P1',))
        i.start()
View Code
import multiprocessing,threading
import time

def thread_run(num):
    print("I'm a thread of %sProcess"%num,threading.get_ident())#threading.get_ident()获取线程号

def hello(name):
    print("Hello %sProcess"%name)
    t1=threading.Thread(target=thread_run,args=(name,)) #在进程中插入线程
    t1.start()
    time.sleep(1)

if __name__=='__main__':
    for i in range(10):
        i=multiprocessing.Process(target=hello,args=('%s'%i,))
        i.start()
进程中插入线程
import multiprocessing,threading
import time
import os

def thread_run(num):
    print("I'm a thread of %sProcess"%num,threading.get_ident())#threading.get_ident()获取线程号
    
def hello(name):
    print("parent process:",os.getppid())#获取父进程号  每一个进程都是由父进程启动的,这里由pychram启动的
    print("Hello %sProcess"%name,os.getpid())#os.getpid()获取进程号
    t1=threading.Thread(target=thread_run,args=(name,)) #在进程中插入线程
    t1.start()
    time.sleep(1)

if __name__=='__main__':
    for i in range(10):
        i=multiprocessing.Process(target=hello,args=('%s'%i,))
        i.start()
        # i.join()
获取进程号与父进程号

 

进程间的通信

  不同进程间内存是不共享的,要想实现两个进程间的数据交换,可以用以下方法:

Queues
# 使用方法跟threading里的queue差不多
from multiprocessing import Process, Queue

def f(q):
    q.put(['li','male' ,23 ])

if __name__ == '__main__':
    q = Queue()
    p = Process(target=f, args=(q,))
    p.start()
    print(q.get())
    p.join()

# 执行结果
# ['li', 'male', 23]
View Code

Pipes
  Pipe()返回的两个连接对象表示管道的两端。每个连接对象都有send()和recv()方法(等等)。请注意,如果两个进程(或线程)尝试同时读取或写入管道的同一端,则管道中的数据可能会损坏。
  Pipe()函数返回通过管道连接的一对连接对象,默认情况下是双向(双向)。例如:
from multiprocessing import Process, Pipe

def f(conn):
    conn.send(['li','male' ,23 ])
    conn.close()

if __name__ == '__main__':
    parent_conn, child_conn = Pipe()
    p = Process(target=f, args=(child_conn,))
    p.start()
    print(parent_conn.recv())
    p.join()
View Code

 注意:Queue和pipe的区别: pipe用来在两个进程间通信。Queue用来在多个进程间实现通信。而且Pipe和Queue,也只可接收可pickle对象,这些都是由它们的实现方式造成的。因为它们不支持不可pickle对象,带有系统状态的对象,如套接字可能不适用

 

 Value + Array

  Value + Array 是python中共享内存 映射文件的方法,利用Value或Array把数据存储在一个共享的列表中。速度比较快。

from multiprocessing import Process, Value, Array

def f(n, a):
    n.value = n.value + 1
    for i in range(len(a)):
        a[i] = a[i] * 10

if __name__ == '__main__':
    num = Value('i', 1)
    arr = Array('i', range(10))

    p = Process(target=f, args=(num, arr))
    p.start()
    p.join()
    print(num.value)
    print(arr[:])

    p2 = Process(target=f, args=(num, arr))
    p2.start()
    p2.join()
    print(num.value)
    print(arr[:])

#执行结果:
2
[0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
3
[0, 100, 200, 300, 400, 500, 600, 700, 800, 900]
View Code

  

Manager

  Python实现多进程间通信的方式有很多种,例如队列,管道等,但这些方式只适用于多个进程都是源于同一个父进程的情况。如果多个进程不是源于同一个父进程,只能用共享,信号量等方式,但是这些方式对于复杂的数据结构,例如Queue,dict,list等,使用起来比较麻烦,不够灵活。
  Manager是一种较为高级的多进程通信方式,它能支持Python支持的的任何数据结构。
它的原理是:先启动一个ManagerServer进程,这个进程是阻塞的,它监听一个socket,然后其他进程(ManagerClient)通过socket来连接到ManagerServer,实现通信。

 

  Manager()返回的管理器对象控制一个保存Python对象的服务器进程,并允许其他进程使用代理来操作它们。 Manager()返回的管理器将支持类型:list,dict,Namespace,Lock,RLock,Semaphore,BoundedSemaphore,Condition,Event,Barrier,Queue,Value和Array。例如,

import os
from multiprocessing import Process, Manager

def f(d,l):
    d[1] = '1'
    d['2'] = 2
    d[0.25] = None
    l.append(os.getpid())
    print(l)

if __name__ == '__main__':
    with Manager() as manager:#等同于manager=Manager()
        d = manager.dict()#生成一个可在多个进程之间进行传递和共享的字典
        d['1'] = '1'
        d['2'] = '2'
        d['3'] = None
        l = manager.list(range(5))
        p_list = []
        for i in range(10):
            p = Process(target=f, args=(d,l))
            p.start()
            p_list.append(p)
        for res in p_list:
            res.join()

        print(d)
        print(l)
Manager

 

进程池

  在利用Python进行系统管理的时候,特别是同时操作多个文件目录,或者远程控制多台主机,并行操作可以节约大量的时间。当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,10几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,这时候进程池Pool发挥作用的时候就到了。
  Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来它。

  multiprocessing 模块下的Pool类下的几个方法:

apply()

函数原型:

apply(func[, args=()[, kwds={}]])

该函数用于传递不定参数,主进程会被阻塞直到函数执行结束(不建议使用,并且3.x以后不在出现)。#实际上就是串行

apply_async()

函数原型:

apply_async(func[, args=()[, kwds={}[, callback=None]]])

与apply用法一样,但它是非阻塞且支持结果返回进行回调。#并行

#这里callback为回调函数,是通过父线程调用的,并非是子线程,那有什么作用的?很简单,举个例子,假如该线程需要调用函数连接数据库的,父进程只需连一次,常连接就可以写了,但如果子进程调用的话,假如有一百个线程的话,我们就要起一百个连接了,呜呜,性能就在这无形间降低了。

map()

函数原型:

map(func, iterable[, chunksize=None])

Pool类中的map方法,与内置的map函数用法行为基本一致,它会使进程阻塞直到返回结果。 
注意,虽然第二个参数是一个迭代器,但在实际使用中,必须在整个队列都就绪后,程序才会运行子进程。

close()

关闭进程池(pool),使其不在接受新的任务。

terminate()

结束工作进程,不在处理未处理的任务。

注意:close()跟terminate()的区别在于close()会等待池中的worker进程执行结束再关闭pool,而terminate()则是直接关闭

join()

主进程阻塞等待子进程的退出,join方法必须在close或terminate之后使用。

 

创建一个进程池

from  multiprocessing import Process, Pool
import time

def Foo(i):
        time.sleep(2)
        print('hello')
        return i + 100

def Bar(arg):
        print('-->exec done:', arg)

if __name__=='__main__':
    pool = Pool(5)#允许进程池里同时放入进程池  同时执行的只有5个,其他的处于挂起状态
    for i in range(10):
        # pool.apply_async(func=Foo, args=(i,), callback=Bar) #并行  callback回调(是通过主进程调用的) ,执行完Foo后再执行Bar
        pool.apply_async(func=Foo, args=(i,))
        # pool.apply(func=Foo, args=(i,))#串行
    print('end')
    pool.close()
    pool.join()  # 进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。
View Code

 

posted @ 2017-03-07 19:50  似是故人来~  阅读(219)  评论(0编辑  收藏  举报