python 实现协程 提高效率

python 实现协程 提高效率

协程的概念
其实在操作系统中并没有协程的概念,协程的出现为的是解决单线程后者单进程下实现并发的效果。使用方式:操作系统无法感知单线程中的协程之间的切换。

实现协程的必备条件:
基于多道技术,我们知道了线程间的切换需要实现空间和时间上的复用,即:“保存状态+切换”
当进程或者线程间遇到I/O阻塞时进行切换才是有意义的;如果遇到计算型时还切换,只会增加消耗切换时间。

协程可以减少了CPU的切换,使得运行效率大大调高,但是协程也有协程的确定,如果切换的位置设置不准确也会导致运行的效率减低。(欺骗CPU,使得CPU一直处于运行状态,遇到I/O操作就切换)

#1 yiled可以保存状态(生成器),yield的状态保存与操作系统的保存线程状态很像,但是yield是代码级别控制的,更轻量级
#2 send可以把一个函数的结果传给另外一个函数,以此实现单线程内程序之间的切换 

单纯的切换并没有能够提高运行效率

1、协程:
单线程实现并发
在应用程序里控制多个任务的切换+保存状态
优点:
应用程序级别速度要远远高于操作系统的切换
缺点:
多个任务一旦有一个阻塞没有切,整个线程都阻塞在原地
该线程内的其他的任务都不能执行了

    一旦引入协程,就需要检测单线程下所有的IO行为,
    实现遇到IO就切换,少一个都不行,以为一旦一个任务阻塞了,整个线程就阻塞了,
    其他的任务即便是可以计算,但是也无法运行了

2、协程序的目的:
想要在单线程下实现并发
并发指的是多个任务看起来是同时运行的
并发=切换+保存状态


#串行执行
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)         


#基于yield并发执行(使用生成器进行模拟协程的程序的切换)
import time
def func1():
    while True:
        yield

def func2():
    g=func1()
    for i in range(10000000):
        i+1
        next(g)


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

总结:对于计算型的单进程,还是直接使用单进程进行运行效率会更好。

对于I/O型进程

import time
def func1():
    while True:
        print('func1')
        yield

def func2():
    g=func1()
    for i in range(10000000):
        i+1
        next(g)
        time.sleep(3)
        print('func2')
start=time.time()
func2()
stop=time.time()
print(stop-start)

总结:yield 生成器没有办法检测到I/O操作,只会在设定的位置进行切换。这样子运行效率并没有多大的提升。

真正意义上的协程

from gevent import monkey;monkey.patch_all()

import gevent
import time
def eat():
    print('eat food 1')
    time.sleep(2)
    print('eat food 2')

def play():
    print('play 1')
    time.sleep(1)
    print('play 2')

g1=gevent.spawn(eat)
g2=gevent.spawn(play_phone)
gevent.joinall([g1,g2])
print('主')

使用 gevent 模块进行I/O切换时候,可以自动检测到I/O操作,自动进行切换(实现协程)。

协程在爬虫方面的应用(单个进程实现)

from gevent import monkey;monkey.patch_all()
import gevent
import requests
import time

def get_page(url):
    print('GET: %s' %url)
    # 因为发起请求都是在等待服务器的回应
    response=requests.get(url)
    if response.status_code == 200:
        print('%d bytes received from %s' %(len(response.text),url))


start_time=time.time()
gevent.joinall([
    gevent.spawn(get_page,'https://www.python.org/'),
    gevent.spawn(get_page,'https://www.yahoo.com/'),
    gevent.spawn(get_page,'https://github.com/'),
])
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))

总结:使用协程实现多个请求,检测I/O型切换进程。

协程在socket通信中的应用

服务端

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

#如果不想用money.patch_all()打补丁,可以用gevent自带的socket
# from gevent import socket
# s=socket.socket()

def server(server_ip,port):
    s=socket(AF_INET,SOCK_STREAM)
    s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    s.bind((server_ip,port))
    s.listen(5)
    while True:
        conn,addr=s.accept()
        gevent.spawn(talk,conn,addr)

def talk(conn,addr):
    try:
        while True:
            res=conn.recv(1024)
            print('client %s:%s msg: %s' %(addr[0],addr[1],res))
            conn.send(res.upper())
    except Exception as e:
        print(e)
    finally:
        conn.close()

if __name__ == '__main__':
    server('127.0.0.1',8080)

客户端

#_*_coding:utf-8_*_

from socket import *

client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))


