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

multiprocessing

Process类

Process类遵循了Thread类的API,减少了学习难度
先看一个例子,前面介绍的单线程、多线程比较的例子的多进程版本

import multiprocessing
import datetime

# 计算
def calc(i):
    sum = 0
    for _ in range(1000000000):
        sum += 1
    print(i, sum)

if __name__ == '__main__':
    start = datetime.datetime.now()

    ps = []
    for i in range(5):
        p = multiprocessing.Process(target=calc, args=(i,), name="calc-{}".format(i))
        ps.append(p)
        p.start()
    for p in ps:
        p.join()
    
    delta = (datetime.datetime.now() - start).total_seconds()
    print(delta)
    print('end=')

单线程、多线程都跑了4分多钟,而多进程用了1分半,这是真的并行
可以看出,几乎没有什么学习难度
注意:__name__ == "__main__"多进程代码一定要放在这下面执行

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

进程间同步

进程间同步提供了和线程同步一样的类,使用的方法一样,使用的效果也类似
不过,进程间代价要高于线程间,而且底层实现是不同的,只不过Python屏蔽了这些不同之处,让用户简单使用多进程

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

通信方式不同

  1. 多进程就是启动多个解释器进程,进程间通信必须序列化、反序列话
  2. 数据的线程安全性问题 由于每个进程中没有实现多线程,GIL可以说没什么用了

进程池举例

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之后使用
import logging
import datetime
import multiprocessing

# 日志打印进程id、进程名、线程id、线程名
logging.basicConfig(level=logging.INFO, format="%(process)d %(processName)s %(thread)d %(message)s")

# 计算
def calc(i):
    sum = 0
    for _ in range(1000):  # 增大这个值观察效果
        sum += 1
    logging.info('{} in function'.farmat(sum))


if __name__ == '__main__':
    start = datetime.datetime.now()
    pool = multiprocessing.Pool(5)
    for i in range(5):
        # 回调函数必须接受一个参数
        pool.apply_async(calc, args=(i, ), callback=lambda x: logging.info("{}.in callback".format(x)))  # 异步执行
    pool.close()
    pool.join()

    delta = (datetime.datetime.now() - start).total_seconds()
    print(delta)
    print('end===')

多进程、多线程的选择

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

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

应用

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

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