• Python之多线程与多进程(二)


    多进程

    上一章:Python多线程与多进程(一)

     由于GIL的存在,Python的多线程并没有实现真正的并行。因此,一些问题使用threading模块并不能解决

    不过Python为并行提供了一个替代方法:多进程。在多进程里,线程被换成一个个子进程。每个进程都运作着各自的GIL(这样Python就可以并行开启多个进程,没有数量限制)。需要明确的是,线程都是同一个进程的组成部分,它们共享同一块内存、存储空间和计算资源。而进程却不会与它们的父进程共享内存,因此进程间通信比线程间通信更为复杂

    多进程相比多线程优缺点如下:

    优点 缺点
    可以使用多核操作系统 更多的内存消耗
    进程使用独立的内存空间,避免竞态问题 进程间的数据共享变得更加困难
    子进程容易中断 进程间通信比线程困难
    避开GIL限制  

      

    Python多进程

    multiprocessing模块提供了一个Process类,它有点类似多线程模块中threading.Thread类。因此,把多线程代码迁移到多进程还是比较简单的,因为代码的基本结构不变

    我们快速演示一个多进程的示例:

    import multiprocessing
    
    
    def run(pname):
        print(pname)
    
    
    for i in range(3):
        p = multiprocessing.Process(target=run, args=("Process-%s" % i,))
        p.start()
        p.join()
    

        

    运行结果:

    Process-0
    Process-1
    Process-2
    

      

    可以看出,多进程和多线程的代码非常像,这里需要注意一点,如果是在Windows上执行的需要把启动进程的代码放到if __name__ == "__main__":底下

    if __name__ == "__main__":
        for i in range(3):
            p = multiprocessing.Process(target=run, args=("Process-%s" % i,))
            p.start()
            p.join()
    

      

    进程退出状态:当进程结束的时候,会产生一个状态码,它是一个数字,表示执行结果,不同数字代表程序运行的不同情况:

    • 等于0表示正常完结
    • 大于0表示异常完结
    • 小于0表示进程被另一个进程通过-1*exit_code信号终结

    下面的代码演示如何读取和使用退出码

    import multiprocessing
    import time
    
    
    def first():
        print("There is no problem here")
    
    
    def second():
        raise RuntimeError("Error raised!")
    
    
    def third():
        time.sleep(3)
        print("This process will be terminated")
    
    
    workers = [multiprocessing.Process(target=first), multiprocessing.Process(target=second),
               multiprocessing.Process(target=third)]
    for w in workers:
        w.start()
    
    workers[-1].terminate()
    
    for w in workers:
        w.join()
    
    for w in workers:
        print(w.exitcode)
    

      

    运行结果:

    There is no problem here
    Process Process-2:
    Traceback (most recent call last):
      File "/home/lf/anconda3/lib/python3.6/multiprocessing/process.py", line 258, in _bootstrap
        self.run()
      File "/home/lf/anconda3/lib/python3.6/multiprocessing/process.py", line 93, in run
        self._target(*self._args, **self._kwargs)
      File "test01.py", line 11, in second
        raise RuntimeError("Error raised!")
    RuntimeError: Error raised!
    0
    1
    -15
    

      

    我们注意到,第三个子进程的print语句没有执行,这是因为在sleep方法结束之前进程已经被中止了。还有一点需要注意的是:两个独立的for循环处理三个子进程:一个启动子进程,另一个通过join方法连接进程。如果我们在开启每个子进程时都执行join方法,而不是没有join直接中断第三个进程,那么第三个进程就不会失败。于是第三个子进程返回的退出码也是0,因为和多线程一样,join方法在目标进程完结之前会阻塞子进程的调用  

    进程池

    多进程模块还提供了pool类,表示一个进程池,里面装有子进程,可以通过不同的方法执行同一组任务。

    Pool类的主要方法如下:

    • apply:这个方法在独立的子进程中运行一个函数。它还会在被调用函数返回结果之前阻塞进程
    • apply_async:这个方法会在独立子进程中异步地运行一个函数,就是说进程会立即返回一个ApplyResult对象,要获得真实的返回值需要使用get()方法。get()在异步执行的函数结束之前都会被阻塞
    • map:这个方法对一组数值应用一个函数。它是一个阻塞动作,所以返回值是每个值经过函数映射的列表

    进程间通信:进程间通信的方式不像线程间通信那么简单,但是,Python提供了一些工具帮助我们解决问题。

    Queue类是一个既线程安全又进程安全的先进先出(FIFO)数据交换机制。multiprocessing提供的Queue类基本是Queue.Queue的克隆版本,因此二者API基本相同

    from multiprocessing import Queue, Process
    import random
    
    
    def generate(q):
        while True:
            value = random.randrange(10)
            q.put(value)
            print("Value added to queue: %s" % (value))
    
    
    def reader(q):
        while True:
            value = q.get()
            print("Value from queue: %s" % (value))
    
    
    queue = Queue()
    p1 = Process(target=generate, args=(queue,))
    p2 = Process(target=reader, args=(queue,))
    p1.start()
    p2.start()
    

      

    Pipe方法:Pipe(管道)方法为两个进程提供了一种双向通信的机制,Piped()函数返回一对连接对象,每个对象表示管道的一端。每个连接对象都有send()和recv()方法

    from multiprocessing import Pipe, Process
    import random
    
    
    def generate(pipe):
        while True:
            value = random.randrange(10)
            pipe.send(value)
            print("Value sent: %s" % (value))
    
    
    def reader(pipe):
        f = open("output.txt", "w")
        while True:
            value = pipe.recv()
            f.write(str(value))
            print(".")
    
    
    input_p, output_p = Pipe()
    p1 = Process(target=generate, args=(input_p,))
    p2 = Process(target=reader, args=(output_p,))
    p1.start()
    p2.start()
    

      

    多进程也有事件Event,它们的工作方式与多线程类似,只是有一点需要记住,事件对象不能被传递到子进程的函数中,这样做会导致运行时错误,信号机制只能在主进程中被子进程共享:

    from multiprocessing import Pool, Event
    import time
    
    event = Event()
    event.set()
    
    
    def worker(i):
        if event.is_set():
            time.sleep(0.1)
            print("A - %s" % (time.time()))
            event.clear()
        else:
            time.sleep(0.1)
            print("B - %s" % (time.time()))
            event.set()
    
    
    pool = Pool(3)
    pool.map(worker, range(9))
    

      

  • 相关阅读:
    pandas模块篇(终章)及初识mataplotlib
    pandas模块篇(之三)
    pandas模块篇(之二)
    numpy最后一部分及pandas初识
    anaconda及jupyter notebook的使用之numpy模块的用法(2)
    anaconda及jupyter notebook的了解及使用方法(1)
    python初略复习(2)及python相关数据分析模块的介绍
    Python回顾笔记(此讲大致说明,详情请看之前的笔记)
    Python第三讲
    折半算法
  • 原文地址:https://www.cnblogs.com/beiluowuzheng/p/9280182.html
Copyright © 2020-2023  润新知