一、进程

python开发【第二十三篇】我们已经详细的讲解了线程。进程的用法和线程的用法基本一致。

multiprocessing是python的多进程管理包,和threading.Thread类似。它可以利用multiprocessing.Process对象来创建一个进程。该进程可以运行在Python程序内部编写的函数。该Process对象与Thread对象的用法相同,也有start(), run(), join()的方法。此外multiprocessing包中也有Lock/Event/Semaphore/Condition类 (这些对象可以像多线程那样,通过参数传递给各个进程),用以同步进程,其用法与threading包中的同名类一致。所以,multiprocessing的很大一部份与threading使用同一套API,只不过换到了多进程的情境。

注意:

  • 在UNIX平台上,当某个进程终结之后,该进程需要被其父进程调用wait,否则进程成为僵尸进程(Zombie)。所以,有必要对每个Process对象调用join()方法 (实际上等同于wait)。对于多线程来说,由于只有一个进程,所以不存在此必要性。原因很简单:想想线程,我们知道可能主线程结束了,但是子线程还没有结束,它们之间谁先结束并没有多大的影响。同样在进程中也有可能父进程结束了,子进程还没有结束,但是因为进程终结,还要依靠父进程的来kill,所以失去父进程的子进程就成了僵尸进程,然后就会无端占用资源。
  • multiprocessing提供了threading包中没有的IPC(比如Pipe和Queue),效率上更高。应优先考虑Pipe和Queue,避免使用Lock/Event/Semaphore/Condition等同步方式(因为它们占据的不是用户进程的资源)。

  • 多进程应该避免共享资源。在多线程中,我们可以比较容易地共享资源,比如使用全局变量或者传递参数。在多进程情况下,由于每个进程有自己独立的内存空间,以上方法并不合适此时我们可以通过共享内存和Manager的方法来共享资源。但这样做提高了程序的复杂度,并因为同步的需要而降低了程序的效率。

生成进程:

#!/usr/bin/env python
# _*_ coding:utf-8 _*_
__author__ = 'liujianzuo'
 
from multiprocessing import Process
 
def f(name):
    print("hello",name)
 
 
if __name__ == "__main__":  #记住在windows下一定要写这句,具体原因现不得而知,不然会报错。在linux中不用
    p = Process(target=f, args=("alex",))
    print(123)
    p.start()
    p.join() #这句必须有,避免出现僵尸进程 。

获取进程id:

from multiprocessing import Process
import os
 
def info(title):
    print(title)
    print('module name:', __name__)  #获取进程名
    print('parent process:', os.getppid())  #生成父进程id
    print('process id:', os.getpid())  #获取当前进程id
    print("\n\n")
 
def f(name):
    info('\033[31;1mfunction f\033[0m')
    print('hello', name)
 
if __name__ == '__main__':
    info('\033[32;1mmain process line\033[0m')
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()

"""
执行结果:
main process line
module name: __main__  
parent process: 4760   
process id: 6308  
function f
module name: __mp_main__
parent process: 6308
process id: 6900
hello bob
"""

从执行结果可以看出:1.主进程也有父进程,因为使用的是pycharm执行的语句,所以其父进程是pycharm程序。

           2.我们手动生成的进程,其父进程是主进程

后台进程(可以视为父进程的守护进程):

主进程结束,子进程马上也结束

#!/usr/bin/env python
# _*_ coding:utf-8 _*_
__author__ = 'liujianzuo'

from multiprocessing import Process
import time

def f(name):
    time.sleep(2)
    print("hello", name)

if __name__ == "__main__":  # 记住在windows下一定要写这句,具体原因现不得而知,不然会报错。在linux中不用
    for i in range(3):
        p = Process(target=f, args=("alex",))
        p.daemon=True  #设置进程为后台进程,主进程结束,子进程立即断掉  。功能同效:线程的setDaemon
        p.start()
print("end")
View Code
执行结果:
>>> end
执行结果

补充:什么叫守护进程

守护进程就是不受终端(如:shell、xwindows等等)控制的进程,启动的方式可以随系统一起启动,也可以后期自己启动。创建守护进程的目的就是为了让某些进程不间断的完成某些特殊任务,除非系统宕了,才终止任务。详解:linux测试用的全能型命令的第4

其实守护进程就是后台进程。只是守护进程时init或者systemd启起来的后台进程,而我们这儿的后台进程时一个用户进程启动起来的,他将随着用户进程同生同灭

二、进程间的通信

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

1.Queue(队列)

使用方法跟threading里的queue差不多

from multiprocessing import Process, Queue
import threading

def f(qq,i):
    print("in child:",qq.qsize())
    qq.put([42, None, 'hello']) #子进程传一个参数

if __name__ == '__main__':
    q = Queue()
    q.put("test123")  #从父进程传一个参数进队列
    p = Process(target=f, args=(q,i,)) #必须将队列作为参数传进子进程
    p.start()
    p.join()
    print("444",q.get_nowait())
    print("444",q.get_nowait())

2.pipe(管道)

管道,就好像一个进程连接管道这头,另一个进程连接管道的那一头,这样他们就能通信了

from multiprocessing import Process, Pipe

