进程/线程池、协程,gevent模块的用法、 IO模型简介

内容大纲

1、进程池与线程池
2、协程
3、gevent模块
4、协程实现服务端 客户端通信
5、IO模型

一、进程池与线程池

由于服务端需要处于24小时运行状态,可不能来一个用户就开一个进程,因为开线程/进程都是需要内存消耗的,一旦客户端访问量很多,那么服务端就会崩溃,我们就可以规定计算机最多可以创建多少进程/线程,虽然降低了效率,但是保证了服务端的稳定

进程池的使用 代码示例

# 进程池使用 代码示例
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import time, os

# 实例化一个进程池对象
# 不知道参数的情况,默认是当前计算机cpu个数乘以5,也可以指定线程个数
pool = ProcessPoolExecutor(5)  # 创建一个池子,池子里面有5个线程


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


def fn_callback(future):
  print('拿到了结果:%s' % future.result())


if __name__ == '__main__':
  for i in range(10):
	  future = pool.submit(task, i).add_done_callback(fn_callback)
  print('主')

主
0 917
1 918
2 919
3 920
4 921
5 917
拿到了结果:0
6 919
拿到了结果:4
7 920
拿到了结果:1
8 918
拿到了结果:9
9 921
拿到了结果:16
拿到了结果:25
拿到了结果:49
拿到了结果:36
拿到了结果:64
拿到了结果:81

线程池的使用 代码示例

# 线程池使用 代码示例
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import time, os

# 实例化一个线程池对象
# 不知道参数的情况,默认是当前计算机cpu个数乘以5,也可以指定线程个数
pool = ThreadPoolExecutor(5)  # 创建一个池子,池子里面有5个线程
# 将上面一行的代码在 ThreadPoolExecutor 与 ProcessPoolExecutor 中切换线程与进程池


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


def fn_callback(future):
  print('拿到了结果:%s' % future.result())


if __name__ == '__main__':
  for i in range(10):
	  future = pool.submit(task, i).add_done_callback(fn_callback)
  print('主')
  
0 933
1 933
2 933
3 933
4 933
主
拿到了结果:0
5 933
拿到了结果:1
6 933
拿到了结果:4
7 933
拿到了结果:9
8 933
拿到了结果:16
9 933
拿到了结果:25
拿到了结果:49
拿到了结果:36
拿到了结果:64
拿到了结果:81

二、协程

进程:资源单位
线程:执行单位
协程:单线程下时下并发(能够在多个任务之间切换和保存状态来节省IO)
协程原理:使用方法捕捉代码中的IO操作,在程序遇到IO的时候切换到线程的另一个方法中,目的是避免程序进入阻塞态而使CPU切换到别处 
                       
操作系统中的多道技术是在多个线程之间切换的
但是协程是在单个线程中的多个功能之间切换的

协程对于操作系统是不存在的东西,是程序员基于代码层面创建出来的方法
并不是 单个线程下实现 切换+保存状态(CPU切换) 就可以提升效率
如果没有IO操作的程序 使用协程反而会降低效率

结论:
	将单个线程的效率提到最高,多进程下开多线程,多线程下用协程
  这样就实现了高并发!

yield实现 保存上次运行状态 代码示例

# 首先看一下 串行执行的代码
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)  # 2.2284772396087646


# 基于yield实现并执行
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)  # 3.470855951309204

# yield能够实现保存上次运行状态,但是无法识别遇到IO才切换

三、gevent模块

from gevent import spawn
# spawn 就是帮我们管理任务的对象
# gevent模块不能识别其模块下之外的IO操作,但是其内部封装了一个monky模块,能够帮助我们识别其他所有的IO行为

# 代码示例:
from gevent import monkey;monkey.patch_all()  # 检测所有的IO行为,写在代码的第一行
from gevent import spawn, joinall  # joinall列表里面放多个对象,实现 一行代码join多个对象 的效果
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()
g1 = spawn(play, 'egon')
g2 = spawn(eat, 'egon')

joinall([g1, g2])
# 上一行代码等于:
# g1.join()
# g2.join()
print('主', time.time() - start)

egon play 1
egon eat 1
egon eat 2
egon play 2
主 5.0070531368255615

四、协程实现服务端 客户端 通信

服务端 与 客户端 通信 属于 IO密集型程序
服务端中的 连接 和 通信 都是 IO密集型 操作,我们只需要在这两者间切换就可以了,当一方遇到IO就切换到另一个上运行

# 代码示例:
# 服务端
from gevent import monkey;monkey.patch_all()
from gevent import spawn
import socket


def communicate(conn):
  while True:
    try:
      data = conn.recv(1024)
      if len(data) == 0:break
      conn.send(data.upper())
    except ConnectionResetError:
      break
  conn.close()
    

def server(ip, port, backlog=5):
  server.bind((ip, port))
  server.listen(backlog)
  while True:  # 连接循环
    conn, addr = server.accept()
    print(addr)
    
    # 通信
    spawn(communicate, conn)

if __name__ == '__main__':
  g1 = spawn(server, '127.0.0.1', 8080)
  g1.join()


# 客户端
from threading import Thread, current_thread
import socket


def client():
  client = socket.socket()
  client.connect(('127.0.0.1', 8080))
  
  n = 0
  while True:
    msg = '%s say hello %s' % (current_thread().name, n)
    n += 1
    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)
    t.start()

五、IO模型

1、阻塞IO

2、非阻塞IO
	 (服务端通信针对accept用s.setlocking(False)加异常捕获,CUP占用率过高)

3、IO多路复用
  在只检测一个套接字的情况下,就能省去wait for data 过程
    
4、异步IO
posted @ 2019-05-09 16:45  输诚  阅读(289)  评论(0编辑  收藏  举报