进程池线程池,携程

1. 进程池

1.1 简介
进程池是资源进程、管理进程组成的技术的应用.
进程池技术的应用至少由以下两部分组成:
资源进程:
预先创建好的空闲进程,管理进程会把工作分发到空闲进程来处理。
管理进程:
管理进程负责创建资源进程,把工作交给空闲资源进程处理,回收已经处理完工作的资源进程。

可以通过IPC,信号,信号量,消息队列,管道... 让资源进程与管理进程进行交互.
1.2 为什么要有进程池?
应用程序运行忙时会有成千上万的任务需要被执行, 闲时可能只有零星任务.

降低程序的效率低的两个问题:
* 1. 每个进程创建需要消耗时间,销毁进程也需要消耗时间.
* 2. 那么在成千上万个任务需要被执行的时候,
     也不能无限制的根据任务去开启或者结束进程, 
     操作系统不可能让他们同时执行.
使用进程池解决上述问题.
定义一个池子, 在池子中创建固定数量的进程数.
有需求来了,就拿一个池中的进程来处理任务, 
等到处理完毕,进程并不关闭,而是将进程再放回进程池中继续等待任务.

如果有很多任务需要执行,池中的进程数量不够,
任务就要等待之前的进程执行任务完毕归来,拿到空闲进程才能继续执行。

进池中进程的数量是固定的, 同时也控制了同一时间最多可以运行的进程数据.
这样不会增加操作系统的调度难度, 还节省了开关进程的时间,
也一定程度上能够实现并发效果。

2. Pool模块开启进程池

通过维护一个进程池来控制进程的数目, 
比如使用httpd的进程模式, 可以规定最大进程数和最小进程数,
2.1 开启进程池
Python的multiprocessing模块Pool类提供开启进程池的方法.

线程池对象 = Pool([numprocess],[initializer],[initargs])

参数:
numprocess	要创建的进程数,如果省略,将默认使用cpu_count()的值
initializer	每个工作进程启动时要执行的可调用对象,默认为None
initargs	传给initializer的参数组
2.2 常用方法
同步调用:
.apply(func,args,kwargs) 为进程池对象绑定函数.
    func: 指定执行的函数.
    args,kwargs: 传递的参数
 
 异步调用:
.apply_async(func,args,kwargs) 为进程池对象绑定函数.
    func: 指定执行的函数.
    args,kwargs: 传递的参数
    
	然后返回结果, 结果是AsyncResult类的实例, 可以使用回调函数callback,
    将前面func返回的结果单做参数传给回调函数.
    
p.close( )	关闭进程池,防止进一步操作, 如果所有操作持续挂起,它们将在工作进程终止前完成
P.jion()	等待所有工作进程退出。此方法只能在close()或teminate()之后调用, 否则报错.
2.3 同步调用示例
同步:提交完任务之后原地等待任务的返回结果, 期间不做任何事.
异步:提交完任务之后不愿地等待任务的返回结果 结果由异步回调机制自动反馈.
import os
# 获取逻辑内核(一个线程算一个cpu)
cpu_num = os.cpu_count()  # 20  12700h 14核20线程
# 安装psutil模块: pip install psutil 
import psutil
# False 获取物理内核, 为True 获取逻辑内核
cpu_num = psutil.cpu_count(logical=False)
print(cpu_num)  # 14
from multiprocessing import Pool
import time, os


def func(i):
    # 获取进程号
    print(f"子进程:{os.getpid()}")
    # 延时1秒
    time.sleep(1)
    # 返回值
    return i


if __name__ == '__main__':
    # 创建进程池并开启进程
    p = Pool(processes=4)
    # 定义一个空列表
    res_list = []
    # 计时开启
    start_time = time.time()
    for i in range(10):
        # 创建十个个任务, 使用同步调用的方式, 会等待返回结果
        res = p.apply(func, args=(i,))
        # 接受返回值
        res_list.append(res)

    # 先关闭进程池, 不会再有新的进程加入到pool中, 防止进一步的操作(同步调用可以不加此方法)
    p.close()
    # 必须在close调用之后执行, 否则报错, 执行后等待所有子进程结束(同步调用可以不加此方法)
    p.join()
    # 同步调用, 得到的就是最终结果,(异步调用得到的是对象, 需要使用get方法取值)
    print(res_list)
    # 程序执行时间
    print(f'程序执行时间:{time.time() - start_time}秒')

