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进程,因为其父进程已经故意被终止掉了。
  守护进程相对于普通的孤儿进程需要做一些特殊处理。

 

posted @ 2019-11-20 05:31  尹正杰  阅读(316)  评论(0编辑  收藏  举报