【c++20】协程与屏障

屏障std::barrier

cpp 复制代码
#include <mutex>
#include <iostream>
#include <future>
#include <barrier>

std::mutex m;
const int cnt = 3;
std::barrier b(cnt); // 创建一个barrier,需要3个线程到达同步点
double totalScore = 0;
void CalcExamScore(int i) {
	m.lock();
	std::cout << "读取第" << i << "个学生的成绩,并累加到总成绩\n";
	m.unlock();
	double cur = i * 10;
	totalScore += cur;
	b.arrive_and_wait(); // 到达同步点并等待
	m.lock();
	std::cout << "第"<< i<< "学生的成绩:" << cur << " 班级平均成绩" << totalScore/cnt << "\n";
	m.unlock();
}
int main() {
	std::vector<std::thread> threads;
	for (int i = 0; i < cnt; ++i) {
		threads.emplace_back([i]() {CalcExamScore(i);});
	}
	for (auto& t : threads) {
		t.join();
	}
	return 0;
}

回调函数

std::barrier 的回调函数先于所有线程从 arrive_and_wait() 返回执行。回调执行完毕后,屏障重置,各线程才继续往下走。这个顺序是标准保证的。回调函数在某个arrive_and_wait所在的线程执行。

以下程序分五步:

一,各线程读取分数。

二,回调函数执行 计算排名。

三, 各线程输入志愿。

四,根据分数和志愿匹配学校。

五,查看录取结果。

cpp 复制代码
std::mutex m;
const int cnt = 3;
int trun = 0;
auto on_phase_complete = []() noexcept {
	if (0 == trun)
	{
		std::cout << "计算排名\n";
	}
	else {
		std::cout << "根据志愿匹配学校\n";
	}
	++trun;
};
std::barrier b(cnt, on_phase_complete); // 创建一个barrier,需要3个线程到达同步点
double totalScore = 0;
void CalcExamScore(int i) {
	m.lock();
	std::cout << "读取第" << i << "个学生的成绩,并累加到总成绩\n";
	m.unlock();
	double cur = i * 10;
	totalScore += cur;
	b.arrive_and_wait(); // 到达同步点并等待
	m.lock();
	std::cout << "第"<< i<< "个学生的成绩:" << cur << " 班级平均成绩" << totalScore/cnt << "。输入自己志愿\n";
	m.unlock();
	b.arrive_and_wait(); // 到达同步点并等待
	m.lock();
	std::cout << "第" << i << "个学生被某院校录取\n";
	m.unlock();
}
int main() {
	std::vector<std::thread> threads;
	for (int i = 0; i < cnt; ++i) {
		threads.emplace_back([i]() {CalcExamScore(i);});
	}
	for (auto& t : threads) {
		t.join();
	}
	return 0;
}

门闩std::latch

门栓只能使用一次,多次后异常。

上面代码的屏障改成std::latch b(cnt)后,就异常;

协程

线程切换:需要陷入内核态,保存全部通用寄存器、更新页表、修改内核调度队列,资源开销大,频繁切换会严重损耗性能;

C++20 协程切换:纯用户态操作,仅保存少量寄存器、局部变量、协程帧、Promise、协程句柄等轻量数据,挂起 / 恢复成本极低。

核心结论:C++20 是编译器原生无栈协程,轻量、切换成本低,但协程内部禁止阻塞。

只要函数内部包含co_await、co_yield、co_return三个关键字任意一个,这个函数就属于协程函数:

co_await:核心关键字,用于主动暂停协程,等待异步操作完成后恢复;

co_yield:暂停协程,同时向外产出一个返回值,典型用于生成器;

co_return:终止协程生命周期,返回最终结果

如果是C++23则代码很简洁。

cpp 复制代码
// #include <generator> C++23支持的generator
#include <iostream>
#include <coroutine>
#include <exception>
#include <utility>
#include <cstddef>

//Generator 模拟C++23的std::generator
template <typename T>
class Generator {
public:
	struct promise_type {
		T current_value;

		Generator get_return_object() {
			return Generator{ std::coroutine_handle<promise_type>::from_promise(*this) };
		}
		std::suspend_always initial_suspend() noexcept { return {}; }
		std::suspend_always final_suspend() noexcept { return {}; }
		std::suspend_always yield_value(T value) {
			current_value = std::move(value);
			return {};
		}
		void return_void() {}
		void unhandled_exception() { std::terminate(); }
	};

	class iterator {
	public:
		using iterator_category = std::input_iterator_tag;
		using value_type = T;
		using difference_type = std::ptrdiff_t;
		using pointer = T*;
		using reference = T&;

