libco源码阅读(一):Hello World

1、协程是什么

   1.1 进程

   1.2 线程

   1.3 协程

2、libco库跑hello world


1、协程是什么

    在了解协程之前,需要对比一下进程、线程和协程的区别:

        

1.1 进程

    进程可以理解为资源分配的基本单位,操作系统以一个进程为基本单位,分配系统资源,包括内存资源和CPU的时间片资源。进程的切换者是操作系统,切换时机是根据操作系统自己的切换策略,用户是无感知的。进程的切换内容包括页全局目录、内核栈、硬件上下文,切换内容保存在内存中。进程切换过程是由“用户态到内核态到用户态”的方式,切换效率低。一个进程有自己独立的地址空间,包括:

  • 数据段
  • 代码段
  • 文件

  一个进程里面可以包含多个线程。

1.2 线程

    线程是CPU调度的基本单位,线程之间的切换需要从用户态切换到内核态,由CPU进行调度。线程的切换者是操作系统,切换时机是根据操作系统自己的切换策略,用户无感知。线程的切换内容包括内核栈和硬件上下文。线程切换内容保存在内核栈中。线程切换过程是由“用户态到内核态到用户态”, 切换效率中等。一个进程内的所有线程共享进程的资源,但是线程也有自己独立的资源,这些资源有:

  • 线程ID每个线程都有自己的线程ID,这个ID在本进程中是唯一的。进程用此来标识线程。
  • 寄存器组的值:由于线程间是并发运行的,每个线程有自己不同的运行线索,当从一个线程切换到另一个线程上时,必须将原有的线程的寄存器集合的状态保存,以便将来该线程在被重新切换到时能得以恢复。
  • 线程的堆栈堆栈是保证线程独立运行所必须的。线程函数可以调用函数,而被调用函数中又是可以层层嵌套的,所以线程必须拥有自己的函数堆栈,使得函数调用可以正常执行,不受其他线程的影响。
  • 错误返回码由于同一个进程中有很多个线程在同时运行,可能某个线程进行系统调用后设置了errno值,而在该线程还没有处理这个错误,另外一个线程就在此时被调度器投入运行,这样错误值就有可能被修改。所以不同线程应该有自己的错误返回码变量。
  • 线程的信号屏蔽码由于每个线程所感兴趣的信号不同,所以线程的信号屏蔽码应该由线程自己管理。但所有的线程都共享同样的信号处理器。
  • 线程的优先级由于线程需要像进程那样能够被调度,那么就必须要有可供调度使用的参数,这个参数就是线程的优先级。

1.3 协程

   协程,可以理解为用户态的线程,即一个线程可以包含多个协程,这些协程由用户创建,并且由用户控制进行协程的调度。协程的切换者是用户(编程者或应用程序),切换时机是用户自己的程序所决定的。协程的切换内容是硬件上下文,切换内存保存在用户自己的变量(用户栈或堆)中。协程的切换过程只有用户态,即没有陷入内核态,因此切换效率高。一个线程内的多个协程虽然可以切换,但是多个协程是串行执行的,只能在一个线程内运行,没法利用CPU多核能力。线程里面的协程对CPU而言是不能感知的,CPU只会感知到一个线程在运行。协程所拥有的资源主要有:

  • 协程的上下文环境,即各种寄存器的值
  • 协程拥有的堆栈

2、libco库跑hello world

    libco是微信后台大规模使用的c/c++协程库,2013年至今稳定运行在微信后台的数万台机器上。libco通过仅有的几个函数接口 co_create/co_resume/co_yield 再配合 co_poll,可以支持同步或者异步的写法,如线程库一样轻松。同时库里面提供了socket族函数的hook,使得后台逻辑服务几乎不用修改逻辑代码就可以完成异步化改造。

    Github地址:https://github.com/Tencent/libco.

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <queue>
#include "co_routine.h"
using namespace std;
struct stTask_t
{
	int id;
};
struct stEnv_t
{
	stCoCond_t* cond;
	queue<stTask_t*> task_queue;
};
void* Producer(void* args)
{
	co_enable_hook_sys();
	stEnv_t* env=  (stEnv_t*)args;
	int id = 0;
	while (true)
	{
		stTask_t* task = (stTask_t*)calloc(1, sizeof(stTask_t));
		task->id = id++;
		env->task_queue.push(task);
		printf("%s:%d produce task %d\n", __func__, __LINE__, task->id);
		co_cond_signal(env->cond);
		poll(NULL, 0, 1000);
	}
	return NULL;
}
void* Consumer(void* args)
{
	co_enable_hook_sys();
	stEnv_t* env = (stEnv_t*)args;
	while (true)
	{
		if (env->task_queue.empty())
		{
			co_cond_timedwait(env->cond, -1);
			continue;
		}
		stTask_t* task = env->task_queue.front();
		env->task_queue.pop();
		printf("%s:%d consume task %d\n", __func__, __LINE__, task->id);
		free(task);
	}
	return NULL;
}

int main()
{
	stEnv_t* env = new stEnv_t;
	env->cond = co_cond_alloc();

	stCoRoutine_t* consumer_routine;
	co_create(&consumer_routine, NULL, Consumer, env);
	co_resume(consumer_routine);

	stCoRoutine_t* producer_routine;
	co_create(&producer_routine, NULL, Producer, env);
	co_resume(producer_routine);
	
	co_eventloop(co_get_epoll_ct(), NULL, NULL);
	return 0;
}

     这是一个生产者-消费者示例。在这个示例中,主要有两个函数:

  • 生产者函数:Producer负责将任务放入任务队列之中
  • 消费者函数:Consumer负责从任务队列中取任务来执行

   main函数中,分别为这两个函数创建两个协程进行来执行,这里主要用到了libco的两个接口,分别是co_create()co_resume(),co_create()负责创建一个协程,而co_resume()负责启动一个协程。这里协程的创建和线程的创建有很大不同,线程创建时可以直接运行,而协程创建后并没有运行,而是需要调另外一个函数才能运行。

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值