多进程
python多进程模块multiprocessing
和多线程模块threading
几乎是一模一样的函数和使用方法。
- 多线程因为GPL全局解释器锁,同一时刻只有一个线程。但是多进程是另起一个进程,就不受GPL控制了。多进程是国与国之间的关系,数据、资源不共享。
- 多进程可以完全独立的进程环境中运行程序,可以充分地利用多处理器.但是进程本身的隔离带来的数据不共享也是一个问题.而且线程比进程轻量级.
- IO密集型不管多线程还是多进程,都是在等,所以一般在多线程解决就ok。
1 进程的pid、exitcode和终止进程
线程不能人为终止,但是进程可以。启动多进程,必须在if __name__ == '__main__':
中写。
示例:
import datetime
import multiprocessing
def calc(x):
sum_num = 0
for _ in range(10000):
sum_num += 1
print(x, sum_num)
cur_process = multiprocessing.current_process()
cur_pid = cur_process.pid # 进程ID
status = cur_process.exitcode # 进程的退出状态码
cur_process.terminate() # 终止进程。线程不能人为终止,但是进程可以。
if __name__ == '__main__':
# 多进程,必须在main中写。
start = datetime.datetime.now()
lst = []
for i in range(5):
p = multiprocessing.Process(target=calc, args=(i,), name='p-{}'.format(i))
lst.append(p)
p.start()
for p in lst:
p.join()
print('pid={}, exitcode={}'.format(p.pid, p.exitcode))
# p.terminate(),终止进程。差异:进程可以终止,但是线程不能人为终止。
print('total time =', (datetime.datetime.now() - start).total_seconds())
# 进程pid、kill进程、进程退出状态码
process_lst = multiprocessing.active_children()
for i, p in enumerate(process_lst):
p.kill() if i % 2 else p.pid
if p.exitcode:
print(p.exitcode, p.pid)
2 进程同步
进程间同步提供了和线程同步一样的类,使用的方法一样,使用的效果也类似。不过,进程间代价要高于线程,而且底层实现是不同的,只不过Python屏蔽了这些,让用户简单使用。
multiprocessing还提供共享内存、服务器进程来共享数据,还提供了Queue
队列、 Pipe
管道用于进程间通信。
import multiprocessing
# 进程同步
multiprocessing.Event() # 事件
multiprocessing.Lock() # 进程锁,进程间的锁协调起来很麻烦,只是python通过API,让多线程和多进程看起来一样。
multiprocessing.RLock() # 可重入锁
multiprocessing.Condition() # 生产者消费者模型
multiprocessing.Barrier() # 栅栏
multiprocessing.Semaphore() # 信号量
multiprocessing.queues # 通过队列实现多进程数据共享,用得少。通常就用消息中间件了
multiprocessing.Pipe() # 管道
3 进程池 multiprocessing.Pool
池解决的是计算机资源有限的问题。针对一个服务,开辟一个空间,这个服务内的各种进程只能在限定的空间内运行,保证计算机资源不被一个服务全部占满。池就可以复用。multiprocessing.Pool
的方法:
apply()
: 阻塞主进程, 并且一个一个按顺序地执行子进程, 等到全部子进程都执行完毕后 ,继续执行apply()后面主进程的代码
apply_async()
: 非阻塞异步的, 他不会等待子进程执行完毕, 主进程会继续执行, 他会根据系统调度来进行进程切换
import datetime
import multiprocessing
def calc(x):
sum_num = 0
for _ in range(10000):
sum_num += 1
print(x, sum_num)
if __name__ == '__main__':
# 进程池:
print('multiprocessing.Pool')
start = datetime.datetime.now()
pool = multiprocessing.Pool(5)
for i in range(5):
# 非阻塞异步,主进程启动一个子进程后,不会等待子进程结束后再往下执行,而是继续执行主进程。如果主进程执行完子进程还没跑完会被kill。可以通过pool.close();pool.join(),阻塞主进程等待子进程执行完后再退出。
pool.apply_async(calc, args=(i,))
pool.close() # close必须在join前。
pool.join()
print('total time = {}'.format((datetime.datetime.now() - start).total_seconds()))
start = datetime.datetime.now()
pool = multiprocessing.Pool(5)
for i in range(5):
# 阻塞。子进程退出后才继续往下走,循环开启下一个子进程。
pool.apply(calc, args=(i,))
print('total time = {}'.format((datetime.datetime.now() - start).total_seconds()))
4 小结
多进程和多线程通信方式不同:
- 进程就是启动多个解释器进程,进程间通信必须序列化、反序列化
- 数据的线程安全性问题:由于每个进程中没有实现多线程,GIL可以说没什么用了。
多进程、多线程的选择:
1、CPU密集型:CPython中使用到了GIL,多钱程的时候锁相E竞争,且多核优势不能发挥,Python多迦程效率更 高.
2、IO密集型:适合是用多线程,减少IO序列化开销.且在IO等待的时候,切换到真他线程继续执行,效率不 错.
应用:
请求/应答模型:WEB应用中常见的处理模型
master启动多个worker工作进程,一般和CPU数目相同.worker工作进程中启动多线程,提高并发处理能力,worker处理用户的请求,往往需要等待数据。
这就是nginx工作模式。
concurrent.futures模块
异步的线程/进程池,concurrent.futures模块: 一次起指定数量的线程数,提交的任务在这些线程中跑,当线程中的任务执行完毕时,线程不会结束,而是保留资源,再次提交新的任务时在空闲的线程中跑新任务,而不会另起线程;优点是避免反复申请资源。
futures.ThreadPoolExecutor:线程池的执行器对象。实例方法:
submit: 提交执行的函数及其参数,返回一个Future实例.
shutdown: 清理池,所有任务结束后清理资源。而不用频繁的申请资源。
Future类的方法:
cancelled(),如果调用被成功的取消,返回True
running(),如果正在运行且不能被取消,返回True
cancel(),尝试取消调用,如果已经执行且不能被取消返回False,否则返回True
result(timeout=None),取返回的结果,当timeout=None时一直等待,直到取得返回结果;设置超时时间时,超时将抛异常
exception(timeout=None),取返回的异常,timeout 同上
示例:
import datetime
from concurrent import futures
import logging
import threading
import time
fn = "[%(asctime)s - processID%(process)d - threadID%(thread)d - %(funcName)s - %(levelname)s] %(message)s"
logging.basicConfig(level=logging.INFO, format=fn)
def worker(n):
cur = threading.current_thread().name
logging.info("{}:start work, num={}".format(cur, n))
time.sleep(5)
logging.info("{}:finish work, num={}".format(cur, n))
if __name__ == '__main__':
start = datetime.datetime.now()
executor = futures.ThreadPoolExecutor(3)
# executor = futures.ProcessPoolExecutor(3)
fn = []
for i in range(3):
f = executor.submit(worker, i)
fn.append(f)
for i in range(3, 6):
f = executor.submit(worker, i)
fn.append(f)
while True:
time.sleep(2)
logging.info(threading.enumerate())
flag = True
for f in fn:
logging.info('current status:{}'.format(f.done()))
flag = flag and f.done()
if flag:
logging.info(threading.enumerate())
break
futures.ProcessPoolExecutor和futures.ThreadPoolExecutor使用方法相同,只是提交执行必须在if __name__ == "__main__"
后。
futures.ProcessPoolExecutor和futures.ThreadPoolExecutor支持上下文管理.例如:
from concurrent import futures
def func(*args):
print(args)
if __name__ == "__main__":
arg = (1, 2, 3)
with futures.ThreadPoolExecutor(3) as executor:
f= executor.submit(func,*arg)
concurrent.futures模块统一了线程池、进程池的调用,简化了编程,是python简单的思想哲学体现。是一种高级API,应当尽量使用。唯一缺点:不能设置线程名称,实际上线程名没多大意义。