协程学习:基本概念(一)

点击打开链接

协程的定义 
根据维基百科的描述,协程是一种程序组件,与子例程一样,但是协程更为一般和灵活,但在实践中使用没有子例程那样广泛。 
https://zh.wikipedia.org/wiki/%E5%8D%8F%E7%A8%8B 
其实这个概念不容易理解,可以先忽略,先看看协程到底是干什么用的。

协程究竟是什么呢?有什么用呢? 
协程其实可以理解为是“用户态”的多线程。在多线程的模型中,操作系统会根据某种调度算法不断地切换当前正在运行的线程,由于每个线程都有自己的栈,因此在切换线程的过程中需要上下文的切换,这样会导致大量的开销,如果系统中多大量的线程,那么系统的资源就会被上下文的切换大量的消耗,导致性能的下降。协程就是用来解决这个问题的(当然还有其他优点),协程运行在同一个线程上,没有上下文的切换。和线程不同,协程可以有多个入口点,可以在指定的位置挂起和回复执行(线程只有一个入口点且只返回一次)。

下面通过生产者-消费者模型来描述下协程的作用。

在传统的多线程模型中,一般会建立一个生产者线程,和一个消费者线程,请看下面的代码(伪代码)。

Queue q; // 全局变量,用于保存消息,假设Queue是线程安全的

void ProduceThread()   // 生产者线程
{
    i = 0;
    while(true)
    {
        if (!q.IsFull())
        {
           q.push(i);
           ++i;
        }
    }
}        

void ConsumeThread()   // 消费者线程
{
    i = 0;
    while(true)
    {
        if (!q.IsEmpty())
        {
           print q.get(0);  // 打印第一个元素
           q.pop(0);        // 从消息队列中删除
        }
    }
}

int main()
{
    Thread t1 = new Thread(ProceThread);
    Thread t2 = new Thread(ConsumeThread);
    t1.start();
    t2.start();

    Sleep(100s);

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

采用协程的方式,代码实现如下(伪代码):

Queue q; // 全局变量,用于保存消息,假设Queue是线程安全的

void ProduceFunc()   // 生产者
{
    i = 0;
    while(true)
    {
        if (!q.IsFull())
        {
           q.push(i);
           ++i;
        }
        resume(ConsumeFunc); // 执行消费者函数
    }
}

void ConsumeFunc()   // 消费者
{
    i = 0;
    while(true)
    {
        if (!q.IsEmpty())
        {
           print q.get(0);  // 打印第一个元素
           q.pop(0);        // 从消息队列中删除
        }
        yield;   // 主动让出CPU,让主线程可以继续执行
    }
}

int main()
{
    ProduceFunc();

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

第一看看上去两段代码没有区别啊?其实在多线程的模式中,有可能是生产者生产了多个,消费者才进行消费,也就是有可能出现这样的情况,队列q中生产了1,2,3个元素,但是消费这才开始打印1,这个就和多线程的调度有关了,哪个线程获得了CPU的资源就可以执行(一般来说,操作系统的这个调度是尽可能公平的,也就是生产者和消费者获得执行的几率是差不多的)。但是这个调度是有操作系统进行的。接着再看协程的实现,resume作用就是恢复上次执行的函数,yield的作用是主动让出CPU资源。在上述例子中,会先执行ProduceFunc,然后生产1放在队列q中,这个时候ProduceFunc会恢复ConsumeFunc,让它继续执行(因为ConsumeFunc 还没执行过,那么会从函数的第一行开始执行,这是第一个入口),然后ConsumeFunc会消费队列q中的1,并且通过yield让出CPU,让ProduceFunc继续执行,然后ProduceFunc再次resume的时候ConsumeFunc会从上次yield的地方继续执行(这就是第二个入口了,也就是协程会有多个入口点)。 
协程实现的效果和多线程类似,所以也可以成为“用户态”的多线程,但是协程不存在上下文的切换,而协程的调度是需要开发者自己来控制的。

其实,上面协程的列子,实现的效果是生产者生成1个消息,然后就会让消费者消费1个消息,那这个不是和同步执行差不多吗?那还不如直接使用同步执行的代码?但是,如果消费者还要等待异步的消息呢?比如要像DB获取一些数据呢?这个例子其实还没真正提现出协程的作用,后续我会继续举出更能体现协程优点的例子。

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/houzhuoming1/article/details/52791810

猜你喜欢

转载自blog.csdn.net/libaineu2004/article/details/80576372
今日推荐