进程池 线程池 协程

一、进程、线程池

#为什么用

开进程开线程都需要消耗资源消耗时间的,只不过两者比较的情况线程消耗的资源比较少
在成千上万个任务需要被执行的时候,我们可以去创建成千上万个进程么?
就算你比较二,建了无数个进程线程,系统敢让你执行么?除非它自己不要命了,辛辛苦苦创建出来还不能执行,气不气?

我们需要一个池子,根据计算机状况最大限度的限制进程线程数量,在计算机能够承受范围之内最大限度的利用计算机


#什么是池?
(硬件的发展跟不上软件的速度)
池其实是降低了程序的运行效率 但是保证了计算机硬件的安全
最大限度的限制进程线程数量,在保证计算机硬件安全的情况下最大限度的利用计算机

#提交任务的方式:
          同步:  提交任务之后 原地等待任务的返回结果 期间不做任何事
          异步:  提交任务之后 不等待任务的返回结果(异步的结果怎么拿???) 直接执行下一行代码
          异步回调机制:当异步提交的任务有返回结果之后,会自动触发回调函数的执行

#创建的特点

池子中创建的进程/线程创建一次就不会再创建了
至始至终用的都是最初的那几个
这样的话节省了反复开辟进程/线程的资源

1、进程池 

池中默认进程数: cpu个数

from concurrent.futures import ProcessPoolExecutor
import time
import os

pool = ProcessPoolExecutor(3)  # 默认是当前计算机cpu的个数

def task(n):
    print(n,os.getpid())  # 查看当前进程号,发现前三个和后三个进程号一样,进程池中三个进程重复利用
    time.sleep(2)
    return n**2,100

def call_back(n):
    print('拿到了异步提交任务的返回结果:',n.result())

if __name__ == '__main__':
    for i in range(6):
        pool.submit(task,i).add_done_callback(call_back)
        # 提交任务的时候,绑定一个回调函数call_back,
        # 一旦该任务有结果 pool.submit(task,i)即为task函数的返回值
        # add_done_callback 立刻将该结果传给回调函数call_back,并执行回调函数

'''
0 9304
1 9060
2 12240

3 9304
拿到了异步提交任务的返回结果: (0, 100)
4 9060
拿到了异步提交任务的返回结果: (1, 100)
5 12240
拿到了异步提交任务的返回结果: (4, 100)

拿到了异步提交任务的返回结果: (9, 100)
拿到了异步提交任务的返回结果: (16, 100)
拿到了异步提交任务的返回结果: (25, 100)

'''

 

2、线程池

池中默认线程数: cpu*5

2.1关闭池子后一起输出结果

pool.shutdown()  #等待池子中所有的任务执行完毕之后 才会往下运行代码
from concurrent.futures import ThreadPoolExecutor
import time

pool = ThreadPoolExecutor(3)  # 不传默认是当前所在计算机的cpu个数乘5

def task(n):
    print(n)
    time.sleep(2)
    return n**2

t_list = []
for i in range(6):

    res = pool.submit(task,i)
    # print(res.result())  # 原地等待任务的返回结果,这一句加上异步就变同步了,不能要
    t_list.append(res)

pool.shutdown()  # 关闭池子 等待池子中所有的任务执行完毕之后 才会往下运行代码
for p in t_list:
    print('>>>:',p.result())
'''
0
1
2

3
4
5

>>>: 0
>>>: 1
>>>: 4
>>>: 9
>>>: 16
>>>: 25
'''

2.2异步回调输出结果

from concurrent.futures import ThreadPoolExecutor
from threading import currentThread
import time

pool = ThreadPoolExecutor(3)  # 括号内可以传参数指定线程池内的线程个数,不传默认是当前所在计算机的cpu个数乘5
def task(n):
    print(n,currentThread().name)  #输出线程号会发现还是那3个在循环使用
    time.sleep(2)
    return n**2

def call_back(n):
    print('拿到了异步提交任务的返回结果:',n.result())

for i in range(6):
    pool.submit(task,i).add_done_callback(call_back)
    # 提交任务的时候,绑定一个回调函数call_back,
    # 一旦该任务有结果 pool.submit(task,i)即为task函数的返回值
    # add_done_callback 立刻将该结果传给回调函数call_back,并执行回调函数
'''
0 ThreadPoolExecutor-0_0
1 ThreadPoolExecutor-0_1
2 ThreadPoolExecutor-0_2

拿到了异步提交任务的返回结果: 0
3 ThreadPoolExecutor-0_0
拿到了异步提交任务的返回结果: 1
4 ThreadPoolExecutor-0_1
拿到了异步提交任务的返回结果: 4
5 ThreadPoolExecutor-0_2

拿到了异步提交任务的返回结果: 9
拿到了异步提交任务的返回结果: 16
拿到了异步提交任务的返回结果: 25
'''

二、协程

1.协程

    进程:资源单位
    线程:执行单位


