C++20中的协程

一、协程

在谷歌的Golang中,如果大家说他的特点有啥,肯定绕不过协程。而在此之前,大多数的语言一般是从多进程讲到多线程,一般来说,对某个语言掌握的深度,就看在多线程下编程的能力(当然,没有多线程的除外)。多线程的编程难度自然是很多程序员望而却步的。虽然说多线程编程有他的优势,但能不能一种更好的方式,既可以有多线程的并行执行的优势,又能降低其开发的难度呢?于是在Go语言中出来的协程。
协程可以理解为用户态的线程,大家都知道,多线程的调度是由内核来完成的。所以在多线程编程中,会涉及到相当多的状态保护和资源保护。同时,为了保证公平,内核又采用了多种CPU分片的调度方法。可是当线程非常多时,不停的上下文切换会浪费大量宝贵的时间。这也是在多线程编程中的一个老大难问题,到底创建多少线程为宜?于是各种线程池技术也都引入进来,用各种手段(比如核的2倍等)来保证线程的切换相对来说最好。
c++20的协程是无栈协程,但是目前看来,其使用有一定复杂性,不过在其它一些封装好的库中,使用就相当方便了。估计标准库中封装好的协程,可能会推迟到c++23标准中去了。由于协程将调度方式和资源控制交还给了用户,那么线程的切换的缺点,就得以相当程度的避免了。但需要注意的是,谈协程仍然离不开线程,协程是运行在线程之上的,只是用户不需要在直接管理线程,同一个线程在同一时刻只能运行一个协程,但在同一个线程中,协程是串行的,不会产生线程资源的竞争(data race),所以原则上说,是不需要加锁的。
这也是为什么在文档中建议不要把同一个协程的运行块放置到不同的线程中运行的原因,因为如果这样,那么就极可能会发生资源竞争,从某种意义上说,回到了线程编程的方式。
在c++20的协程提交过程中,谷歌为了和微软竞争,提交了一个差不太多的提议,有点不厚道的味道,不过最终还是微软的提议胜出了,看来谷歌有点趋向保守的意思?

二、c++20协程应用场景和特点

协程其是就是搞定异步而生的。在以前,异步(事件驱动、异步IO和数据异步)的实现,基本有两个手段,一个是使用多线程+回调函数,另外一个就需要内核的API,在内部通过中断+回调来实现。如果有过异步多线程编程经验的程序员理解这个就非常容易了。可是,在这种编程中的一个缺点是,你得有两种编程理解,一种是同步的,所调即所得;一种是异步的,你得等人家回调你,才会得到想要的结果。这对于一些编程新手和对异步编程不太熟悉的程序员来说,其实是很痛苦的。那么协程就解决了这个问题,在上层,体现为所调即所得,在底层,会不断的等待运行结果的完成。
c++20的协程的特点有以下几个:
1、不需要内部栈分配,仅需要一个调用栈的顶层桢。
2、协程运行过程中,需要使用关键词来控制运行过程(比如co_return)。
3、协程可能分配不同线程,触发资源竞争。
4、没有调度器,但是需要标准和编译器的支持。

三、配置

协程的使用,需要尽量升级编译器到新版本。在Windows平台建议使用VS2019,在Linux平台建议使用GCC10.1以上,并开启c++20选项。这次为了测试方便,使用的是VS2019,并升级到了16.7.3,但是应该从16.6就开始支持了。为了能够支持协程,需要进行如下配置:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

然后在“属性——c/c++——所有选项”中查看:

在这里插入图片描述

四、应用

下面看几个小例子,这些例子都是自带或者官网的一部分,也有一部分是从相关的资源网站获得,此处不再一一列举出处:

#include <iostream>
#include <thread>
#include <experimental/coroutine>
#include <chrono>
#include <functional>

 // coroutine.cpp

using call_back = std::function<void(int)>;
void Add100ByCallback(int init, call_back f) // 异步调用
{
	std::thread t([init, f]() {
		std::this_thread::sleep_for(std::chrono::seconds(1));
		std::cout << "Add100ByCallback: " << init << std::endl;
		f(init + 100);
		});
	t.detach();
}

struct Add100Awaitable
{
	Add100Awaitable(int init) :init_(init) {}
	bool await_ready() const { return false; }
	int await_resume() { return result_; }
	void await_suspend(std::experimental::coroutine_handle<> handle)
	{
		auto f = [handle, this](int value) mutable {
			result_ = value;
			handle.resume();
		};
		Add100ByCallback(init_, f); // 调用原来的异步调用
	}
	int init_;
	int result_;
};

struct Task
{
	struct promise_type {
		auto get_return_object() { return Task{}; }
		auto initial_suspend() { return std::experimental::suspend_never{}; }
		auto final_suspend() { return std::experimental::suspend_never{}; }
		void unhandled_exception() { std::terminate(); }
		void return_void() {}
	};
};