终端显示:
子进程:6228
子进程:4904
子进程:9688
子进程:10540
子进程:6228
子进程:4904
子进程:9688
子进程:10540
子进程:6228
子进程:4904
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
程序执行时间:10.156244277954102
________________________________________________________________________
从pid可以看出始终使用的是那几个进程.
同步提交需要等待上一个任务结束拿到结果才能进行下一个任务, 所以用时 10 秒多一点.
2.4 异步调用示例
from multiprocessing import Pool
import time, os


# 定义进程调用的函数
def func(i):
    # 获取进程号
    print(f"子进程:{os.getpid()}")
    # 延时1秒
    time.sleep(1)
    # 返回值
    return i


if __name__ == '__main__':
    # 本机CPU个数
    cpu_num = os.cpu_count()  # 20
    # 创建进程池并开启进程
    p = Pool(processes=cpu_num)
    # 定义一个空列表
    res_list = []
    # 计时开启
    start_time = time.time()
    for i in range(cpu_num):
        # 创建二十个任务, 使用同步调用的方式, 会等待返回结果
        res = p.apply_async(func, args=(i,))
        # 接受返回值
        res_list.append(res)

    # 先关闭进程池, 不会再有新的进程加入到pool中, 防止进一步的操作(同步调用可以不加此方法)
    p.close()
    # 必须在close调用之后执行, 否则报错, 执行后等待所有子进程结束(同步调用可以不加此方法)
    p.join()
    # 同步调用, 得到的就是最终结果,(异步调用得到的是对象, 需要使用get方法取值)
    print([i.get() for i in res_list])
    # 程序执行时间
    print(f'程序执行时间:{time.time() - start_time}秒')

终端显示:
子进程:7980
....
子进程:15240
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
程序执行时间:1.1466412544250488
___________________________________________________________________________________
异步调用方式, 如果任务进行时遇到阻塞操作会, 会切换进程, 等到阻塞结束在进入就绪态等待执行...
返回值是AsyncResul 的对象, 需要使用 .get() 方法取值.

3. 回调函数

回调函数: 将一个函数的内存地址作为参数传给另一个函数处理, 这一个函数就称为回调函数.
当进程池中一个异步任务处理完之后, 它去通知主进程自己结束了, 
让主进程处调用另一个函数去处理该结果.
from multiprocessing import Pool


# 进程调用方法
def add_num(n):
    for i in range(100000000):
        n += i
    return n  # 返回任务执行的结果


# 回调函数
def callback(num):
    print(num + 1)


if __name__ == '__main__':
    # 开启进程池
    p = Pool(3)
    li = []
    create_time = time.time()
    for i in range(3):
        # 异步调用,并使用callback设置回调
        res = p.apply_async(add_num, args=(i,), callback=callback)
        li.append(res)

    # 关闭进程池
    p.close()
    # 等待子进程结束
    p.join()
    # 使用get方法拿到结果是一个迭代器, 使用[]显示值
    print([ii.get() for ii in li])

终端显示:
4999999950000002
4999999950000001
4999999950000003
[4999999950000000, 4999999950000001, 4999999950000002]
应用场景:
我们可以将耗时间或者阻塞的代码作为一个任务放入进程池, 在主进程中指定回调函数, 
当子进程任务执行完毕后, 返回结果交给主进程去调用函对结果进行处理.
这样主进程在执行通过回调函数省去了I/O的过程, 直接拿到的就是任务的结果.

4. ThreadPool开启线程池

线程池的使用与进程池一样.
使用ThreadPool类生线程池.
from multiprocessing.pool import ThreadPool
4.1 同步调用
# 导入线程池
from multiprocessing.pool import ThreadPool
import time, os


def func(i):
    # 获取进程号
    print(f"进程号:{os.getpid()}")
    # 延时1秒
    time.sleep(1)
    # 返回值
    return i


if __name__ == '__main__':
    # 创建线程池并开启线程
    p = ThreadPool(processes=4)
    # 定义一个空列表
    res_list = []
    # 计时开启
    start_time = time.time()
    for i in range(10):
        # 创建十个个任务, 使用同步调用的方式, 会等待返回结果
        res = p.apply(func, args=(i,))
        # 接受返回值
        res_list.append(res)

    # 先关闭线程池, 不会再有新的线程加入到pool中, 防止线一步的操作(同步调用可以不加此方法)
    p.close()
    # 必须在close调用之后执行, 否则报错, 执行后等待所有子线程结束(同步调用可以不加此方法)
    p.join()
    # 同步调用, 得到的就是最终结果,(异步调用得到的是对象, 需要使用get方法取值)
    print(res_list)
    # 程序执行时间
    print(f'程序执行时间:{time.time() - start_time}秒')

