协程,进程,线程 一家人就要整整齐齐

425 阅读5分钟

前几天在开会的时候,部门大佬回溯了一下上周末项目出现的一个问题,在面对大量的http请求的时候,之前的做法是开多进程多线程去分布式接受然后响应,但是由于没有预估好请求量级,导致大量的请求被堵住,影响了线上的业务。

解决方案:当然多架点服务器,多开点进程线程也能缓解需求,但是为了偶尔的峰值来架多服务器有点浪费~

于是大佬就提出一个东西,协程,说实话,我还没听说过这个东西,之前就知道进程,线程,协程是什么东东。。。

后来开完会我就自己去研究了一下,分享点学习体会给大家

一、进程,线程,协程

进程是应用程序的启动实例,进程拥有代码和打开的文件资源、数据资源、独立的内存空间。

线程从属于进程,是程序的实际执行者,一个进程至少包含一个主线程,也可以有更多的子线程,线程拥有自己的栈空间。

协程(Coroutines)是一种比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程。

简单的理解就是: 进程是一个厂,有空间,有各条工作流水线。 线程是一个个的工作流水线,线程之间可以进行工作交流。 协程就是工作人员,只在某条流水线上工作。 协程的工作是这样的,在整条流水线上,有多个工作要做,例如有传送带跟包装带,工作人员在等传送带过来的时候可以进行包装工作,但是等传送带过来的时候就又回去传送带工作。也就是两个函数之间可以交替执行,在某个函数进行等待的时候,可以先去执行另外一个函数。

上面说到的项目遇到的问题,就是线程在执行接受 -> 响应是一步操作,只会等到响应之后再进行下一个消息的接受处理,如果是协程的话可以做到当接受完消息A 等待响应的时候 先进行接受消息B。这样效率就会提高很多。

二、yield 使用

代码借鉴来自https://www.liaoxuefeng.com/wiki/897692888725344/923057403198272

import time
def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print('[CONSUMER] Consuming %s...' % n)
        time.sleep(5)
        r = '200 OK'

def produce(c):
    c.next()
    n = 0
    while n < 5:
        n = n + 1
        print('[PRODUCER] Producing %s...' % n)
        r = c.send(n)
        print('[PRODUCER] Consumer return: %s' % r)
    c.close()

python协程的实现主要依赖于yield,上面是两个简单的例子,一个生产者,一个消费者。 当生产者生产消息的时候,会同过send发送给消费者进行消费,消费者消费完再同个yield返回结果给生产者,生产者再进行下个消息的生产。 执行结果如下

在这里插入图片描述

三、协程gevent 的使用

直接通过 pip 安装 pip install gevent

def play(name):
    for i in range(5):
        print('play {} {}'.format(name,i))
        time.sleep(1.5)

def play_1(name):
    for i in range(5):
        print('play_1 {} {}'.format(name ,i))
        time.sleep(3)
       
if __name__=='__main__':
    # con = consumer()
    # pro = produce(con)
    from gevent import monkey
    monkey.patch_all()
    g1 = gevent.spawn(play,'test1')
    g2 = gevent.spawn(play_1,'test2')
    gevent.joinall([g1, g2])

加入 monkey.patch_all() 是为了把标准库中的thread/socket等给替换掉.这样我们在后面使用socket的时候能够跟寻常一样使用,无需改动不论什么代码,可以把它变成非堵塞的了。 或者也可以手动导入gevent.socket。

在这里插入图片描述
可以看到执行结果,在函数sleep等待的时候会切换到play_1去执行,play_1sleep的时候又会切回到play接着执行。 这只是对协程最简单的应用,可以开N多个协程去同步执行这些函数,一旦有人在等待响应的时候就会自动去执行另外一个任务,这样可以大量节省时间。 但是要注意这里是当函数在等待响应的时候,也就是函数目前并没有消耗资源在进行中,而是仅仅在等待的时候才会切换到另外一个函数去执行。 测试例子,读一个大数据量的csv文件,看看在堵住的时候会不会执行另外一个函数

# 读一次这个文件大概要5s
def eat():
    for i in range(5):
        print('open_read {}'.format(i))
        df = pd.read_excel('test.xlsx')
        print('len ',len(df))
        time.sleep(2)

def play(name):
    for i in range(5):
        print('play {} {}'.format(name,i))
        time.sleep(1.5)

执行结果如下

在这里插入图片描述
可以看到在执行read_excel的5s中并不是函数在等待阶段,而是正在执行读的操作,因此不会执行函数play,只有读完之后sleep才是等待,这时候才会执行函数play。

四、总结

简单来说,一个进程内可以包含多个线程运行,如果是多核CPU的就支持多个线程并行,一个线程之内可以开多个协程,但是不管怎么样,多个协程之间是串行的,也就是不管什么时候,线程空间中只有一个协程函数在运行,其他函数则被挂起。

为什么协程效率这么高呢,主要是因为协程的切换是程序之间进行决定的,切换过程只有自己的程序中,这也是跟线程区别的地方,线程切换需要依赖操作系统进行。

我是一只前进的蚂蚁,希望能一起前行。

如果对您有一点帮助,一个赞就够了,感谢!

注:如果本篇博客有任何错误和建议,欢迎各位指出,不胜感激!!!