纤程(协程)

     纤程又称协程,它是运行在线程之上的,一个线程可以包含多个纤程(只有一个纤程的纤程没有意义),多个纤程可以快速切换来执行不同的任务。因为纤程实际上是运行在线程之上的,所以一个线程上的多个纤程实际上也是顺序执行的,但纤程之间的切换很快,这样就好像是多个线程在执行一样。

    纤程是在用户模式下实现的,内核对线程了如指掌,但对纤程却一无所知,内核会根据我们定义的算法来对纤程进行调度,线程一次只能执行一个纤程的代码,具体执行哪个纤程由我们来决定。

    内核对于线程分配的花销比较大,而纤程的花销则比较小,而且线程切换会经历用户态、内核态的切换,速度较慢,纤程则是在用户态上即可完成切换,切换的上下文信息也很小,所以切换速度很快。又因为多个协程是在一个线程上执行的,所以协程之间没有锁机制,执行效率更高。

    可以在一个线程里创建数万甚至数十万个纤程,就像数万次函数调用一样,而如果是线程的话就会产生大量的上下文切换花销,而且线程所需的资源比纤程多的多,一个进程里能创建的线程数量远远不如纤程多。

    对于传统的“生产者-消费者模型”,一般使用多线程实现,这就不可避免的产生线程切换、全局资源的锁机制、休眠等待的一系列开销。为了提高效率,可以改用协程,如下所示为windows下使用协程的生产消费模型:

#include <windows.h>

#define MAIN			  0
#define PRODUCER  1
#define CONSUMER 2
int data;
LPVOID aryFiberAddr[2];

// 生产者
void __stdcall producer(LPVOID param)
{
	int counter = 0; 
	int count_sum = *(int*)param; 
	while (counter++ < count_sum) 
	{
		Sleep(5000); // 每五秒生产一次数据
		data = rand(); 

		SwitchToFiber(aryFiberAddr[CONSUMER]); //切换到消费者纤程,直到消费纤程中调用SwitchToFiber()切换到生产者纤程,才会继续执行下面的代码
	} 

	SwitchToFiber(aryFiberAddr[MAIN]); //生产结束,切换到主纤程
}

// 消费者
void __stdcall consumer(LPVOID param)
{
	while (1)
	{
		int i = data;
		SwitchToFiber(aryFiberAddr[PRODUCER]); // 完成数据消费后切换到生产者纤程,直到生产纤程中调用SwitchToFiber()切换到消费纤程,才会继续执行下面的代码
	}
}


int main(int argc, char** argv)
{
	aryFiberAddr[MAIN] = ConvertThreadToFiber(NULL); // 创建主纤程,只有纤程才能切换执行其他纤程

	int taskCount = 1;
	aryFiberAddr[PRODUCER] = CreateFiber(0, producer, &taskCount);// 创建生产者纤程
	aryFiberAddr[CONSUMER] = CreateFiber(0, consumer, 0); // 创建消费者纤程

	SwitchToFiber(aryFiberAddr[PRODUCER]); // 切换到生产者纤程,直到其它纤程里调用SwitchToFiber()切换到主纤程,下面的代码才会继续执行

	// 纤程清理
	DeleteFiber(aryFiberAddr[PRODUCER]);
	DeleteFiber(aryFiberAddr[CONSUMER]);
	//DeleteFiber(aryFiberAddr[MAIN]); //当前运行的光纤调用 DeleteFiber()的话,则其线程将调用 ExitThread

	return 0;
}

      多个纤程虽然是快速切换的,但是因为它们是在一个线程上运行的,所以它们实际上也是串行执行的,所以在协程中尽量不要调用阻塞IO的方法,因为这样会让整个线程进入阻塞状态,从而使其它纤程也得不到调度。

    一个线程内的多个协程是串行执行的,不能利用多核,所以,协程不适合计算密集型的场景。而对于IO密集型的场景,如每秒10万次IO操作,可以使用协程配合多线程来进行处理。比如一个有10万连接的服务,有的连接没有数据可读,有的连接需要读取数据,那么就可以使用协程,当本纤程需要阻塞等待数据的时候,就可以切换到另一个纤程上去读取数据。

    golang从编译器和语言基础库多个层面对协程做了实现,所以,go的协程是目前各类有协程概念的语言中实现的最完整和成熟的。go对各种IO函数进行了封装,而其内部是调用了操作系统的非阻塞或异步IO函数,当这些函数返回 busy 或 bloking 时,go利会将现有的执行序列压栈,让线程去拉另外一个协程的代码来执行,即从当前协程切换到另一个协程去执行。我们原来都是基于IO事件调度的异步编程来实现高性能,IO执行结果通过回调通知,程序的线性被打乱,如Node.js中的层层Callback。而Go中的协程实现了对 IO 事件的封装,通过语言级的支持让异步的代码看上去像同步执行的一样。

    目前C++20中提供的协程比较偏底层,协程的实现依赖于几个关键的概念和关键字,如co_await、co_return、co_yield。我们可以使用async_simple这种封装好的简单易用的协程库来使用协程,关于这个库的更多可以参考这篇文章:C++20协程原理和应用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值