Task Add100ByCoroutine(int init, call_back f)
{
	std::cout << "call coroutine----1" << std::endl;
	int ret = co_await Add100Awaitable(init);
	std::cout << "call coroutine----1 yyyy:" <<ret<< std::endl;

	co_return ;
	ret = co_await Add100Awaitable(ret);
	std::cout << "call coroutine----1 xxxxx:" << ret << std::endl;
	//ret = co_await Add100Awaitable(ret);
	f(ret);
}

void co_vs_callback()
{
	Add100ByCallback(5, [](int value) { std::cout << "get result: " << value << "\n"; });
	Add100ByCoroutine(10, [](int value) { std::cout << "get result from coroutine1: " << value << "\n"; });
	Add100ByCoroutine(20, [](int value) { std::cout << "get result from coroutine2: " << value << "\n"; });
	Add100ByCoroutine(30, [](int value) { std::cout << "get result from coroutine3: " << value << "\n"; });
	Add100ByCoroutine(40, [](int value) { std::cout << "get result from coroutine4: " << value << "\n"; });
	std::this_thread::sleep_for(std::chrono::seconds(20));
}

//co_return.cpp
#include <iostream>
#include <experimental/coroutine>
using namespace std;

template<class T>
struct test {
    // inner types
    struct promise_type;
    using handle_type = std::experimental::coroutine_handle<promise_type>; //type alias

    // functions
    test(handle_type h) :handle(h) { cout << "# Created a Test object\n"; }
    test(const test& s) = delete;
    test& operator=(const test&) = delete;
    test(test&& s) :handle(s.handle) { s.handle = nullptr; }
    test& operator=(test&& s) { handle = s.handle; s.handle = nullptr; return * this; }
    ~test() { cout << "#Test gone\n"; if (handle) handle.destroy(); }

    T get()
    {
        cout << "# Got return value\n";
        if (!(this->handle.done()))
        {
            handle.resume();  //resume
            return handle.promise().value;
        }
    }

    struct promise_type
    {
        promise_type() { cout << "@ promise_type created\n"; }
        ~promise_type() { cout << "@ promise_type died\n"; }

        auto get_return_object() //get return object
        {
            cout << "@ get_return_object called\n";
            return test<T>{handle_type::from_promise(*this)};// pass handle to create "return object"
        }

        auto initial_suspend() // called before run coroutine body
        {
            cout << "@ initial_suspend is called\n";
            // return std::experimental::suspend_never{}; // dont suspend it
            return std::experimental::suspend_always{};
        }

        auto return_value(T v) // called when there is co_return expression
        {
            cout << "@ return_value is called\n";
            value = v;
            return std::experimental::suspend_never{}; // dont suspend it
                                                       //return std::experimental::suspend_always{};
        }

        auto final_suspend() // called at the end of coroutine body
        {
            cout << "@ final_suspend is called\n";
            return std::experimental::suspend_always{};
        }

        void unhandled_exception() //exception handler
        {
            std::exit(1);
        }

        // data
        T value;
    };

    // member variables
    handle_type handle;
};
test<int> return_coroutine()
{
    std::cout << "start return_coroutine\n";
    co_return 1;
    co_return 2; // will never reach here
}

void co_vs_return()
{
    auto a = return_coroutine();
    cout << "created a corutine, try to get a value\n";
    int an = a.get();
    cout << "value is " << an << endl;
    an = a.get();
    cout << "value is " << an << endl;
}

//test_coroutin.cpp
#include <vector>
#include <chrono>
#include <future>
#include <string>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <experimental/coroutine>
#include <experimental/generator>
#include <time.h>


namespace coroutines = std::experimental;


namespace hey_coroutines
{
	using namespace std::chrono_literals;


	// generator<T> 延迟求值
	coroutines::generator<int> lazy_get_range(int min, int max)
	{
		for (int i = min; i <= max; i++)
		{
			co_yield i;
		}
	}


	std::future<std::vector<std::string>> heavy_code_async()
	{
		return std::async([]
			{
				std::vector<std::string> result;


				// 模拟耗时任务
				for (size_t i = 100; i <= 500; i += 100)
				{
					result.emplace_back("我是:" + std::to_string(i));
					std::this_thread::sleep_for(1s);
				}


				return result;
			});
	}


	std::future<void> invoke_heavy_code()
	{
		for (auto& item : co_await heavy_code_async())
		{
			std::cout << item << std::endl;
		}
	}