协程:单线程下实现并发(完全是程序员自己意淫出来的名词) 

优点:
协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级 单线程内就可以实现并发的效果,最大限度地利用cpu

2.协程的实现:

进程三态中我们知道只要进程遇到阻塞就会被系统强制剥夺cpu权限,怎样不让剥夺呢?

当然是我们自己主动去发现io并尽量改正,好好表现争取更多cpu资源

对于单线程下,我们不可避免程序中出现io操作,但如果我们能在自己的程序中,即用户程序级别,主动监控IO,尽量不让操作系统操心

当单线程下的多个任务在一个任务遇到io阻塞时就立刻切换到另外一个任务去计算

这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被cpu执行的状态,相当于我们在用户程序级别将自己的io操作最大限度地隐藏起来

从而可以迷惑操作系统,让其看到:该线程好像是一直在计算,io比较少,从而更多的将cpu的执行权限分配给我们的线程,大大提升效率

4.提高效率的终极方法:

          多进程下开多线程
          多线程下再开协程

 

 

import time

def func1():
    for i in range(10000000):
        i+1

def func2():
    for i in range(10000000):
        i+1

start = time.time()
func1()
func2()
stop = time.time()
print(stop - start)
#串行执行 0.85s

#yield可以保存状态,然后next来取结果,我们能不能通过yield来切换并保存状态呢? 

运行发现不能提高效率也不能识别IO更别说自动切换了,浪费感情

# 效率还没串行高,并且还不能识别IO更别说自动切换了
import time
def func1():
    while True:
        10000000+1
        yield

def func2():
    g=func1()
    for i in range(10000000):
        # time.sleep(100)  # 模拟IO,打开后,程序不会运行了,也就是说yield并不会捕捉IO并自动切换
        i+1
        next(g)

start=time.time()
func2()
stop=time.time()
print(stop-start)
#基于yield并发执行 1.3s

 

2.1 gevent模块

from gevent import monkey;monkey.patch_all()  # 由于该模块经常被使用 所以建议写成一行 小猴可以监控任何IO
from gevent import spawn #这个可以识别少量IO主要用来切换加保存,监控交给小猴来做
from gevent import monkey;monkey.patch_all()  # 由于该模块经常被使用 所以建议写成一行
from gevent import spawn
import time
"""
注意gevent模块没办法自动识别time.sleep等io情况
需要你手动再配置一个参数,即小猴模块,小猴可以发现所有IO
"""

def heng():
    print("")
    time.sleep(2)
    print('哼哼')

def ha():
    print('')
    time.sleep(3)
    print('哈哈')

def heiheihei():
    print('')
    time.sleep(5)
    print('嘿嘿')

start = time.time()
g1 = spawn(heng)#spawn类似一个列表,把所有函数都放进去,程序一运行,他监测所有任务,一旦发现有IO立马换任务执行
g2 = spawn(ha)  # spawn对任务的切换是在代码层面上,操作系统不会发现,只会以为该进程一直在干活,就不会剥夺CPU权限
g3 = spawn(heiheihei) #spawn只要传进函数名字会自动加括号调用
# g1.join()
# g2.join()
g3.join()  #如果你不等,这里可就这一个主线程,直接就往下执行输出代码,代码完程序立刻停止,一个函数也不会执行
print(time.time() - start)  #5s多一点

2.2 基于gevent模块的TCP服务端单线程实现并发

#服务端

from gevent import monkey;monkey.patch_all()
import socket
from gevent import spawn

server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)

def talk(conn):
    while True:
        try:
            data = conn.recv(1024)
            if len(data) == 0:break
            print(data.decode('utf-8'))
            conn.send(data.upper())
        except ConnectionResetError as e:
            print(e)
            break
    conn.close()

def server1():
    while True:
        conn, addr = server.accept()
        spawn(talk,conn)  #监控talk中的IO,并传入参数conn

if __name__ == '__main__':
    g1 = spawn(server1)  #监控accept的IO
    g1.join()

#客户端

import socket
from threading import Thread,current_thread


def client():
    client = socket.socket()
    client.connect(('127.0.0.1',8080))
    n = 0
    while True:

        data = '%s %s'%(current_thread().name,n)
        client.send(data.encode('utf-8'))
        res = client.recv(1024)
        print(res.decode('utf-8'))
        n += 1

for i in range(400):
    t = Thread(target=client)
    t.start()

三、IO模型

    Stevens在文章中一共比较了五种IO Model:
    * blocking IO           阻塞IO
    * nonblocking IO      非阻塞IO
    * IO multiplexing      IO多路复用 (面试会问到看一看)
    * asynchronous IO    异步IO

    * signal driven IO     信号驱动IO

    由signal driven IO(信号驱动IO)在实际中并不常用,所以主要介绍其余四种IO Model。

 

posted @ 2019-08-15 21:28  www.pu  Views(165)  Comments(0Edit  收藏  举报