十四、协程

协程

协程:是单线程下的并发,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。

'''
进程:资源单位
线程:执行单位
协程:这个概念完全是程序员自己意淫出来的,根本不存在

协程思想:单线程下实现并发
	程序员自己在代码层面上监测所有的IO操作
	一旦遇到IO操作,我们就在代码层面完成切换
	这样给CPU的感觉是你这个程序一致在运行,没有IO
	从而提升程序的运行效率
	
	
如何做到切换+保存状态?

切换:
	遇到IO就切,提升效率

保存状态
	保存上一次执行的状态,下一次接着上一次的操作继续往后执行,使用yield关键字
'''

需要注意的是:

#1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)

#2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)

优点如下:

#1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
#2. 单线程内就可以实现并发的效果,最大限度地利用cpu

缺点如下:

#1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
#2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程

总结协程特点:

  1. 必须在只有一个单线程里实现并发
  2. 修改共享数据不需加锁
  3. 用户程序里自己保存多个控制流的上下文栈
  4. 附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))

一、gevent模块(了解)

注意:

'''
gevent模块本身无法监测常见的一些io操作
在使用的时候需要额外的导入一句话(monkey补丁):
    from gevent import monkey
    monkey.patch_all()
又由于上面的两句指令,在使用gevent模块的时候是必须导入的,所以支持如下简写:
    from gevent import monkey;monkey.patch_all()
'''

1、安装gevent模块

pip3 intsall gevent

2、spawn()

1.spawn()方法是一个异步方法,即使用该方法的时候,主线程依然在运行

2.spawn()的返回值是一个Greenlet类,该类中存在join方法,可以阻塞主线程,等待其他子线程执行结束

  1. 语法:
from gevent import spawn
from gevent import monkey;monkey.patch_all()

'''
gevent模块本身无法监测常见的一些io操作
在使用的时候需要额外的导入一句话(monkey补丁):
    from gevent import monkey
    monkey.patch_all()
又由于上面的两句指令,在使用gevent模块的时候是必须导入的,所以支持如下简写:
    from gevent import monkey;monkey.patch_all()
'''

g = spawn(fn, *args)

'''
参数介绍:
	fn:填入想要被监测IO操作的函数名,一旦该函数内执行了IO操作,则立马切换CPU资源
	*args:即填入fn函数需要的参数

返回值:
	spawn的返回值是一个Greenlet类,可以调用该类中的join方法,实现阻塞主线程,等待其他子线程执行结束
	<class 'gevent._gevent_cgreenlet.Greenlet'>
'''

二、greenlet模块

greenlet模块并没有自动检测IO操作的功能,它一般是配合yield实现IO检测,这种方法效率极低

  1. 语法:
from greenlet import greenlet

g = greelet(fn)

'''
参数介绍:
	fn:表示需要进行切换操作的函数名,即与该函数绑定
'''

1、安装greenlet模块

pip3 install greenlet

2、switch()

调用switch()方法,则进行CPU资源切换

  1. 语法:
from greenlet import greenlet

g = greelet(fn)

g.switch(obj)

'''
参数介绍:
	obj:传入给g变量所绑定的fn函数参数
		可以在第一次switch时传入参数,以后默认值则为第一次switch传入的参数
'''

三、协程实现TCP服务端并发

服务器

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


# 通信循环
def talk(a):
    while True:
        try:
            client_data = a.recv(1024).decode('utf8')
            if len(client_data) == 0:
                break
            print(client_data)
            a.send(client_data.upper().encode('utf8'))
        except ConnectionError as e:
            print(e)
            break
    a.close()


def server(ip, port):
    s = socket.socket()
    s.bind((ip, port))
    s.listen(128)
    while True:
        a, b = s.accept()
        spawn(talk, a)


if __name__ == '__main__':
    g1 = spawn(server, '127.0.0.1', 8000)
    g1.join()
posted @ 2021-06-04 13:55  zzwYYYYYY  阅读(55)  评论(0)    收藏  举报