终端显示:
进程号:4388
进程号:4388
进程号:4388
进程号:4388
进程号:4388
进程号:4388
进程号:4388
进程号:4388
进程号:4388
进程号:4388
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
程序执行时间:10.074846267700195
4.2 异步调用
from multiprocessing.pool import ThreadPool
import time, os


# 定义线程调用的函数
def func(i):
    # 获取线程号
    print(f"子线程:{os.getpid()}")
    # 延时1秒
    time.sleep(1)
    # 返回值
    return i


if __name__ == '__main__':
    # 本机CPU个数
    cpu_num = os.cpu_count()  # 20
    # 创建线程池并开启线程
    p = ThreadPool(processes=cpu_num)
    # 定义一个空列表
    res_list = []
    # 计时开启
    start_time = time.time()
    for i in range(cpu_num):
        # 创建二十个任务, 使用同步调用的方式, 会等待返回结果
        res = p.apply_async(func, args=(i,))
        # 接受返回值
        res_list.append(res)

    # 先关闭线程池, 不会再有新的线程加入到pool中, 防止线一步的操作(同步调用可以不加此方法)
    p.close()
    # 必须在close调用之后执行, 否则报错, 执行后等待所有子线程结束(同步调用可以不加此方法)
    p.join()
    # 同步调用, 得到的就是最终结果,(异步调用得到的是对象, 需要使用get方法取值)
    print([i.get() for i in res_list])
    # 程序执行时间
    print(f'程序执行时间:{time.time() - start_time}秒')
	
终端显示:(挤在一行)
进程号:17736进程号:17736
...
进程号:17736
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
程序执行时间:1.0043601989746094

5. ProcessPoolExecutor开启进程池

导入ProcessPoolExecutor模块:
from concurrent.futures import ProcessPoolExecutor

开启进程池:
进程池对象 = ProcessPoolExecutor(开启的数量)

进程池对象常用方法:
res = pool.submit(调用的函数, 函数的参数) 默认接受两个参数.
res是一个Future对象, 异步接收子进程调用的函数的返回值.
Future对象.result() 获取返回值.

pool.shutdown() 等待子进程执行完毕

回调函数使用:
pool.submit().add_done_callback(回调函数名)
5.1 同步提交
from concurrent.futures import ProcessPoolExecutor
import time
import os


# 定义一个任务
def task(n):
    # 获取进程pid
    print(n, os.getpid())
    time.sleep(2)
    # 返回结果
    return n


if __name__ == '__main__':
    # 开设进程池
    pool = ProcessPoolExecutor(5)
    res_list = []
    create_time = time.time()
    for i in range(10):
        # 提交任务与任务参数, 返回一个<class 'concurrent.futures._base.Future'>对象
        res = pool.submit(task, i)
        # 对象使用.result()获取值
        res_list.append(res.result())  # 这一步是区分程序是同步执行还是异步执行的关键.
        """
        获取子进程后调用函数后, 返回值会异步封装到Future对象中
        执行到res.result(), res的值还没有.result()会等待res有值才会执行
        程序就进入阻塞, 影响了开启后续的子进程, 程序就是同步的了.
        """

    # 等待子进程执行完毕
    pool.shutdown()
    print(f'程序执行时间:{time.time() - create_time}')

终端显示:
0 10012
0
1 20360
1
2 4864
2
3 13272
3
4 9252
4
5 10012
5
6 20360
6
7 4864
7
8 13272
8
9 9252
9
程序执行时间:20.1526141166687
5.2 异步提交
from concurrent.futures import ProcessPoolExecutor
import time
import os


# 定义一个任务
def task(n):
    # 获取进程pid
    print(n, os.getpid())
    time.sleep(2)
    # 返回结果
    return n


if __name__ == '__main__':
    # 开设进程池
    pool = ProcessPoolExecutor(5)

    # 创建一个空列表
    res_list = []

    create_time = time.time()
    for i in range(10):
        # 提交任务与任务参数, 返回一个<class 'concurrent.futures._base.Future'>对象
        res = pool.submit(task, i)
        # 将返回结果添加到列表中
        res_list.append(res)
        """
        获取子进程后调用函数后, 返回值会异步封装到Future对象中
	    执行到res_list.append(res) 将<class 'concurrent.futures._base.Future'>对象添加到列表中
	    在主进程中获取Future对象的值, 不影响连续开启子进程. 程序就是异步的了.
        """

    # 等待子进程执行完毕
    pool.shutdown()
    # 获取返回结果
    print([res.result() for res in res_list])
    print(f'程序执行时间:{time.time() - create_time}')
