协程笔记

今日内容
思考题:
并发部分的最后一题
270w条数据
多线程 给每100行创建一个任务
对这100行进行分析
思路:多线程 + 协程


1 协程:
概念:多个任务在线程里来回切换这就是协程,
为什么说协程安全:
协程是不能够被操作系统感知的,所以在协程中数据的代码运行都是一块执行完整的,就不会发生不安全问题

2 作用:
解决高io的并发的编程,当cpu给线程的时间片,线程遇到阻塞就会切出去,而协程就解决了这个问题,让多个任务在一条线程来回切换,
让操作系统看起来很繁忙,而不去切换线程.提高了效率
在软件与硬件中是操作系统聊感知的,而python 解释器不能够知道硬件比如键盘的输入
3 为什么要有协程
1:能够让线程看起来很忙碌,就会骗过操作系统,让你的系统不进入阻塞队列
2:线程中也有三个状态,就绪---运行---阻塞 线程之间遇到io 就会被操作系统切换,进入阻塞状态,就降低了效率
3:有了协程就不用去做线程之间的切换了,减轻了操作系统的负担
4:就在就绪队列与运作之间来回
5:针对线程来说,协程数据是安全的,因为它没有被操作系统直接切换


注意 Cpython解释器
高计算型 开多进程
高IO型 开多线程

进程 线程 协程
进程:
是操作系统分配资源的最小单位
就是一程序正在执行的状态,实例 支持多核 开销大 数据隔离(数据安全) 建议开cpU 的一到俩倍
线程:
是操作系统调度的最小单位
依赖于进程,轻型的进程,开销小,数据共享, 一条进程大约开20条线程

应用与 网络编程 爬虫
协程 + 进程 爬虫 网站开发
线程 : 针对于文件管理 处理文件操作(处理日志文件) 处理网络并发(请求爬虫的页面)
进程 : 针对于 高精度算法
在cpyhon解释器下 线程 协程 都不支持 多核

关于协程的开启: 忘记了
依赖于第三方库 clenvet ---(yiled)--> envent


gevent :使用的是 greenlet 的切换机制,实现了IO的自动化的切换 稍微比yiled慢一点

asyncio: 使用的yield 关键字 实现了规避IO的操作 稍微快一点

进程 线程 协程 代码的区别
进程: 主进程是等待其他进程结束才结束的,不过主进程代码可能在子进程前结束所以得加join在子进程结束后在运行

线程:是不加join 应为主线程 结束需要所有的子线程运行结束了 它才结束

协程: 需要加join 的 得在线程中阻塞一下 ,才去执行 协程的任务


协程的原理 为什么要有协程:
使用协程 是为了 规避io
遇到io切换
切换机制:greenlet yiled

针对数据不安全的讲解
线程中的多个任务,其中的每一个任务都可以成为一个协程(感觉太准确,得几个任务来回切换,并且针对于io阻塞)

我们在线程的内部 又出现了一个新的单位 协程
能够放多个任务在一个线程中
每一个协程都可以在一条线程中任意的切换

线程 : cpython解释器 只能用一核 只有有IO操作的多个任务才适合起多线程
协程 : 在一个线程内能够放多个任务,并且这多个任务的执行状态还可以切换

20个任务 都是访问网页的爬虫任务
起20个线程,每个线程执行一个任务 并发效果
操作系统来做的

20个任务 5个线程
每个线程 执行4个任务
也不是协程

什么是协程呢?
在一条线程的基础上多个任务能够来回切换 这就是最基础协程

是不是所有放在线程内的任务都放在协程里就好了?
不是因为协程解决的是io操作
是不是就不需要有多线程了,直接协程就行了?
不是,线程是操作系统能够感知,调度的最小单位,操作系统对于io操作的识别度比程序还要高,比如键盘的输入协程是感知不到的
首先 如果对于所有的IO操作都能够做到协程 那么在Cpython解释器下 线程就没有意义了
但是 操作系统对于io操作的识别度 还是比 程序要高
操作系统是离硬件最近的
io操作 : 读写文件,网络操作,input,sleep
sleep : 时间 python能获取时间
input : 键盘 python程序中能够明确键盘操作
文件 : 硬件 操作系统是否提供了你一个工具,能够让你感知到某一个文件中的数据变化
网络 : 网卡 python能不能让我感知到网络上有数据来了
IO多路复用 操作系统提供给你的
我们能够从程序级别感知到的io操作很有限
网络
时间


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

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

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

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

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

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

