Python-多进程

1、多进程:

  由于Python的GIL,多线程未必是CPU密集型程序的最好选择。

  多进程可以完全独立的进程环境中运行程序,可以充分的利用多处理器。

  但是进程本身的隔离带来的数据不共享也是一个问题,而且线程比进程轻量级。

2、multiprocessing:

  Process类遵循了Thread类的API 

  测试:开启三个进程,计算一个CPU密集型程序。

 1 import multiprocessing
 2 import datetime
 3 
 4 #   次函数是一个CPU 密集型,所以,多线程和单线程 分别计算5 次, 计算是一样的效果,至少4分钟左右
 5 def calc(i):
 6     sum = 0
 7     for _ in range(100000000):
 8         sum += 1
 9     print(i, sum)
10 
11 if __name__ == '__main__':
12     start = datetime.datetime.now()
13 
14     ps = []
15     for i in range(3):# 启动三个进程。
16         p = multiprocessing.Process(target=calc, args=(i,), name='cac={}'.format(i))
17         ps.append(p)
18         p.start() # 分别启动三个进程。
19     for p in ps:
20         p.join() # 当前主进程 的主线程等待所有的子进程结束后,再退出。
21 
22     delta = (datetime.datetime.now() - start).total_seconds()
23     print(delta)
24     print('end---')
测试:多进程

 

1 1 100000000
2 0 100000000
3 2 100000000
4 10.221419
5 end---

 

  测试:同时CPU 密集型 ,单线程,执行3次:

 1 import multiprocessing
 2 import datetime
 3 import logging
 4 
 5 
 6 logging.basicConfig(level=logging.INFO, format='%(thread)s %(message)s')
 7 start = datetime.datetime.now()
 8 def calc(i):
 9     sum = 0
10     for _ in range(100000000):
11         sum += 1
12     print(i, sum)
13 
14 calc(0)
15 calc(1)
16 calc(2)
17 
18 delta = (datetime.datetime.now() - start).total_seconds()
19 logging.info(delta)
测试:单线程执行三次
1 0 100000000
2 1 100000000
3 6088 23.520346
4 2 100000000

  测试:同上CPU 密集型,多线程,执行;

 1 import multiprocessing
 2 import datetime
 3 import logging
 4 import threading
 5 #   次函数是一个CPU 密集型,所以,多线程和单线程 分别计算5 次, 计算是一样的效果,至少4分钟左右
 6 
 7 logging.basicConfig(level=logging.INFO, format='%(thread)s %(message)s')
 8 start = datetime.datetime.now()
 9 def calc(i):
10     sum = 0
11     for _ in range(100000000):
12         sum += 1
13     print(i, sum)
14 
15 t1 = threading.Thread(target=calc, args=(1,))
16 t2 = threading.Thread(target=calc, args=(2,))
17 t3 = threading.Thread(target=calc, args=(3,))
18 
19 t1.start()
20 t2.start()
21 t3.start()
22 t1.join()
23 t2.join()
24 t3.join()
25 
26 
27 delta = (datetime.datetime.now() - start).total_seconds()
28 logging.info(delta)
测试:多线程开启三个子线程
2 100000000
3 100000000
9908 22.107265
1 100000000

 

  总结:

    1、可以看到:CPU密集型,多线程和单线程执行的时间是差不多的,也就是说,由于GIL(全局解释器锁,进程锁)的存在,导致,即便是多线程,也是串行执行

    2、可以看到,多进程执行,时间要很短,而且充分利用CPU资源,每个进程调度到一个CPU上,但是并没有绑定。但是如果绑定的话,就分别在自己的绑定的CPU上运行,此外CPU绑定,是为了利用cpu缓存。

    3、注意的一点,多进程代码一定要在 __name__ == '__main__' 中执行。

                                  线程是没有如下属性或方法:

名称 说明
pid 进程ID
exitcode 进程的退出状态
terminate() 终止指定的进程

3、进程间同步:(不怎么用,不过注意,方法,类相同,但是来自不同的模块下,线程是线程的,进程是进程的)

  Python在进程间同步提供了和线程同步一样的类,使用的方法一样,使用的效果也类似。

  不过,进程间代价要高于线程间,而且 底层实现是不同的,只不过Python屏蔽了这些不同之处,让用户简单使用多进程。

  multiprocessing 还提供共享内存,服务器进程来共享数据,还提供了用于 进程间通讯的Queue队列,Pipe管道

  通信方式不同:

    1、多进程就是启动多个解释器进程,进程间通信必须序列化,反序列化

    2、数据的线程安全性问题

      如果 每个进程中没有实现多线程,GIL 可以说没什么用了

  注:

    queue模块是给线程用的,但是进程的Queue是multiprocessing提供的,一般不用,是本机使用

    进程和进程之间通信:使用第三方Queue,如:kfakfa,而且基本都是网络通信(进程间通讯)

    多进程,就是多解释器进程

    进程间通讯:一般都要序列话,反序列化。

    进程间通讯的锁:分布式锁, zookeper

4、进程池举例:

  multiprocessing.Pool 是进程池类

 

 

