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()

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

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

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • booth算法原理的简单化理解「建议收藏」

    booth算法原理的简单化理解「建议收藏」最近,在学习带符号二进制数乘法(multiplicationof signednumbers)时接触到了布思算法(boothalgorithm)。由于是第一次接触,对于其原理却一无所知,书上的解释以及网上的文章不知是自己才疏学浅还本来就是泛泛而谈,没有让我了解其本质。经过长时间的思考分析,最终找到了一种比较简单的理解方法。举一个简单的例子,比如说计算10100001×00111110,

    2022年10月23日
  • VSCode安装教程(超详细)[通俗易懂]

    VSCode安装教程(超详细)[通俗易懂]VSCode安装教程(超详细)下载安装一、同意协议(废话了我)二、选择合适的安装位置,下一步三、下一步四、这里注意下,进行相关的选择五、点击安装六、等待安装完成,很快配置中文界面上面安装完成后会出现下面的界面,我们搜索Chinese,点击install然后Restart重启后就ok了,中文界面下载下载地址:DownloadVisualStudioCode选择相应的版本下载。安装跟着图一步步走,简单明了。一、同意协议(废话了我)二、选择合适的安装位置,下一步三、下一步四

  • PHP获取客户端IP地址方式[通俗易懂]

    PHP获取客户端IP地址方式[通俗易懂]一、如果没有使用代理服务器REMOTE_ADDR=客户端IPHTTP_X_FORWARDED_FOR=没数值或不显示$ip=$_SERVER[‘REMOTE_ADDR’];二、使用透明代理REMOTE_ADDR=最后一个代理服务器IPHTTP_X_FORWARDED_FOR=客户端真实IP(经过多个代理服务器时,这个值类似:221.5.252….

    2022年10月26日
  • 如何创建属于自己的博客!「建议收藏」

    如何创建属于自己的博客!「建议收藏」如何创建属于自己的博客!首先,我们需要一些创建博客必须的工具,如下:1. 云虚拟主机+数据库。如果把创建网站比作盖房子的话,主机与数据库就相当于地基,是整个网站的基本,地基稳固,我们的房子

  • 攻击技术

    攻击技术

  • 浅谈数字音视频传输网络——AVB[通俗易懂]

    AVB有两种流格式:AM824和AAF。AM824支持24bit音频,iec60958音频编码(SPDIF和AES3),SMPTE时间码和MIDI。对于发送端AM824有三个选项“non-blocking(sync)”、“non-blocking(aync)”和“blocking”。流量整形是为了避免在以太网中发生丢弃数据的情况,通常采用漏桶算法(LeakyBucket)来完成流量整形或速率限制(RateLimiting)。它的主要目的是控制数据注入到网络的速率,平滑网络上的突发流量。

发表回复

您的电子邮箱地址不会被公开。

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