爬虫
get url
数据分析 利用多核

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

8Cpu
进程 8个进程
协程 1000个
4*500 = 8000并发
# 线程池
from  concurrent.futures import ThreadPoolExecutor as Executor,ProcessPoolExecutor    #as Executor  线程与进程之间来回灵活的切换
def func(i):
    print('66',i)
    # return '*'*i
if __name__ == '__main__':
    t = Executor(4)
    #map函数型
    # ret = map(func,range(5))
    # for i in ret:
    #     print(i)#----->获取返回值
# map 不需要result() 来获取结果

    #for 循环型
    lis = []
    for i in range(5):
        ret = t.submit(func,i)#   --->func 直接运行吗
        lis.append(ret)
    t.shutdown()  #用shutdown  主线程就会等线程执行完了在执行
    for i in lis:
        print(i.result()) #获取  func 函数返回值
    print('主线程')

#回调函数
import time
import random
from concurrent.futures import ThreadPoolExecutor
def back(ret):
    print(ret)#打印的是对象
    print(ret.result())  #打印的是结果son_func的reurn i+i
def son_func():
    time.sleep(random.random())
    return i+i
p = ThreadPoolExecutor(4)
for i in range(6): #线程池提交6个任务,分别执行son_func函数
    ret = p.submit(son_func,i)#将son_func的返回值 作为回调函数 赋值给ret
    ret.add_done_callback(back)#回调back 把ret 对象传回去 ---.>back(ret)

##############线程池\进程池的爬虫
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
from multiprocessing import Pool
import requests
import json
import os

def get_page(url):
    print('<进程%s> get %s' %(os.getpid(),url))
    respone=requests.get(url)
    if respone.status_code == 200:
        return {'url':url,'text':respone.text}

def parse_page(res):
    res=res.result()
    print('<进程%s> parse %s' %(os.getpid(),res['url']))
    parse_res='url:<%s> size:[%s]\n' %(res['url'],len(res['text']))
    with open('db.txt','a') as f:
        f.write(parse_res)


if __name__ == '__main__':
    urls=[
        'https://www.baidu.com',
        'https://www.python.org',
        'https://www.openstack.org',
        'https://help.github.com/',
        'http://www.sina.com.cn/'
    ]

    # p=Pool(3)
    # for url in urls:
    #     p.apply_async(get_page,args=(url,),callback=pasrse_page)
    # p.close()
    # p.join()

    p=ProcessPoolExecutor(3)
    for url in urls:
        # parse_page拿到的是一个future对象obj,需要用obj.result()拿到结果
        p.submit(get_page,url).add_done_callback(parse_page)





# /协程代码: 生成器本身就是一个协程
# 基于函数版的  yiled  协程
def consumer():
    while 1:
        goods = yield
        print(goods)
def producer():  #生产者
    c = consumer()
    next(c)
    for i in range(10):
        c.send('苹果%s'%i)
producer()

  #####基于协程的socket server
from gevent import monkey
monkey.patch_all()
import socket
import gevent

server = socket.socket()
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(('127.0.0.1',8888))
server.listen(5)
def func(conn):
    msg = conn.send(b'qwer')
    conn.recv(1024).decode('utf8')
    print(msg)
while 1:
    conn,_ = server.accept()
    g =gevent.spawn(func,conn)
#######一条线程的多协程运用
from threading import currentThread
from gevent import monkey
monkey.patch_all()
import time
import gevent   #内部使用了 greenlet 的切换机制  实现了io自动切换
def eat():
    print('start eating ',currentThread())
    time.sleep(1)
    print('eating finished')
def sleep():
    print('start sleeping ',currentThread())
    time.sleep(1)
    print('sleeping finshed')
l =[]
for i in range(5):
    g1 =gevent.spawn(eat)
    g2 =gevent.spawn(sleep)
    l.append(g1,)
    l.append(g2,)
gevent.joinall(l)


from gevent import monkey
monkey.patch_all()
import time
import gevent
def eat():
    print('start eating ')
    time.sleep(1)
    print('eating finished ')
def sleep():
    print('starr sleeping')
    time.sleep(1)
    print('sleeping finished')
g1 = gevent.spawn(eat)
g2 = gevent.spawn(sleep)
g1.join()
g2.join()

 



posted @ 2019-03-04 18:02  LmtMe  阅读(153)  评论(0编辑  收藏  举报