终端显示:
0 13108
1 17476
2 17544
3 4916
4 1964
5 17476
6 13108
7 1964
8 4916
9 17544
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
程序执行时间:4.105138063430786
________________________________________________________________________
从pid可以看出始终使用的是那几个进程.
同步提交需要等待上一个任务结束拿到结果才能进行下一个任务, 所以用时 4 秒多一点.
5.3 回调函数
from concurrent.futures import ProcessPoolExecutor
import time
import os


# 定义一个任务
def task(n):
    # 获取进程pid
    print(n, os.getpid())
    time.sleep(2)
    # 返回结果
    return n


# 定义回调函数
def callback(x):
    print(f'回调函数处理结果: {x.result()}')


if __name__ == '__main__':
    # 开设进程池
    pool = ProcessPoolExecutor(5)
    create_time = time.time()
    for i in range(10):
        # 提交任务与任务参数, 返回值会作为参数传递给回调函数
        pool.submit(task, i).add_done_callback(callback)

    # 等待子进程执行完毕
    pool.shutdown()
    print(f'程序执行时间:{time.time() - create_time}')

终端显示
0 14240
1 6760
2 17752
3 6044
4 8672
5 14240
回调函数处理结果: 0  # 子进程执行完毕后自动回调
6 8672
7 6044
8 17752
回调函数处理结果: 4
9 6760
回调函数处理结果: 3
回调函数处理结果: 2
回调函数处理结果: 1
回调函数处理结果: 5
回调函数处理结果: 8
回调函数处理结果: 9
回调函数处理结果: 7
回调函数处理结果: 6
程序执行时间:4.103053569793701

6. ThreadPoolExecutor开启线程池

使用方式与进程池一样.
6.1 同步提交
from concurrent.futures import ThreadPoolExecutor
import time
import os


# 定义一个任务
def task(n):
    # 获取线程pid
    print(f'{n} {os.getpid()}\n', end='')
    time.sleep(2)
    # 返回结果
    return n


if __name__ == '__main__':
    # 开设线程池
    pool = ThreadPoolExecutor(5)
    create_time = time.time()
    for i in range(10):
        # 提交任务与任务参数, 返回值会作为参数传递给回调函数
        res = pool.submit(task, i)
        # 获取返回值
        print(res.result())

    # 等待线程执行完毕
    pool.shutdown()
    print(f'程序执行时间:{time.time() - create_time}')

终端显示:
0 1384
0
1 1384
1
2 1384
2
3 1384
3
4 1384
4
5 1384
5
6 1384
6
7 1384
7
8 1384
8
9 1384
9
程序执行时间:20.06202530860901
6.2 异步提交
from concurrent.futures import ThreadPoolExecutor
import time
import os


# 定义一个任务
def task(n):
    # 获取线程pid
    print(f'{n} {os.getpid()}\n', end='')
    time.sleep(2)
    # 返回结果
    return n


if __name__ == '__main__':
    # 开设线程池
    pool = ThreadPoolExecutor(5)
    res_list = []
    create_time = time.time()
    for i in range(10):
        # 提交任务与任务参数, 返回值会作为参数传递给回调函数
        res = pool.submit(task, i)
        res_list.append(res)

    # 等待线程执行完毕
    pool.shutdown()
    print([res.result() for res in res_list])
    print(f'程序执行时间:{time.time() - create_time}')

终端显示:
0 9644
1 9644
2 9644
3 9644
4 9644
5 9644
6 9644
7 9644
8 9644
9 9644
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
程序执行时间:4.025704383850098
6.3 回调函数
from concurrent.futures import ThreadPoolExecutor
import time
import os


# 定义一个任务
def task(n):
    # 获取线程pid
    print(f'{n} {os.getpid()}\n', end='')
    time.sleep(2)
    # 返回结果
    return n


# 定义回调函数
def callback(x):
    print(f'回调函数处理结果: {x.result()}\n', end='')


if __name__ == '__main__':
    # 开设线程池
    pool = ThreadPoolExecutor(5)
    create_time = time.time()
    for i in range(10):
        # 提交任务与任务参数, 返回值会作为参数传递给回调函数
        pool.submit(task, i).add_done_callback(callback)

    # 等待线程执行完毕
    pool.shutdown()
    print(f'程序执行时间:{time.time() - create_time}')