名称 说明
apply(self,func,args=(),kwds={}) 阻塞执行,导致主进程执行其他子进程就像一个执行

apply_async(self,func, args=(),kwds={},

callback=None, error_callback=None)

与apply方法用法一致,非阻塞异步执行,得到结果后执行回调
cose() 关闭池,池不能再接受新的任务
terminate() 结束工作进程,不再处理未处理的任务
join() 主进程阻塞等待子进程的退出,join方法要在close或terminate之后使用

  测试:进程池的使用 apply_async

 1 import multiprocessing
 2 import datetime
 3 import logging
 4 import threading
 5 #   次函数是一个CPU 密集型,所以,多线程和单线程 分别计算5 次, 计算是一样的效果,至少4分钟左右
 6 start = datetime.datetime.now()
 7 
 8 def calc(i):
 9     sum = 0
10     for _ in range(100000000):
11         sum += 1
12     print(i, sum)
13 
14 if __name__ == '__main__':
15 
16 
17     ps = []
18 
19     pool = multiprocessing.Pool(3)
20     for i in range(3):
21         pool.apply_async(calc, args=(i,))
22     pool.close()
23     pool.join()
24 
25     delta = (datetime.datetime.now() - start).total_seconds()
26     print(delta)
27     print('end---')
4核心CPU处理三个进程

 

2 100000000
10.15358
end---

  总结:

    可以看出,利用进程池得出的结果和上面的结果差不多的。

      有个前提要注意:

        首先测试的电脑是4核心cpu,处理三个进程不会出现不够用,如果处理的进程如果大于CPU 核心数,就会出现等待,

        但是这样,利用进程池,可以有效的使用有限资源,剩余的资源可以做别的事,但是,像上面的情况,就会出现明显的资源抢占,资源不够用。

        所以,一般推荐使用线程池。

  测试:进程池的使用 apply,可以看到,一个一个执行完,没有多进程优势存在

 1 import multiprocessing
 2 import datetime
 3 import logging
 4 import threading
 5 #   次函数是一个CPU 密集型,所以,多线程和单线程 分别计算5 次, 计算是一样的效果,至少4分钟左右
 6 start = datetime.datetime.now()
 7 
 8 def calc(i):
 9     sum = 0
10     for _ in range(100000000):
11         sum += 1
12     print(i, sum)
13 
14 if __name__ == '__main__':
15 
16 
17     ps = []
18 
19     pool = multiprocessing.Pool(3)
20     for i in range(3):
21         pool.apply(calc, args=(i,))
22     pool.close()
23     pool.join()
24 
25     delta = (datetime.datetime.now() - start).total_seconds()
26     print(delta)
27     print('end---')
apply

 

0 100000000
1 100000000
2 100000000
22.069262
end---

 

  测试:回调

 1 import multiprocessing
 2 import datetime
 3 import logging
 4 import threading
 5 #   次函数是一个CPU 密集型,所以,多线程和单线程 分别计算5 次, 计算是一样的效果,至少4分钟左右
 6 logging.basicConfig(level=logging.INFO, format='%(process)s %(processName)s %(thread)s %(message)s')
 7 start = datetime.datetime.now()
 8 
 9 def calc(i):
10     sum = 0
11     for _ in range(100000000):
12         sum += 1
13     return i
14 
15 if __name__ == '__main__':
16     ps = []
17 
18     pool = multiprocessing.Pool(3)
19     for i in range(3):
20         pool.apply_async(calc, args=(i,), callback=lambda x: logging.info('x ={}'.format(x)))
21     pool.close()
22     pool.join()
23 
24     delta = (datetime.datetime.now() - start).total_seconds()
25     print(delta)
26     print('end---')
callback
3848 MainProcess 7764 x =0
3848 MainProcess 7764 x =2
3848 MainProcess 7764 x =1
13.521773
end---

 

    回调:自己去主动调用,这里的回调就是,每个任务结束后的返回值,作为回调的参数传入。

    而使用多线程,线程的返回值 是不能被用到的。结束就结束了。

5、多进程,多线程的选择:

  1、CPU密集型

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

  2、IO密集型

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

6、应用

  请求/ 应答 模型:WEB应用中常见的处理模型

  master 启动朵儿worker工作进程,一般 额CPU 数目相同,发挥多核优势。

  worker工作进程中,往往需要操作网络IO 和 磁盘 IO ,启动多线程,提高并发处理能力worker处理用户的请求,往往需要等待数据,处理完请求还要通过网络IO 返回响应。

  这就是Nginx 工作模式。

  worker进程开启多线程,因为要进行IO ,所以多线程相对合适。

 

 

 

 

 

 

 

 

 

 

 

 

  
  



        apply 是同步阻塞,不推荐使用
        apply_async :是异步非阻塞

        回调就是调用你的函数。调用空闲函数


        自己的任务结束后,才会执行,calback必须有一个参数,接受前面函数的返回值


        concurrent :

        多进程和多线程,注意是cpu密集型还是IO 密集型



logging模块:
    

 

posted @ 2018-10-29 22:05  JerryZao  阅读(339)  评论(0编辑  收藏  举报