python3协程的使用,yield、greenlet和gevent实现协程,及协程的一些使用环境

协程

协程是线程的更小切分,又称为“微线程”,是一种用户态的轻量级线程。
与进程的区别:
相同点:
相同点存在于,当我们挂起一个执行流的时,我们要保存的东西:
栈, 其实在你切换前你的局部变量,以及要函数的调用都需要保存,否则都无法恢复
寄存器状态,这个其实用于当你的执行流恢复后要做什么
而寄存器和栈的结合就可以理解为上下文,上下文切换的理解:
CPU看上去像是在并发的执行多个进程,这是通过处理器在进程之间切换来实现的,操作系统实现这种交错执行的机制称为上下文切换
操作系统保持跟踪进程运行所需的所有状态信息。这种状态,就是上下文。
在任何一个时刻,操作系统都只能执行一个进程代码,当操作系统决定把控制权从当前进程转移到某个新进程时,就会进行上下文切换,即保存当前进程的上下文,恢复新进程的上下文,然后将控制权传递到新进程,新进程就会从它上次停止的地方开始。
不同点:
执行流的调度者不同,进程是内核调度,而协程是在用户态调度,也就是说进程的上下文是在内核态保存恢复的,而协程是在用户态保存恢复的,很显然用户态的代价更低
进程会被强占,而协程不会,也就是说协程如果不主动让出CPU,那么其他的协程,就没有执行的机会。
对内存的占用不同,实际上协程可以只需要4K的栈就足够了,而进程占用的内存要大的多
从操作系统的角度讲,多协程的程序是单进程,单协程

与线程的区别:
既然我们上面也说了,协程也被称为微线程,下面对比一下协程和线程:
线程之间需要上下文切换成本相对协程来说是比较高的,尤其在开启线程较多时,但协程的切换成本非常低。
同样的线程的切换更多的是靠操作系统来控制,而协程的执行由我们自己控制。
  协程只是在单一的线程里不同的协程之间切换,其实和线程很像,线程是在一个进程下,不同的线程之间做切换,这也可能是协程称为微线程的原因吧。

协程的优点:
  (1)无需线程上下文切换的开销,协程避免了无意义的调度,由此可以提高性能(但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力)
  (2)无需原子操作锁定及同步的开销
  (3)方便切换控制流,简化编程模型
  (4)高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理
协程的缺点:
  (1)无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
  (2)进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

  • 协程,又称微线程,在不开辟多个线程的情况下,让多个任务交替执行 ,
  • 由于协程不会开辟多个线程,所以当一个线程下有多个协程时,同一时间只会有一个协程正在执行。

通过yield来实现协程

代码执行到yield会暂停,然后把结果返回出去,下次启动时会在暂停的位置继续往下执行

  • 每次启动都会返回一个值,多次启动可以返回多个值,也就是yield可以返回多个值
def work1():
    count = 0
    while True:
	    count += 1
	    print('work1',count)
	    yield

# 任务2
def work2():
	count2 = 0
	while True:
		count2 += 1
		print('任务2...', count2)
		time.sleep(0.2)
		yield

if __name__ == '__main__':
    # 创建协程
    g1 = work1()
    g2 = work2()
    # 启动协程
    while True:
        next(g1)
        time.sleep(0.5)
        next(g2)

可以看到work1和work2交替执行,每次执行到yield都会停止,调用next继续从yield开始执行

通过greenlet来实现协程

  • 为了更好使用协程来完成多任务,python中的greenlet模块对其封装,从而使得切换任务变的更加简单,greenlet相当于是线程/进程内的一种合理安排后的串行,通过合理的代码执行流程切换,完全避免了死锁和阻塞等情况,但这些切换、返回,需要程序员手动进行编写安排。
# # 携程-greenlet

import greenlet
import time


def work1():
	for i in range(10):
		print('work1')
		time.sleep(0.5)
		# 切换任务
		g2.switch()


def work2():
	for i in range(8):
		print('work2')
		time.sleep(0.5)
		# 任务切换
		g1.switch()

if __name__ == '__main__':

	g1 = greenlet.greenlet(work1)
	g2 = greenlet.greenlet(work2)
	# 可手动调整启动顺序
	# 一个 “greenlet” 是一个小型的独立伪线程。可以把它想像成一些栈帧,
	# 栈底是初始调用的函数,而栈顶是当前greenlet的暂停位置。
	# 你使用greenlet创建一堆这样的堆栈,然后在他们之间跳转执行。
	# 跳转必须显式声明的:一个greenlet必须选择要跳转到的另一个greenlet,这会让前一个挂起,而后一个在此前挂起处恢复执行。
	# 不同greenlets之间的跳转称为切换(switching) 
	# 先执行1
	g1.switch()
	g2.switch()

使用gevent实现协程

在 Python 里,按照官方解释 greenlet 是轻量级的并行编程,gevent 就是利用 greenlet 实现的基于协程的 python 的网络 library,通过使用greenlet提供了一个在libev事件循环顶部的高级别并发API。即 gevent 是对 greenlet 的高级封装。
gevent内部封装的greenlet,其原理是当一个greenlet遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。
由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。由于切换是在IO操作时自动完成,所以gevent需要修改Python自带的一些标准库,这一过程在启动时通过monkey patch完成。

# gevent
import greenlet
import gevent,time
from gevent import monkey
# 打补丁
monkey.patch_all()

# 使用gevent自带的方法
def work1():
	for i in range(8):
		print("看球")
		# time.sleep(0.5)
		gevent.sleep(0.5)
def work2():
	for i in range(10):
		print('吃饭')
		# time.sleep(0.5)
		gevent.sleep(0.5)


monkey.patch_all()
# 使用monkey 改写
def work3():
	for i in range(5):
		print("玩手机")
		time.sleep(1)

def work4():
	for i in range(5):
		print("看书")
		time.sleep(1)
爬虫异步IO阻塞切换:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2018/1/24 17:00
# @Author  : Py.qi
# @File    : gevent_urllib.py
# @Software: PyCharm

from urllib import request
import gevent,time
from gevent import monkey

monkey.patch_all()   #将程序中所有IO操作做上标记使程序非阻塞状态
def url_request(url):
    print('get:%s'%url)
    resp = request.urlopen(url)
    data = resp.read()
    print('%s bytes received from %s'%(len(data),url))

async_time_start = time.time() #开始时间
gevent.joinall([
    gevent.spawn(url_request,'https://www.python.org/'),
    gevent.spawn(url_request,'https://www.nginx.org/'),
    gevent.spawn(url_request,'https://www.ibm.com'),
])
print('haoshi:',time.time()-async_time_start) #总用时
协程实现多并发链接socket通信:
import socket,gevent
from gevent import monkey
monkey.patch_all()

def server_sock(port):
    s = socket.socket()
    s.bind(('',port))
    s.listen(10)
    while True:
        conn,addr = s.accept()
        gevent.spawn(handle_request,conn)
def handle_request(conn):
    try:
        while True:
            data = conn.recv(1024)
            if not data: conn.shutdown(socket.SHUT_WR)
            print('recv:',data.decode())
            conn.send(data)
    except Exception as ex:
        print(ex)
    finally:
        conn.close()

if __name__ == '__main__':
    server_sock(8888)


import socket

HOST = 'localhost'  # The remote host
PORT = 8888  # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
while True:
    #msg = bytes(input(">>:"), encoding="utf8")
    for i in range(50):
        s.send('dddd'.encode())
        data = s.recv(1024)
    # print(data)

        print('Received', repr(data))
    s.close()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值