python关于协程coroutine

1.定义
协程, 又名微线程, coroutine
协程的特点在于执行子程序的过程中可以中断, 挂起去执行另一个子程序;看起来执行过程有点像多线程, 但不同的是协程是一个线程内部进行切换;多个线程相对独立, 有自己的上下文, 切换受系统控制; 而协程也相对独立, 有自己的上下文, 但切换由协程自己控制;

优点:1.由于协程是单线程执行, 切换只在程序内部, 没有系统级别的消耗, 相比线程和进程性能优势明显, 某种程度上可以实现并发;
2.单线程不需要多线程的同步锁等机制,执行效率比多线程要高;
3.多进程+协程既可利用多核又可以发挥协程的高效率;

python的实现:
1.python可以通过yield和使用简单实现线程, yield挂起程序, 然后可通过next, send等重新回到重新执行, 从而实现切换;
2.python中的asyncio模块可实现协程,此模块的使用后续再补充;
3.第三方库greenlet, gevent;

(1)greenlet
实例1:

from greenlet import greenlet


def test1(*args):
    print(123, args)
    a = c2.switch('shit')  # c2切换到c1后将附带的参数给a
    print(a)

def test2(*args):
    print(456, *args)
    c1.switch('happy')
    print('test2 ending')  # 不切换回来不执行

c1 = greenlet(test1)
c2 = greenlet(test2)

c1.switch(111, 222)  # 如果函数未启动,传入位置参数
print('main ending')

#result
123 (111, 222)
456 shit
happy
main ending

调用greenlet类实例化出一个协程对象, 然后协程对象的switch方法可以在不同协程间切换, 运行后回到主协程;switch方法可以传入位置参数和关键字参数, 如果协程未启动, 那将作为函数的参数输入, 如果协程已启动, 那将参数作为返回值传递到协程内部;
switch使用说明如下:

switch(*args, **kwargs)
Switch execution to this greenlet.
1) If this greenlet has never been run, then this greenlet
will be switched to using the body of self.run(*args, **kwargs).
2) If the greenlet is active (has been run, but was switch()'ed
out before leaving its run function), then this greenlet will
be resumed and the return value to its switch call will be
None if no arguments are given, the given argument if one
argument is given, or the args tuple and keyword args dict if
multiple arguments are given.
3) If the greenlet is dead, or is the current greenlet then this
function will simply return the arguments using the same rules as
above.

实例2:

from greenlet import greenlet


def test1(*args):
    print(123, args)
    a = c2.switch('shit')  # c2切换到c1后将附带的参数给a
    print(a)
    c2.switch()
    print('test1 ending')  # 子协程结束后回到父协程

def test2(*args):
    print(456, *args)
    c1.switch('happy')
    print('test2 ending')  # 不切换回来不执行


c1 = greenlet(test1)
c2 = greenlet(test2, c1)

c1.switch(111, 222)  # 如果函数未启动,传入位置参数
print('main ending')

#result
123 (111, 222)
456 shit
happy
test2 ending
test1 ending
main ending

实例化greentlet()有两个参数, 第二个参数可指定父协程, 当子协程运行结束后就算没有是switch也会回到父协程; 默认主协程为父协程;

(2)gevent
gevent通过greenlet实现协程, 区别是它遇到I/O阻塞时可以实现自动切换.
下面为简单使用实例1:

import gevent
import time

def test1(*args):
    print(123, args)
    gevent.sleep(2)  # time.sleep()并不会让协程切换?
    print('test1 ending')  # 子协程结束后回到父协程

def test2(*args):
    print(456, *args)
    time.sleep(1)
    print('test2 ending')


a = gevent.spawn(test1, 1, 2, 3)
b = gevent.spawn(test2, 666)
gevent.joinall([a, b])
print('main ending')  # 不加join直接结束,主协程不会等待

#result
123 (1, 2, 3)
456 666
test2 ending
test1 ending
main ending

使用实例2:利用gevent实现socket并发, 从中可看出gevent并不完善, 遇到IO阻塞切换后并不会切换回来,只有加了monkey.patch后才能正常使用; 在实例1中也一样,gevent.sleep()换成time.sleep()只会顺序运行, 不会切换

import socket
import gevent
import time
from gevent import monkey


def handle_msg(con):
    while True:
        try:
            print('aa')
            data = con.recv(1024)  # gevent运行到这里会一直阻塞然后切换,并不会切换回来, 只能加monkey.patch
        except Exception as e:
            print(e)
            break
        if not data: break
        try:
            print(data.decode('utf-8'))
            msg = input('发送:').encode('utf8')
            con.send(msg)
        except:
            print('hh')
            time.sleep(1)
            continue

gevent.monkey.patch_all()  # 运行中动态更改模块
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 9000))
s.listen(5)
while True:
    try:
        con, addr = s.accept()
        print(addr, '已连接')
        g = gevent.spawn(handle_msg, con)
    except Exception as e:
        print(e)
        continue
        
#client
import socket

c = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
c.connect(('127.0.0.1', 9000))
while True:
    msg = input('发送:')
    c.send(msg.encode('utf8'))
    data = c.recv(1024)
    print(data.decode('utf8'))
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值