python协程

进程是资源分配的最小单位,线程是CPU调度的最小单位

进程
    开销大 数据隔离(资源分配的最小单位) 能够利用多核
线程
    开销小 资源共享(cpu调度的最小单位)  能够利用多核(Cpython解释器下不行)

Cpython解释器

高计算型 开多进程
高IO型   开多线程
所以大部分情况下,基本上只要有网络操作,开启多线程一定可以提高这一整个程序的cpu使用率

线程

GIL 全局解释器锁 导致了线程不能利用多核
线程之间数据安全的问题
    Lock互斥锁
    Rlock递归锁
    死锁现象: 同一个线程中,出现了两把锁,同时锁两个资源去进行某些操作
线程队列
    先进先出的队列
    后进先出的栈
    优先级队列
池
    concurrent.futrues
    处理文件操作(处理日志文件) 处理网络并发(请求爬虫的页面)
    ThreadPoolExcutor  ProcessPoolExcutor
    submit
    shutdown
    map
    result
    add_done_callback

协程介绍

线程中能够放多个任务在一个线程中,其中的每一个任务都可以成为一个协程,每一个协程都可以在一条线程中任意的切换

线程: cpython解释器 只能用一核  只有有IO操作的多个任务才适合起多线程
协程的本质就是在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率

优点:
1.协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
2.单线程内就可以实现并发的效果,最大限度地利用cpu
缺点:
1.协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
2.协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程

串行执行

import time
def consumer(res):
    '''任务1:接收数据,处理数据'''
    pass

def producer():
    '''任务2:生产数据'''
    res=[]
    for i in range(10000000):
        res.append(i)
    return res

start=time.time()
#串行执行
res=producer()
#写成consumer(producer())会降低执行效率
consumer(res)
stop=time.time()
print(stop-start)

基于yield并发执行

import time
def consumer():
    '''任务1:接收数据,处理数据'''
    while True:
        x=yield

def producer():
    '''任务2:生产数据'''
    g=consumer()
    next(g)
    for i in range(10000000):
        g.send(i)

start=time.time()
#基于yield保存状态,实现两个任务直接来回切换,即并发的效果
producer()

stop=time.time()
print(stop-start)

greenlet模块介绍:

模块安装: pip install greenlet

import time
from greenlet import greenlet
def eat():
    print('start eating')
    
    # 保存当前函数的状态 切换到对应的函数中去执行
    g2.switch()
    time.sleep(1)
    print('eating finished')

def sleep():
    print('start sleeping')
    time.sleep(1)
    print('sleeping finished')
    g1.switch()

# 创建了一个协程对象
g1 = greenlet(eat)
# 创建了一个协程对象
g2 = greenlet(sleep)
g1.switch()

yield   快
greenlet 慢

gevent模块介绍:

pip3 install gevent
使用协程 是为了规避io,因为程序:遇到io就切换
gevent遇到IO就切换,基于greenlet进行切换

from threading import currentThread
from gevent import monkey
monkey.patch_all()
import time
# 内部使用了greenlet的切换机制  实现了遇到IO自动进行切换
import gevent

def eat():
    
    # DummyThread-1 伪线程
    print('start eating',currentThread())
    time.sleep(1)
    print('eating finished')

def sleep():

    # DummyThread-1 伪线程
    print('start sleeping',currentThread())
    time.sleep(1)
    print('sleeping finished')
g_l = []
for  i in range(5):

#创建一个协程对象g1,spawn括号内第一个参数是函数名,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数
    g1 = gevent.spawn(eat)
    g2 = gevent.spawn(sleep)
    g_l.append(g1)
    g_l.append(g2)

#等待g1结束
# g1.join()
# g2.join()

gevent.joinall(g_l)

两个协程模块

gevent - 基于greenlet - 使用更方便 性能相对低
asyncio - 基于yield  - 性能更好

flask 支持协程 gevent的
twisted sanic tornado  异步框架 用的是asyncio协程模块
让gevent识别的io操作是来自其他模块的(要写在其他模块之前)
from gevent import monkey
monkey.patch_all()

import gevent
import time
from urllib import request

def get_page(url_t):
    ret = request.urlopen(url_t[0])
    content = ret.read()
    with open(url_t[1]+'_new','wb') as f:
        f.write(content)
url_lst = [
    ('http://www.sogou.com','sogou'),
    ('http://www.baidu.com','baidu'),
    ('http://www.douban.com','douban'),
    ('http://www.cnblogs.com','cnblogs'),
    ('https://www.redhat.com/en','redhat'),
    ('https://www.centos.org/','centos'),
    ('https://www.liaoxuefeng.com/','lxf'),
    ('http://www.jd.com','jd'),
    ('http://www.taobao.com','tb')
]
start = time.time()
g_l = []
for url_t in url_lst:
    g = gevent.spawn(get_page,url_t)
    g_l.append(g)
gevent.joinall(g_l)
print(time.time() - start)

协程模拟socket server多客户端通信

server端:
from gevent import monkey
monkey.patch_all()
import socket
import gevent
def talk(conn):
    while True:
        msg = conn.recv(1024).decode()
        ret_msg = msg.upper().encode()
        conn.send(ret_msg)

sk = socket.socket()
sk.bind(('127.0.0.1',9000))
sk.listen()

while True:
    conn,_ = sk.accept()
    g = gevent.spawn(talk,conn)


client端:
import socket
import  threading
def client():
    sk = socket.socket()
    sk.connect(('127.0.0.1',9000))

    while True:
        sk.send(b'hello')
        msg = sk.recv(1024)
        print(msg)

for i in range(500):
    threading.Thread(target=client).start()

总结:

asyncio的状态切换是由yield关键字完成的
greenlet 扩展模块 进行函数中代码的来回切换

在其他编译型语言中 由于多线程可以利用多核 所以协程这个概念被弱化了
对于Cpython 多线程也不能利用多核 所以协程这个概念就变得至关重要了

协程的本质是一条线程
    1.不能利用多核
    2.用户级的概念,操作系统不可见
    3.协程不存在数据安全问题

切换 一条线程
    操作系统感知到线程一直在运行 就减少了线程进入阻塞状态的次数,从而提高了效率

协程实际的定义: 在一条线程之间来回切换

1.yield和函数作比较 yield进行切换实际上会浪费时间,即便是程序级别的切换也会浪费时间
2.500个client并发的server的效率计算,协程的处理并发能力 很强
3.geturl爬虫的练习,协程和函数做对比,协程的速度快  gevent去处理问题实际上很简便 直接扔给spawn就行了

建议
进程数 cpu个数的1-2倍  对于其他的语言 来说 进程是不常开的
线程数 20
协程数 500并发

进程  +  协程
    开5个进程,在进程中开协程 既能够利用多核 也可以提高处理IO的效率

线程
    处理 文件 ,input

进程 8个进程
协程 1000个
4*500 = 8000并发

 

posted @ 2019-01-14 08:39  LinuxCBB  阅读(202)  评论(0)    收藏  举报