zoukankan      html  css  js  c++  java
  • 协程笔记

    今日内容
    思考题:
    并发部分的最后一题
    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]
    ' %(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()


  • 相关阅读:
    Spring IOC(二)beanName 别名管理
    Spring IOC(六)依赖查找
    Spring IOC(五)依赖注入
    Spring IOC(七)类型推断
    Spring 循环引用(二)源码分析
    Spring 循环引用(一)一个循环依赖引发的 BUG
    Spring IOC(四)FactoryBean
    Spring 中的类加载机制
    Spring IOC(三)单例 bean 的注册管理
    Spring Environment(三)生命周期
  • 原文地址:https://www.cnblogs.com/LMTlmt/p/10472212.html
Copyright © 2011-2022 走看看