• 多进程编程


    多进程

      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
  • 相关阅读:
    21.错误和异常
    20.装饰器相关
    19.装饰器
    18.函数编程的练习
    Remove Duplicates from Sorted List II
    Ubuntu 12.04输入密码登陆后又跳回到登录界面
    Remove Linked List Elements
    Populating Next Right Pointers in Each Node *
    Convert Sorted Array to Binary Search Tree
    Flatten Binary Tree to Linked List *
  • 原文地址:https://www.cnblogs.com/freely/p/6511621.html
Copyright © 2020-2023  润新知