	void CallTest()
	{
		std::vector<int> numbers;


		// 延迟求值
		auto range = hey_coroutines::lazy_get_range(0, 10);


		// 实际求值
		std::copy(std::begin(range), std::end(range), std::back_inserter(numbers));


		for (auto& item : numbers)
		{
			std::cout << item << std::endl;
		}


		// 模拟异步耗时任务
		std::cout << "请等待协程执行完毕..." << std::endl;
		// 由于只是例子,就粗暴等待。。实际应用肯定是不会阻塞主线程执行的。
		hey_coroutines::invoke_heavy_code().wait();
	}

	std::experimental::generator<int> GetSequenceGenerator(
		int startValue,
		size_t numberOfValues) {
		for (int i = 0 ; i < startValue + numberOfValues; ++i) {
			time_t t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
			char str[600];
			ctime_s(str, 600, &t);
			std::cout << str << std::endl;
			co_yield i;
		}
	}

	void call_GetSeq()
	{
		auto gen = GetSequenceGenerator(10, 5);
		for (const auto& value : gen) {
			std::cout << value << "(Press enter for next value)" << std::endl;
			std::cin.ignore();
		}
	}
}

//main.cpp
void co_vs_callback();
void co_vs_return();

namespace hey_coroutines
{
	void call_GetSeq();
	void CallTest();
}

int main()
{
	hey_coroutines::CallTest();
	//hey_coroutines::call_GetSeq();
	//co_vs_callback();
	//co_vs_return();
	return 0;
}

上面的文件共需要三个测试的cpp和一个主文件cpp,运行结果很是有意思,这里只看其一个:

call coroutine----1
Add100ByCallback: 10
call coroutine----1 yyyy:110
Add100ByCallback: 110
call coroutine----1 xxxxx:210
get result from coroutine1: 210

程序在回调结果和直接调用的结果上都保持了一致,这样看来,异步编程以后可能就会变得简单易用。
通过上述的代码可以看到,未封装的代码和封装的代码还是有一些不同的,这里分析一下c++20中未封装的代码,实现协程需要处理以下步骤:
1、首先要处理关键字:co_wait,co_return,co_yield,可能这些关键字还会在新标准中不断的完善。
2、要实现awaitable,其实就是实现await_ready、await_suspend、await_resume。
3、要实现自己的promise,operator new,operator delete,get_return_object,initial_suspend,final_suspend,unhandled_exception,return_void或者return_value
4、实现相关上层逻辑
5、调用
上面的promise_type和Awaitable大家可能会觉得陌生,其实在标准的文档中都有解释,比对代码和名称,一目了然。如果有兴趣可以看看:
https://lewissbaker.github.io/
上面有相当清楚的分析。

五、总结

协程因为不需要在像线程一样不断的上下文切换,所以可以认为无限制的创建,一般来说,线程创建过百,普通的PC和服务器基本就体会不到线程的优势了,但协程却可以创建几万几十万,甚至更多。网上有资料说甚至可以创建亿级以上,这个暂时还不能实战来验证。
不过协程的看似同步化操作,仍然是大幅的降低了异步编程的门槛,以后如果封装得体,会在未来产生不可估量的影响,c++的编程难度在底层复杂性增加的前提下,应用开始大幅降低难度,这对c++语言本身应该是一个非常利好的消息。
在这里插入图片描述

