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


相关推荐

  • 2026年武汉GEO监测系统实力测评:从技术到效果的4大核心维度选型指南

    2026年武汉GEO监测系统实力测评:从技术到效果的4大核心维度选型指南

    2026年3月16日
    3
  • Linux安装Tomcat9[通俗易懂]

    Linux安装Tomcat9[通俗易懂]Linux安装Tomcat91:下载安装包安装完jdk,我们需要下载tomcat了,点我跳转,这样子我们找到BinaryDistributions,下载链接.tar.gz(http://mirrors.cnnic.cn/apache/tomcat/tomcat-9/v9.0.0.M9/bin/apache-tomcat-9.0.0.M9.tar.gz)下载可以用wget

    2022年5月21日
    48
  • 知识管理工具有哪些_效率管理app

    知识管理工具有哪些_效率管理app转载自:http://xbeta.info/pkm2.htmPKM2:优秀的个人知识管理工具目录:1.前言2.推荐PKM2的6条理由3.不足4.官方介绍附:作者留言1.前言一直想把PKM2

    2022年8月2日
    12
  • javascript运动系列第二篇——变速运动

    javascript运动系列第二篇——变速运动原文地址 http www cnblogs com xiaohuochai p 5973556 html 前面的话 前面介绍过匀速运动的实现及注意事项 本文在匀速运动的基础上 更进一步 实现各种变速运动 包括加速运动 减速运动 缓冲运动 重力运动和弹性运动 nbsp 准备工作匀速运动 在原生 javascript 中实现运动的主要工具是定时器 通过设置固定的间隔时间 使元素在确定的间隔时间内实

    2026年3月19日
    3
  • 关于ASP.Net的validateRequest=false(验证请求)[通俗易懂]

    关于ASP.Net的validateRequest=false(验证请求)[通俗易懂]ASP.Net的validateRequest=false      validateRequest=”false”  指是否要IIS验证页面提交的非法字符,比如:>,   ASP.Net1.1后引入了对提交表单自动检查是否存在XSS(跨站脚本攻击)的能力。当用户试图用之类的输入影响页面返回结果的时候,ASP.Net的引擎会引发一个HttpReques

    2022年6月4日
    35
  • 你是一个合格的孩子吗?

    你是一个合格的孩子吗?最近在优酷上看到两个视频感触很深,拿来和大家一起分享一下。第一个是《天堂的午餐》  视频虽然只有短短的六分钟,也许会改变你的一生,故事讲了一个儿子为辛苦了一辈子的母亲做了一顿丰盛的午餐,但这顿午餐却成了母亲的天堂的午餐,就在母亲到家准备进家门时突然晕倒了,及时送到医院后抢救无效去世,母亲没有吃上儿子给她做的饭就去世了,视频中母亲问:我多会能吃上一顿你给我做的饭呢?儿子回答说…

    2025年11月7日
    5

发表回复

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

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