终端显示:
0 17448
1 17448
2 17448
3 17448
4 17448
回调函数处理结果: 3
5 17448
回调函数处理结果: 4
回调函数处理结果: 0
6 17448
回调函数处理结果: 2
7 17448
8 17448
回调函数处理结果: 1
9 17448
回调函数处理结果: 9
回调函数处理结果: 7
回调函数处理结果: 6
回调函数处理结果: 5
回调函数处理结果: 8
程序执行时间:4.017932891845703

6. 携程

6.1 简介
协程(微线程,纤程,也称为用户级线程) :在不开辟线程的基础上完成多任务, 
也就是在单线程的情况下完成多任务,多个任务之间遇到io阻塞就会切换任务执行.
6.2 原理
CPU被剥夺的条件:
* 1. 程序长时间占用
* 2. 程序进入IO操作(欺骗CPU)

单线程下自己检测IO操作并且实现代码层面的任务切换
那么对于CPU而言这个程序就没有IO操作, CPU会尽可能的被占用.
6.3 使用
使用gevent第三方模块对脚本文件进行检测, 遇到io切行任务.
# 执行两个函数
import time


def play(name):
    print('%s play 1' % name)
    time.sleep(5)
    print('%s play 2' % name)


def eat(name):
    print('%s eat 1' % name)
    time.sleep(3)
    print('%s eat 2' % name)


start = time.time()
play('kid')
eat('qq')
# 串行执行
print('主', time.time() - start)

终端显示:
kid play 1
kid play 2
qq eat 1
qq eat 2
 8.015759944915771
# 携程执行两个函数
from gevent import monkey
from gevent import spawn
import time

# 检测所有的IO行为
monkey.patch_all()


def play(name):
    print('%s play 1' % name)
    time.sleep(5)
    print('%s play 2' % name)


def eat(name):
    print('%s eat 1' % name)
    time.sleep(3)
    print('%s eat 2' % name)


start = time.time()

# 异步提交 (执行函数, 给函数的参数)
g1 = spawn(play, 'kid')
# 异步提交
g2 = spawn(eat, 'qq')
# 等待被监测的任务运行完毕
g1.join()
g2.join()
# 单线程下实现并发,提升效率
print('主', time.time() - start)

终端显示:
kid play 1
qq eat 1
qq eat 2
kid play 2
 5.023998975753784

2022-10-20_00864

6.4 协程实现TCP服务端并发
1. 服务端
# 并发效果:一个服务端可以同时服务多个客户端
import socket
# 检测所有io
from gevent import monkey

from gevent import spawn

# 检测所有io阻塞
monkey.patch_all()


# 监听
def talk(sock):
    # 通信循环
    while True:
        try:
            data = sock.recv(1024)
            # 不接受空信息
            if len(data) == 0: break
            # 展示客户端发送的信息
            print(data.decode('utf-8'))
            # 向服务端发送信息
            sock.send(b'hello baby!')
        except ConnectionResetError as e:
            print(e)
            sock.close()
            break


# 开启服务端
def servers():
    server = socket.socket()
    server.bind(('127.0.0.1', 8080))
    server.listen()
    # 链接循环
    while True:
        # 监听 (阻塞)
        sock, addr = server.accept()
        # 每次接收到客户端的连接都提交一次任务
        spawn(talk, sock)


# 携程任务
g1 = spawn(servers)
g1.join()

2. 客户端
# 客户端开设几百个线程发消息
from threading import Thread, current_thread
from socket import *


def client_task():
    client = socket(AF_INET, SOCK_STREAM)
    client.connect(('127.0.0.1', 8080))

    # 通信循环 where True:  # 缩进下面所有的代码
    # current_thread().name 获取线程的名 Thread-序号
    msg = f'{current_thread().name}s ay hello'

    # 发送信息
    client.send(msg.encode('utf-8'))
    # 接收信息
    data = client.recv(1024)
    print(data.decode('utf-8'))


if __name__ == '__main__':
    for i in range(500):
        t = Thread(target=client_task)
        t.start()

最厉害的情况:多进程下开设多线程, 多线程下开设协程.
以后可能自己动手写的不多, 一般都是使用别人封装好的模块或框架.

文章的段落全是代码块包裹的, 留言0是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言1是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言2是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言3是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言4是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言5是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言6是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言7是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言8是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言9是为了避免文章提示质量低.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值