def f(conn):
    conn.send([42, None, 'hello from child'])
    conn.send([42, None, 'hello from child2'])
    print("from parent:",conn.recv())
    conn.close()

if __name__ == '__main__':
    parent_conn, child_conn = Pipe()
    p = Process(target=f, args=(child_conn,))
    p.start()
    print(parent_conn.recv())  # prints "[42, None, 'hello']"
    print(parent_conn.recv())  # prints "[42, None, 'hello']"
    parent_conn.send("张洋可好") #向子进程推送数据
    p.join()

3.manager

queue 和pipe 都还只是数据的传递,从某个进程传到某个进程中,manager是真正的实现了数据的共享。所有进程都可以往里面放数据,然后去取数据 。

manager 可共享的数据有:字典、列表、锁、等等

from multiprocessing import Process, Manager
import os
def f(d, l):
    d[os.getpid()] =os.getpid()
    l.append(os.getpid())
    print(l)

if __name__ == '__main__':
    with Manager() as manager:  #生成一个manager()对象
        d = manager.dict() #{} #生成一个字典,可在多个进程间共享和传递

        l = manager.list(range(3))#生成一个列表,可在多个进程间共享和传递
        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)

4.lock

多进程LOCK,如果不锁 屏幕抢占输出

from multiprocessing import Process, Lock

def f(l, i):
    l.acquire()
    print('hello world', i)
    l.release()

if __name__ == '__main__':
    lock = Lock()

    for num in range(10):
        Process(target=f, args=(lock, num)).start()

5.进程池

进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进程,那么程序就会等待,直到进程池中有可用进程为止。

进程池中有两个方法:

  • apply
  • apply_async

  实际应用中,并不是每次执行任务的时候,都去创建多进程,而是维护了一个进程池,每次执行的时候,都去进程池取一个,如果进程池里面的进程取光了,就会阻塞在那里,直到进程池中有可用进程为止。首先来看一下进程池提供了哪些方法

  • apply(func[, args[, kwds]]) :使用arg和kwds参数调用func函数,结果返回前会一直阻塞,由于这个原因,apply_async()更适合并发执行,另外,func函数仅被pool中的一个进程运行。

  • apply_async(func[, args[, kwds[, callback[, error_callback]]]]) : apply()方法的一个变体,会返回一个结果对象。如果callback被指定,那么callback可以接收一个参数然后被调用,当结果准备好回调时会调用callback,调用失败时,则用error_callback替换callback。 Callbacks应被立即完成,否则处理结果的线程会被阻塞。

  • close() : 等待任务完成后在停止工作进程,阻止更多的任务提交到pool,待任务完成后,工作进程会退出。

  • terminate() : 不管任务是否完成,立即停止工作进程。在对pool对象进程垃圾回收的时候,会立即调用terminate()。

  • join() : 等待工作线程的退出,在调用join()前,必须调用close() or terminate()。这样是因为被终止的进程需要被父进程调用wait(join等价与wait,否则进程会成为僵尸进程。

下面来简单的看一下代码怎么用的

from  multiprocessing import Process, Pool
import time
import os

def Foo(i):
    time.sleep(2)
    print("in process",os.getpid())
    return i + 100

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

if __name__ == '__main__':
    pool = Pool(processes=3) #允许进程池同时放入3个进程
    print("主进程",os.getpid())
    for i in range(10):
        pool.apply_async(func=Foo, args=(i,), callback=Bar) #callback=回调  。回调的意思是:当前进程执行的方法执行完了,然后再回去执行回调的方法
        #pool.apply(func=Foo, args=(i,)) #串行
    print('end')
    pool.close() #注意pool.close() 必须写在pool.join()前
    pool.join() #进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。

执行结果:

主进程 7204
end
in process 9040
-->exec done: 100 7204
in process 8496
-->exec done: 101 7204
in process 788
-->exec done: 102 7204

in process 9040 -->exec done: 103 7204 in process 8496 -->exec done: 104 7204 in process 788
-->exec done: 105 7204
in process 9040 -->exec done: 106 7204 in process 8496 -->exec done: 107 7204 in process 788 -->exec done: 108 7204

in process 9040 -->exec done: 109 7204

三、多进程

为什么多进程可以解决GIL限制问题

# Processing pool (see below for initiazation)
pool = None

# Performs a large calculation (CPU bound)
def some_work(args):
    ...
    return result

# A thread that calls the above function
def some_thread():
    while True:
        ...
        r = pool.apply(some_work, (args))
        ...

# Initiaze the pool
if __name__ == '__main__':
    import multiprocessing
    pool = multiprocessing.Pool()

这个通过使用一个技巧利用进程池解决了GIL的问题。 当一个线程想要执行CPU密集型工作时,会将任务发给进程池。 然后进程池会在另外一个进程中启动一个单独的Python解释器来工作。 当线程等待结果的时候会释放GIL。 并且,由于计算任务在单独解释器中执行,那么就不会受限于GIL了。 在一个多核系统上面,你会发现这个技术可以让你很好的利用多CPU的优势。

 

posted on 2017-06-22 16:37  进_进  阅读(126)  评论(0)    收藏  举报