libco source code reading (1): Hello World

1. What is a coroutine

   1.1 Process

   1.2 Thread

   1.3 Coroutine

2、libco库跑hello world


1. What is a coroutine

    Before understanding the coroutine, you need to compare the differences between processes, threads and coroutines:

        

1.1 Process

    A process can be understood as the basic unit of resource allocation. The operating system uses a process as the basic unit to allocate system resources, including memory resources and CPU time slice resources. The process switcher is the operating system, and the switching timing is based on the operating system's own switching strategy, and the user is unaware. The switching content of the process includes the page global directory, the kernel stack, and the hardware context. The switching content is stored in the memory. The process switching process is from "user mode to kernel mode to user mode", and the switching efficiency is low. A process has its own independent address space, including:

  • Data segment
  • Code snippet
  • heap
  • Stack
  • file

  A process can contain multiple threads.

1.2 Thread

    Thread is the basic unit of CPU scheduling. Switching between threads needs to switch from user mode to kernel mode, which is scheduled by the CPU. The thread switcher is the operating system, and the switching timing is based on the operating system's own switching strategy, and the user has no perception. The switching content of the thread includes the kernel stack and the hardware context. The content of thread switching is stored in the kernel stack. The thread switching process is from "user mode to kernel mode to user mode", and the switching efficiency is moderate. All threads in a process share the resources of the process, but threads also have their own independent resources. These resources are:

  • Thread ID : Each thread has its own thread ID, which is unique in this process. The process uses this to identify the thread.
  • The value of the register group: Since threads run concurrently, each thread has its own different running clues. When switching from one thread to another, the state of the original thread's register set must be saved for future The thread can be restored when it is switched back to.
  • Thread stack : The stack is necessary to ensure that threads run independently. A thread function can call a function, and the called function can be nested layer by layer, so the thread must have its own function stack so that the function call can be executed normally without being affected by other threads.
  • Error return code : Because there are many threads running at the same time in the same process, it is possible that a thread has set the errno value after making a system call, and the thread has not processed the error yet, and another thread is being scheduled by the scheduler at this time Put it into operation, so that the wrong value may be modified. So different threads should have their own error return code variables.
  • Thread's signal masking code : Since each thread is interested in different signals, the thread's signal masking code should be managed by the thread itself. But all threads share the same signal handler.
  • Thread priority : Since the thread needs to be able to be scheduled like a process, there must be a parameter available for scheduling. This parameter is the priority of the thread.

1.3 Coroutine

   A coroutine can be understood as a thread in the user mode, that is, a thread can contain multiple coroutines, which are created by the user and controlled by the user to perform the scheduling of the coroutine. The switcher of the coroutine is the user (programmer or application), and the switching timing is determined by the user's own program. The switching content of the coroutine is the hardware context, and the switching memory is stored in the user's own variable (user stack or heap). The switching process of the coroutine only has the user mode, that is, it does not fall into the kernel mode, so the switching efficiency is high. Although multiple goroutines in a thread can be switched, multiple goroutines are executed serially and can only run in one thread, and the CPU multi-core capability cannot be used. The coroutine in the thread is not perceptible to the CPU, and the CPU only perceives that a thread is running. The main resources owned by the coroutine are:

  • The context of the coroutine, that is, the value of various registers
  • The stack owned by the coroutine

2、libco库跑hello world

    Libco is a C/C++ coroutine library used on a large scale in the WeChat backend. It has been running stably on tens of thousands of machines in the WeChat backend since 2013. Libco can support synchronous or asynchronous writing through only a few functional interfaces co_create/co_resume/co_yield and co_poll, just as easy as a thread library. At the same time, the library provides hooks for socket family functions, so that back-end logic services can be asynchronously transformed almost without modifying the logic code.

    Github address: 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;
}

     This is a producer-consumer example. In this example, there are mainly two functions:

  • Producer function: Producer is responsible for placing tasks in the task queue
  • Consumer function: Consumer is responsible for taking tasks from the task queue for execution

   In the main function, two coroutines are created for these two functions to execute. The two interfaces of libco are mainly used here, namely co_create() and co_resume() , co_create() is responsible for creating a coroutine, and co_resume () Responsible for starting a coroutine. There is a big difference between the creation of a coroutine and the creation of a thread. The thread can be run directly when it is created, but the coroutine is not run after it is created, but needs to call another function to run.

 

Guess you like

Origin blog.csdn.net/MOU_IT/article/details/114649736