下载代码方式:https://pan.quark.cn/s/dd3561eca308 在软件开发领域,面向对象编程(OOP)是一种普遍采纳的结构化方法,它使得开发者能够借助模拟现实环境中的实体和关系来构建软件系统。在本案例中,我们观察到的是一个关于借助抽象类来执行不同几何图形面积求解的实践应用。现在,让我们详细分析这一议题。 标题 "应用抽象类计算面积" 清晰地表明我们将要讨论一个抽象类,此类设定了一个用于测量图形面积的标准函数,但并未提供实际的执行过程。抽象类在诸如C#或Java等编程语言中通常借助`abstract`修饰符进行声明,它们无法直接创建对象实例,仅能作为其他类的基础模板。 描述部分提及的"图形界面应用"暗示这是一个基于视觉用户界面(GUI)的系统,可能运用了.NET Framework的Windows Forms或WPF技术,或者是Java平台的Swing或JavaFX框架。在这样环境下,用户能够通过视觉元素与这些几何体进行互动,例如输入相关尺寸并观看到计算得出的面积值。 抽象类“几何体”内嵌了“计算面积”这一抽象函数。在代码层面,这可以被表述为: ```csharp public abstract class GeometricShape { public abstract double CalculateArea(); } ``` 随后,有三个派生类:圆(Circle)、矩形(Rectangle)和三角形(Triangle),它们各自提供了这个抽象函数的具体实现。比如,圆的面积是通过π乘以半径的平方得到的,矩形的面积是长和宽的乘积,而三角形的面积可能是底乘以高再除以2的结果。这些类将提供具体实现来计算它们各自的面积: ```csharp p...
内容概要:本文系统研究了移相控制全桥LLC谐振变换器的工作特性,深入分析其在不同工作模式下的运行机理与性能表现,重点探讨了软开关实现、高效率能量转换及宽范围电压调节等关键技术优势。通过Simulink搭建精确的仿真模型,对谐振腔参数、开关频率、电压增益、系统效率等关键指标进行仿真分析,验证了理论设计的正确性。同时,详细研究了移相控制策略对系统动态响应、稳定性和轻载/重载工况适应性的影响,揭示了控制参数与电路参数之间的耦合关系,为高频高效电源设计提供了理论依据和实践指导。; 适合人群:具备电力电子技术、模拟电路及自动控制理论基础,从事开关电源、新能源变换器、电动汽车充电模块或高频电源系统研发的工程师及高校研究生。; 使用场景及目标:①掌握全桥LLC谐振变换器的拓扑结构、工作原理与关键参数设计方法;②理解移相控制在实现零电压开通(ZVS)和零电流关断(ZCS)中的作用机制;③通过Simulink仿真掌握变换器建模、参数优化与性能评估流程,服务于实际产品开发与学术课题研究。; 阅读建议:建议读者结合提供的Simulink仿真模型进行同步操作,重点关注谐振网络(Lr, Lm, Cr)参数与移相角之间的匹配设计,深入理解软开关条件的形成过程,并通过调整负载和输入电压进行多工况仿真,以全面掌握系统动态特性。
一、项目概述 本项目设计并实现了一个基于STM32F103C8T6微控制器的温湿度监测与报警系统。系统通过DHT11传感器实时采集环境温湿度数据,当数据超过预设阈值时,蜂鸣器发出声音报警,同时通过4位数码管实时显示当前温湿度值。整个系统采用Type-C接口供电,支持USB串口通信,便于数据调试与传输。 二、硬件组成 1. 主控模块:STM32F103C8T6最小系统(含晶振、复位、BOOT电路、去耦电容)。 2. 传感器模块:DHT11温湿度传感器(单总线通信)。 3. 报警模块:有源蜂鸣器(三极管驱动)。 4. 显示模块:4位共阴数码管(TM1637驱动)。 5. 通信模块:CH340N USB转串口芯片(Type-C接口)。 6. 电源模块:AMS1117-3.3稳压电路(5V转3.3V)。 7. 调试接口:SWD调试排针(SWDIO/SWCLK)。 三、开发流程 1. 原理图设计:使用嘉立创EDA绘制完整原理图,包含各功能模块。 2. PCB设计:进行PCB布局布线,注重电源完整性、信号隔离及晶振走线。 3. 制板焊接:嘉立创打样,手工焊接元器件。 4. 软件开发:STM32CubeMX配置工程,编写DHT11驱动、TM1637显示、串口通信及报警逻辑。 5. 调试测试:通过SWD下载程序,串口输出数据,测试温湿度采集与报警功能。 四、项目特点 - 完整的嵌入式系统开发流程(硬件设计→制板→软件编程→调试)。 - 多传感器数据融合与实时显示。 - 低功耗设计,支持USB供电与串口通信。 - 模块化设计,便于功能扩展(如添加WiFi模块、数据存储等)。 五、应用前景 适用于家庭环境监测、农业大棚、仓库温湿度监控等场景
内容概要:本文提出了一种基于TOGI-SOGI混合积分器的光储并网谐波自适应抑制方法,并通过Simulink实现完整仿真验证。该方法融合三重二阶广义积分器(TOGI)与标准二阶广义积分器(SOGI),能够精确提取电网电压中的基波正序分量,有效分离并抑制高次谐波成分,尤其在电网电压畸变条件下显著提升了并网逆变器的控制精度与电能质量。文中详细阐述了混合积分器的结构设计、谐波检测机制、自适应调节策略及其在锁相环(PLL)中的集成应用,并通过构建光储并网系统模型进行多工况仿真,结果表明该方法具备优良的动态响应特性、强鲁棒性及谐波抑制能力。; 适合人群:具备电力电子、新能源发电、自动控制等相关专业背景,熟悉Simulink仿真环境,从事光伏并网控制、电能质量治理、微电网运行与控制等领域研究的科研人员、工程技术人员及研究生。; 使用场景及目标:①应用于电网存在谐波污染的光伏发电并网场景,提升逆变器在非理想电网下的运行稳定性;②为基于广义积分器的先进锁相技术与谐波补偿策略提供理论支持与实现范例;③服务于高校科研项目复现、学位论文研究、电力电子控制器原型开发及学术成果验证。; 阅读建议:建议学习者结合提供的Simulink模型深入剖析TOGI-SOGI的内部信号流向与参数整定逻辑,重点关注谐波分量的解耦过程与自适应控制模块的实现机制,可通过设置不同谐波含量、频率偏移等扰动工况进行对比测试,以全面掌握其在复杂电网环境下的适应性与优越性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值