zoukankan      html  css  js  c++  java
  • Python并发编程-多进程

           Python并发编程-多进程

                           作者:尹正杰

    版权声明:原创作品,谢绝转载!否则将追究法律责任。

    一.多进程相关概念

      由于Python的GIL全局解释器锁存在,多线程未必是CPU密集型程序的好的选择。
    
      多进程可以完全独立的进程环境中运行程序,可以较充分地利用多处理器。
      但是进程本身的隔离带来的数据不共享也是一个问题。而且线程比进程轻量级。

    二.multiprocessing

     1 #!/usr/bin/env python
     2 #_*_conding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie
     5 
     6 import multiprocessing
     7 import datetime
     8 
     9 """
    10   Process类遵循了Thread类的API,减少了学习难度。
    11 
    12   相比线程的方法,进程多出来几个方法需要注意下:
    13     pid:
    14       进程id
    15     exitcode:
    16       进程的退出状态码
    17     terminate():
    18       终止指定的进程
    19 
    20     先看一个例子,前面介绍的单线程、多线程比较的例子的多进程版本,具体代码如下所示。
    21 """
    22 
    23 def calc(i):
    24     sum = 0
    25     for _ in range(1000000000):
    26         sum += 1
    27     return i,sum
    28 
    29 if __name__ == '__main__':
    30     start = datetime.datetime.now()
    31 
    32     ps = []
    33 
    34     for i in range(4):
    35         p = multiprocessing.Process(target=calc,args=(i,),name="calc-{}".format(i))
    36         ps.append(p)
    37         p.start()
    38 
    39     for p in ps:
    40         p.join()
    41         print(p.name,p.exitcode)
    42 
    43     delta = (datetime.datetime.now() - start).total_seconds()
    44     print(delta)
    45 
    46     for p in ps:
    47         print(p.name,p.exitcode)
    48 
    49     print("=== end ===")
    calc-0 0
    calc-1 0
    calc-2 0
    calc-3 0
    47.011848
    calc-0 0
    calc-1 0
    calc-2 0
    calc-3 0
    === end ===
    以上代码执行结果戳这里

    三.进程间同步

      Python在进程间同步提供了和线程同步一样的类,使用的方法一样,使用的效果也类似。
    
      不过,进程间代价要高于线程间,而且系统底层实现是不同的,只不过Python屏蔽了这些不同之处,让用户简单使用多进程。
      multiprocessing还提供共享内存、服务器进程来共享数据,还提供了用于进程间通讯的Queue队列,Pipe管道。
      通信方式不同     
    1.多进程就是启动多个解释器进程,进程间通信必须序列化、反序列化     2.数据的线程安全性问题
      如果每个进程中没有实现多线程,即每个进程仅有一个线程,此时GIL可以说没什么用了。

    四.进程池举例

    1>.multiprocessing.Pool 是进程池类

    multiprocessing.Pool 是进程池类,他有很多方法,具体如下:
      apply(self, func, args=(), kwds={}):
        阻塞执行,导致主进程执行其他子进程就像一个个执行
    
      apply_async(self, func, args
    =(), kwds={},callback=None, error_callback=None):     与apply方法用法一致,非阻塞异步执行,得到结果后会执行回调
      close():     关闭池,池不能再接受新的任务,所有任务完成后退出进程
      terminate():
        立即结束工作进程,不再处理未处理的任务
      
      join():     主进程阻塞等待子进程的退出, join方法要在close或terminate之后使用

    2>.同步调用

     1 #!/usr/bin/env python
     2 #_*_conding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie
     5 
     6 import logging
     7 import datetime
     8 import multiprocessing
     9 # 日志打印进程id、进程名、线程id、线程名
    10 logging.basicConfig(level=logging.INFO, format="%(process)d %(processName)s %(thread)d %(message)s")
    11 def calc(i):
    12     sum = 0
    13     for _ in range(1000000000): # 10亿
    14         sum += 1
    15     logging.info(sum)
    16     return i,sum  # 进程要return,才可以拿到这个结果
    17 
    18 
    19 if __name__ == '__main__': # 注意一定要有这一句
    20     start = datetime.datetime.now()
    21 
    22     pool = multiprocessing.Pool(4)
    23 
    24     for i in range(4):
    25         # 返回值,同步调用,注意观察CPU使用
    26         ret = pool.apply(calc, args=(i,))
    27         print(ret)
    28     pool.close()
    29     pool.join()
    30 
    31     delta = (datetime.datetime.now() - start).total_seconds()
    32     print(delta)
    33     print('===end====')
    calc-0 0
    calc-1 0
    calc-2 0
    calc-3 0
    50.399442
    calc-0 0
    calc-1 0
    calc-2 0
    calc-3 0
    === end ===
    以上代码执行结果戳这里

    3>.异步调用

     1 #!/usr/bin/env python
     2 #_*_conding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie
     5 
     6 import logging
     7 import datetime
     8 import multiprocessing
     9 # 日志打印进程id、进程名、线程id、线程名
    10 logging.basicConfig(level=logging.INFO, format="%(process)d %(processName)s %(thread)d %(message)s")
    11 
    12 def calc(i):
    13     sum = 0
    14     for _ in range(1000000000): # 10亿
    15         sum += 1
    16     logging.info(sum)
    17     return i, sum # 进程要return,callback才可以拿到这个结果
    18 
    19 if __name__ == '__main__':
    20     start = datetime.datetime.now() # 注意一定要有这一句
    21     pool = multiprocessing.Pool(4)
    22     for i in range(4):
    23         # 异步拿到的返回值是什么?
    24         ret = pool.apply_async(calc, args=(i,))
    25         print(ret, '~~~~~~~') # 异步,如何拿到真正的结果呢?
    26     pool.close()
    27     pool.join()
    28     delta = (datetime.datetime.now() - start).total_seconds()
    29     print(delta)
    30     print('===end====')
    <multiprocessing.pool.ApplyResult object at 0x000001F02D3230C8> ~~~~~~~
    <multiprocessing.pool.ApplyResult object at 0x000001F02D3231C8> ~~~~~~~
    <multiprocessing.pool.ApplyResult object at 0x000001F02D323308> ~~~~~~~
    <multiprocessing.pool.ApplyResult object at 0x000001F02D3233C8> ~~~~~~~
    5828 SpawnPoolWorker-4 7608 1000000000
    7248 SpawnPoolWorker-3 5168 1000000000
    2020 SpawnPoolWorker-1 2252 1000000000
    1620 SpawnPoolWorker-2 3128 1000000000
    47.058129
    ===end====
    以上代码执行结果

    4>.异步调用,拿最终结果

     1 #!/usr/bin/env python
     2 #_*_conding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie
     5 
     6 import logging
     7 import datetime
     8 import multiprocessing
     9 
    10 # 日志打印进程id、进程名、线程id、线程名
    11 logging.basicConfig(level=logging.INFO, format="%(process)d %(processName)s %(thread)d %(message)s")
    12 
    13 
    14 def calc(i):
    15     sum = 0
    16     for _ in range(1000000000): # 10亿
    17         sum += 1
    18     logging.info(sum)
    19     return i, sum # 进程要return,callback才可以拿到这个结果
    20 
    21 if __name__ == '__main__':
    22     start = datetime.datetime.now() # 注意一定要有这一句
    23 
    24     pool = multiprocessing.Pool(4)
    25 
    26     for i in range(4):
    27     #异步拿到的返回值是什么?回调起了什么作用?
    28         ret = pool.apply_async(calc, args=(i,),callback=lambda ret: logging.info('{} in callback'.format(ret)))
    29         print(ret, '~~~~~~~')
    30 
    31     pool.close()
    32     pool.join()
    33     delta = (datetime.datetime.now() - start).total_seconds()
    34     print(delta)
    35     print('===end====')
    <multiprocessing.pool.ApplyResult object at 0x0000017D9709F308> ~~~~~~~
    <multiprocessing.pool.ApplyResult object at 0x0000017D9709F448> ~~~~~~~
    <multiprocessing.pool.ApplyResult object at 0x0000017D9709F508> ~~~~~~~
    <multiprocessing.pool.ApplyResult object at 0x0000017D9709F5C8> ~~~~~~~
    8964 SpawnPoolWorker-1 1620 1000000000
    572 MainProcess 8184 (0, 1000000000) in callback
    992 SpawnPoolWorker-2 2484 1000000000
    572 MainProcess 8184 (1, 1000000000) in callback
    10608 SpawnPoolWorker-3 2404 1000000000
    572 MainProcess 8184 (2, 1000000000) in callback
    3268 SpawnPoolWorker-4 8684 1000000000
    572 MainProcess 8184 (3, 1000000000) in callback
    44.991332
    ===end====
    以上代码执行结果戳这里

    五.多进程、多线程的选择

    1>.CPU密集型

      CPython中使用到了GIL,多线程的时候锁相互竞争,且多核优势不能发挥,选用Python多进程效率更高。

    2>.IO密集型

      在Python中适合是用多线程,可以减少多进程间IO的序列化开销。且在IO等待的时候,切换到其他线程继续执行,效率不错。

    3>.应用

    请求/应答模型:WEB应用中常见的处理模型
      master启动多个worker工作进程,一般和CPU数目相同。发挥多核优势。
      worker工作进程中,往往需要操作网络IO和磁盘IO,启动多线程,提高并发处理能力。worker处理用户的请求,往往需要等待数据,处理完请求还要通过网络IO返回响应。
      这就是nginx工作模式。

    六.Linux的特殊进程(在Linux/Unix中,通过父进程创建子进程。)

    1>.僵尸进程

      一个进程使用了fork创建了子进程,如果子进程终止进入僵死状态,而父进程并没有调用wait或者waitpid获取子进程的状态信息,那么子进程仍留下一个数据结构保存在系统中,这种进程称为僵尸进程。
    
      僵尸进程会占用一定的内存空间,还占用了进程号,所以一定要避免大量的僵尸进程产生。有很多方法可以避免僵尸进程。

    2>.孤儿进程

      父进程退出,而它的子进程仍在运行,那么这些子进程就会成为孤儿进程。孤儿进程会被init进程(进程号为1)收养,并由init进程对它们完成状态收集工作。
    
      init进程会循环调用wait这些孤儿进程,所以,孤儿进程没有什么危害。

    3>.守护进程

      它是运行在后台的一种特殊进程。它独立于控制终端并周期性执行某种任务或等待处理某些事件。
    
      守护进程的父进程是init进程,因为其父进程已经故意被终止掉了。
      守护进程相对于普通的孤儿进程需要做一些特殊处理。
  • 相关阅读:
    微信内置浏览器的JsAPI(WeixinJSBridge)
    跨域请求 jQuery的ajax jsonp的使用
    Window10设置技巧
    Oracle命令集
    通过sql server 连接mysql
    深入 AngularUI Router
    解决Web部署 svg/woff/woff2字体 404错误
    C#四舍五入、上取整、下取整
    SQL语句:两个时间区间段,只要有交集,就能筛选出来
    url传参中文乱码问题
  • 原文地址:https://www.cnblogs.com/yinzhengjie/p/11894972.html
Copyright © 2011-2022 走看看