• 多进程学习总结


       Python 解释器有一个全局解释器锁(PIL),导致每个 Python 进程中最多同时运行一个线程,因此 Python 多线程程序并不能改善程序性能,不能发挥多核系统的优势,可以通过这篇文章了解。但是多进程程序不受此影响, Python 2.6 引入了 multiprocessing 来解决这个问题。这里介绍 multiprocessing 模块下的进程,进程同步,进程间通信和进程管理四个方面的内容。 这里主要讲解多进程的典型使用,multiprocessing 的 API 几乎是完复制了 threading 的API, 因此只需花少量的时间就可以熟悉 threading 编程了。

    一 起多进程

    from multiprocessing import Process, current_process
    import time
    
    
    def func(i):
        time.sleep(2)
        proc = current_process()
        print("proce.name : ", proc.name)  # 输出进程名
        print("proc.pid :", proc.pid)  # 输出进程ID
    
    if __name__ == "__main__":
        for i in range(10):
            p = Process(target=func, args=(i,))
            p.start()
            # p.join()
    
            # p.join() 加上这一句之后,就是要等这个进程执行完之后才能进入下一个循环,也就是才能执行下一个进程,这样就失去了多进程的意义

    二 进程中再起线程

    from multiprocessing import Process, current_process
    import time
    import threading
    
    
    def foo():
        print("threading id is ", threading.get_ident())  # 获取当前线和的ID
    
    
    def func(i):
        time.sleep(2)
        proc = current_process()
        print("proce.name : ", proc.name)  # 输出进程名
        print("proc.pid :", proc.pid)  # 输出进程ID
        p = threading.Thread(target=foo, )  # 进程中再起线程 
        p.start()
    
    
    if __name__ == "__main__":
        for i in range(10):
            p = Process(target=func, args=(i,))
            p.start()

    三  利用os模块来查看各个进程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("
    
    ")
    
    
    def f(name):
        info('called from child process function f ') 
        print('hello', name)
    
    if __name__ == '__main__':
        info('main process line')
        p = Process(target=f, args=('bob',))
        p.start()
        # p.join()

    结果如下: 可以看到,主进程的id就是子进程的父ID

    四 进程间的数据传递---queue

    from multiprocessing import Process, Queue
    import threading
    
    
    def f(q):
        q.put([42, None, 'hello'])  # 子进程中put 一个值进入Queue
    
    
    if __name__ == '__main__':
        q = Queue()
        q.put("test123")  # 主进程中put 一个值进入Queue
        p = Process(target=f, args=(q,))
        p.start()
        p.join()
        print("444", q.get_nowait())
        print("444", q.get_nowait())

    可以看到,在这个Queue中取出两个值,所以在子进程中给这个Queue传递的值,在主进程中也可以取出来.

      和多线程之间数据传递(共享比较)

    import threading
    import queue
    
    def f():
        q.put([42, None, 'hello'])
    
    if __name__ == '__main__':
        q = queue.Queue()
        q.put("test123")
        p = threading.Thread(target=f, )
        p.start()
        print("444", q.get_nowait())
        print("444", q.get_nowait())

    结果是:

    和进程数据传递比较:这里不用传递这个queue,因为线程之间数据是共享的,在多进程中,如果不传递,则在子进程中就会报未定义的错误,

    不能把线程queue作为参数传给子进程

    from multiprocessing import Process, Queue
    import threading
    import queue
    
    def f(q):
        q.put([42, None, 'hello']) 
    
    
    if __name__ == '__main__':
        q =  queue.Queue()
        q.put("test123")  
        p = Process(target=f,args=(q,))
        p.start()
        print("444", q.get_nowait())
        print("444", q.get_nowait())

    要想在进程之间传递数据,只能是进程queue,不能把线程queue作为参数传给子进程.

    五 进程间的数据传递---Pipe

    from multiprocessing import Process, Pipe
    
    
    def f(conn):
        conn.send([42, None, 'hello from child'])
        print("",conn.recv()) # prints "from main"
        conn.close()
    
    if __name__ == '__main__':
        parent_conn, child_conn = Pipe() #产生两个返回对象,分别代表两头
        p = Process(target=f, args=(child_conn,))
        p.start()
        print("parent",parent_conn.recv())  # prints "[42, None, 'hello from child']"
        parent_conn.send("from main")  
        p.join()

    生成一个Pipe 对象后就自动生成两个返回对象,可以理解成两头,

    也有两个方法 send() 发送数据 recv()接收数据.

    六 进程间的数据传递---manager

    from multiprocessing import Process, Manager
    import os
    
    def f(dct, lst):
        d[os.getpid()] = os.getppid()
        l.append(os.getpid())
        # print(l)
    
    
    if __name__ == '__main__':
        with Manager() as manager:
            dct = manager.dict()  # {} #生成一个manager的字典(不是平常的字典),可在多个进程间共享和传递.
            lst = manager.list(range(5))  # 生成一个列表,可在多个进程间共享和传递
            p_list = []
            for i in range(10):
                p = Process(target=f, args=(dct, lst))
                p.start()
                p_list.append(p)
            for res in p_list:  # 等待所有进程执行完闭结果,这里如如不写就会报错,说系统找不到指定文件
                res.join()
            print(dct)
            print(lst)

    结果如下:

    七 进程锁

    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):
            p=Process(target=f, args=(lock, num))
            p.start()
            # p.join() # 加上这个之后就要当前进程执行完之后才执行下一下进程

    结果如下:

    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):
            p=Process(target=f, args=(lock, num))
            p.start()
            p.join() # 加上这个之后就要当前进程执行完之后才执行下一下进程

    结果如下

       可以看到,打印的循序是固定的,而且在执行行明显知道,他是上一个进程执行完之后才执行的下一个进程.

       按道理说,各进程之间数据是独立的,各进程之个对同一份内存数据是不共享的,(上面写的那几种数据共享都不是对同一份内存数据共享,只是复制了之后再共享的),不应该加锁啊,但这里各个进程是共享屏幕的,加锁的目的主要是为了防止屏幕输入输出数据出错.在python3中,进程锁的意义不大.

    八 进程池

    from multiprocessing import Process, Pool, freeze_support
    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__':  # 在windows 中必须写在这个里面,不然出错
        # freeze_support()
        pool = Pool(processes=5)  # 允许进程池同时放入5个进程
        print("主进程", os.getpid())
        for i in range(10):
            pool.apply_async(func=Foo, args=(i,), callback=Bar)  # callback=回调函数,是指这个进程执行完之后要执行的函数
            # callback 函数是主进程调用的
            # pool.apply(func=Foo, args=(i,)) #串行
            # pool.apply_async(func=Foo, args=(i,)) #串行
        print('end')
        pool.close()  # 先close() 再join(),
        pool.join()  # 进程池中进程执行完毕后再关闭,如果注释的话,当主程度执行完之后程序就关闭,而不会等各子进程执行完
        # 进程池的另一种用法,这种方法不能调用callback函数
        groups = [x * 20 for x in range(10)]
        pool = Pool(processes=5)
        pool.map(Foo, groups)

        apply()是串行,也就是说只有等一个进程执行完才执行下一个进程,apply_async() 是并行,会按进程池中的个数一起执行,进程池的方法很多,用些方法不常用. map_async() 这是一个异常的map方法,可以调用callback()函数,用法和map一样,只是是异步执行.

  • 相关阅读:
    条件语句实例
    数据类型
    C#与.NET概述
    c#循环
    语句
    数组

    英文文献中的数学符号
    如何计算协方差、 协方差矩阵 、 相关系数 、 马氏距离
    opengl 笔记
  • 原文地址:https://www.cnblogs.com/liuqianli/p/8400737.html
Copyright © 2020-2023  润新知