while True:
    msg=input('>>: ').strip()
    if not msg:continue

    client.send(msg.encode('utf-8'))
    msg=client.recv(1024)
    print(msg.decode('utf-8'))

多线程并发的客户端

from threading import Thread
from socket import *
import threading

def client(server_ip,port):
    c=socket(AF_INET,SOCK_STREAM) #套接字对象一定要加到函数内,即局部名称空间内,放在函数外则被所有线程共享,则大家公用一个套接字对象,那么客户端端口永远一样了
    c.connect((server_ip,port))

    count=0
    while True:
        c.send(('%s say hello %s' %(threading.current_thread().getName(),count)).encode('utf-8'))
        msg=c.recv(1024)
        print(msg.decode('utf-8'))
        count+=1
if __name__ == '__main__':
    for i in range(500):
        t=Thread(target=client,args=('127.0.0.1',8080))
        t.start()

协程的实现有优点也有缺点。
注意取舍。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/115129.html原文链接:https://javaforall.net

(0)
上一篇 2021年11月11日 下午3:00
下一篇 2021年11月11日 下午4:00


相关推荐

  • js垃圾处理机制_java中垃圾回收有什么目的

    js垃圾处理机制_java中垃圾回收有什么目的文章目录前置知识堆栈栈堆执行上下文与作用域链执行上下文作用域链一、JavaScript中怎么被定义为垃圾使用局部变量使用对象概括二、两种回收策略标记清理引用计数概括三、什么时候执行垃圾回收关于ChromeV8引擎的GC分代回收指针与活跃对象的区分回收的执行周期四、内存问题五、Es6WeakMap参考文章前置知识堆栈栈什么是栈栈其实是一种数据结构,有着先进后出,后进先出的特性,用生活中的事物来理解最形象的就是汉诺塔了。我们在栈中存储的数据就像汉诺塔的盘子一样,最先放进去在最下面,最后放入的盘.

    2022年10月9日
    4
  • 网站10大常见安全漏洞及解决方案

    网站10大常见安全漏洞及解决方案

    2021年10月31日
    47
  • Kettle工具的基本使用[通俗易懂]

    Kettle工具的基本使用[通俗易懂]2.1Kettle简介2.1.1Kettle概述Kettle是国外免费的开源轻量级ETL工具,是基于Java语言开发的,可以在Windows.Linux,UNIX系统上运行,且绿色不需安装,可用于各种数据库之间的连接。Kettle工具主要有四个组件组成,分别是Spoon,Pan,Kitchen以及Carte组件,具体功能如下:*Spoon为集成开发软件,用于构建作业和转换,执行或调试作业和转换,还可以用于监控ETL操作性能。*Pan以命令行形式执行Spoon生成的转…

    2022年10月16日
    6
  • 防火墙透明模式和路由模式区别_防火墙的部署模式

    防火墙透明模式和路由模式区别_防火墙的部署模式防火墙能够工作在三种模式下:路由模式、透明模式、混合模式。如果防火墙以第三层对外连接(接口具有IP地址),则认为防火墙工作在路由模式下;若防火墙通过第二层对外连接(接口无IP地址),则防火墙工作在透明模式下;若防火墙同时具有工作在路由模式和透明模式的接口(某些接口具有IP地址,某些接口无IP地址),则防火墙工作在混合模式下。防火墙三种工作模式的简介1、路由模式当防火墙位于内部网络和外部网络之间时,需要将防火墙与内部网络、外部网络以及DMZ三个区域相连的接口分别配置成不同网段的IP地址

    2025年8月10日
    3
  • HBase Shell命令大全「建议收藏」

    HBase Shell命令大全「建议收藏」HBase关键名称:RowKey列族columnfamily单元Cell时间戳timestampHBaseShell是官方提供的一组命令,用于操作HBase。如果配置了HBase的环境变量了,就可以知己在命令行中输入hbaseshell命令进入命令行。hbaseshellhelp命令可以通过help’命名名称’来查看命令行的具体使用查询服务器状态st…

    2022年7月16日
    18
  • BM3D算法学习

    BM3D算法学习来源:BM3D算法学习-知乎(zhihu.com)作者:爱酷的胡巴前些日子在学习图像降噪的算法,自然而然的发现了这篇里程碑式的作品,“BM3D”3D块匹配降噪算法,想来时间也久,赶紧再写下来,以免过后忘记。在学习的过程中,由于没学过数字图像处理,学起来还是挺墨迹的,前前后后得有四五天吧,才算整个大差不差,期间看了许多前辈的博客和代码,也总算有些许的进步和理解,特此感…

    2022年5月22日
    77

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号