线程和进程四--多进程和concurrent.futures模块

多进程

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 小结

多进程和多线程通信方式不同:

  1. 进程就是启动多个解释器进程,进程间通信必须序列化、反序列化
  2. 数据的线程安全性问题:由于每个进程中没有实现多线程,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,应当尽量使用。唯一缺点:不能设置线程名称,实际上线程名没多大意义。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一个两个四个三

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值