		iterator() : handle_(nullptr) {}
		explicit iterator(std::coroutine_handle<promise_type> h) : handle_(h) {}

		iterator& operator++() {
			handle_.resume();
			if (handle_.done()) handle_ = nullptr;
			return *this;
		}
		void operator++(int) { ++(*this); }

		bool operator==(const iterator& other) const { return handle_ == other.handle_; }
		bool operator!=(const iterator& other) const { return !(*this == other); }

		T& operator*() { return handle_.promise().current_value; }
		T* operator->() { return &handle_.promise().current_value; }

	private:
		std::coroutine_handle<promise_type> handle_;
	};

	iterator begin() {
		if (!handle_) return iterator{};
		handle_.resume();
		if (handle_.done()) return iterator{};
		return iterator{ handle_ };
	}

	iterator end() { return iterator{}; }

	Generator(Generator&& other) noexcept : handle_(std::exchange(other.handle_, nullptr)) {}
	~Generator() { if (handle_) handle_.destroy(); }

	Generator(const Generator&) = delete;
	Generator& operator=(const Generator&) = delete;

private:
	Generator(std::coroutine_handle<promise_type> h) : handle_(h) {}
	std::coroutine_handle<promise_type> handle_;
};

Generator<long long> fibonacci(int count) {
	long long a = 0, b = 1;

	for (int i = 0; i < count; ++i) {
		co_yield a;
		auto next = a + b;
		a = b;
		b = next;
	}
}

int main() {
	for (auto num : fibonacci(10)) {
		std::cout << num << " "; // Output: 0 1 1 2 3 5 8 13 21 34
	}
}

co_yield a; 执行完后,就执行std::cout << num。

多次调用

同一个函数执行结束前,可以执行另外一个同名协程函数,互不影响。通过返回值区分。

cpp 复制代码
int main() {
	auto ret1 = fibonacci(3);
	auto ret2 = fibonacci(3);
	auto it1 = ret1.begin();
	auto it2 = ret2.begin();
	std::cout << *it1 << " " << *it2 << "\n";
	++it1, ++it2;
	std::cout << *it1 << " " << *it2 << "\n";
	++it1, ++it2;
	std::cout << *it1 << " " << *it2 << "\n";
}

0 0

1 1

1 1

连接服务端、发送消息、接受信息

协程分四步:一,异步连接服务端。二,异步发送消息。三,挂起线程。 四,同步接受消息并处理。协程两次执行完,第一次执行一二三,第二次执行四。

先将100个协程执行一次,执行完毕后,这些线程再执行一次。

由于网络回应时间占处理时间的绝大部分,故其它时间可忽略。其它时间忽略后:

使用协程,处理时间等于所有网站反应时间的最大值。

不使用协程,处理时间等于所有网站反应时间之和。

扩展阅读

计算几何为骨,排样优化为魂
作品一:亲士套料工具箱 作品二: 亲士CAD工具箱
经典文章推荐:二维排样
万物皆数学
查阅鄙人的博文,请点击博文下载学院导航
活到老,学到老。明朝中后期,大约50%的进士能当上堂官(副部及更高);能当上堂官的举人只有十余人。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。

测试环境

操作系统:win7 开发环境: VS2019 C++17

或者 操作系统:win10 开发环境: VS2022 C++17

如无特殊说明,本算法用**C++**实现。

相关推荐
会编程的小孩2 小时前
C++ 内存分区、堆与栈、static、union 完整修正版文档
c++
erzong2312 小时前
Linux操作系统:线程池&踩坑修复
linux·运维·算法
多弗朗皮卡丘2 小时前
C++的类型转换
开发语言·c++
alphaTao2 小时前
LeetCode 每日一题 2026/10/19-2026/10/25
算法·leetcode
火2253 小时前
# C++ 微服务即时通讯系统(三):网关设计——HTTP 请求、Session 鉴权与 WebSocket 通知上一篇
c++·http·微服务
ZhengEnCi4 小时前
一亿条数据取前100条完全指南:从全量排序到分块流式最小堆的4种解法实测
python·算法·排序算法
C++ 老炮儿的技术栈4 小时前
机器人的坐标,像 XYZ 的坐标值,是用单精度浮点还是用双精度浮点?
c语言·c++·人工智能·单片机·c
zaizai10074 小时前
STL深度剖析2
java·c++·算法
幻智星科技4 小时前
FPGA纯硬件UDP桥接:微秒级响应新范式
c++·fpga开发·硬件架构