C++ 协程:从 asyncio 理解现代异步编程

背景 现代编程离不开异步。Python 有 asyncio,JavaScript 有 async/await,Go 有 goroutine。C++20 终于引入了协程(Coroutines)。 今天从 Python asyncio 的视角来看看 C++ 协程怎么用。 Python asyncio 的模式 async def fetch(url: str) -> str: # 模拟异步IO await asyncio.sleep(1) return f"data from {url}" async def main(): results = await asyncio.gather( fetch("a.com"), fetch("b.com"), fetch("c.com"), ) print(results) asyncio.run(main()) 核心概念:async def 定义协程函数,await 挂起等待,asyncio.gather 并发执行。 C++20 协程入门 C++ 协程的关键类型: co_await — 挂起协程 co_return — 返回值(相当于 return) co_yield — 产出值(用于生成器) std::suspend_never / std::suspend_always — 挂起策略 简单例子:模拟异步任务 #include <coroutine> #include <future> #include <iostream> template<typename T> struct Task { struct promise_type { T value; std::exception_ptr error; auto get_return_object() { return Task{std::coroutine_handle<promise_type>::from_promise(*this)}; } auto initial_suspend() { return std::suspend_never{}; } auto final_suspend() noexcept { return std::suspend_always{}; } void return_value(T v) { value = v; } void unhandled_exception() { error = std::current_exception(); } }; std::coroutine_handle<promise_type> handle; Task(std::coroutine_handle<promise_type> h) : handle(h) {} ~Task() { if (handle) handle.destroy(); } T get() { handle.resume(); return handle.promise().value; } }; // 模拟异步操作 Task<int> async_fetch() { std::cout << "开始异步任务...\n"; co_await std::suspend_always{}; // 挂起,模拟异步等待 std::cout << "异步任务完成\n"; co_return 42; } int main() { auto task = async_fetch(); std::cout << "做一些其他事情...\n"; int result = task.get(); std::cout << "结果: " << result << "\n"; } 和 Python asyncio 对比 Python C++ async def Task<T> 返回类型 await co_await return value co_return value asyncio.sleep(1) std::suspend_always{} 事件循环自动调度 手动 resume() 实际应用:HTTP 客户端 Task<Response> http_get(const std::string& url) { co_await socket_.async_connect(url); co_await socket_.async_write(request_); Response resp = co_await socket_.async_read(); co_return resp; } Task<std::vector<Response>> fetch_all(const std::vector<std::string>& urls) { std::vector<Task<Response>> tasks; for (const auto& url : urls) { tasks.push_back(http_get(url)); } std::vector<Response> results; for (auto& task : tasks) { results.push_back(task.get()); } co_return results; } 坑点 协程不能用在模板参数里 — std::vector<Task<int>> 可以,但 Task<Task<int>> 不行 生命周期管理 — 协程句柄必须手动 destroy(),除非用 RAII 封装 调试困难 — 栈帧被编译器切分,gdb 支持还在完善中 总结 C++20 协程还很年轻,库支持不如 Python 完善。但对于构建高性能网络服务,它的零成本抽象是其他语言难以比拟的。 ...

2026年4月5日 · 2 分钟 · BvBeJ

Kubernetes Operator 开发实战:用 Go 告别手动运维

背景 Kubernetes Operator 是 CNCF 主推的云原生扩展机制。用 Go 写 Operator 是我日常工作的重要部分。 这篇文章聊聊怎么从零开发一个生产级的 Operator。 核心概念 Operator 核心是声明式 API + reconciliation loop: 用户声明期望状态 → Controller 调和 → 实际状态趋近期望 项目结构 my-operator/ ├── main.go ├── api/ │ └── v1/ │ └── myapp_types.go # CRD 定义 ├── controllers/ │ └── myapp_controller.go # Reconciliation 逻辑 └── config/ ├── crd/ └── rbac/ 第一步:定义 CRD (Custom Resource Definition) // api/v1/myapp_types.go package v1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type MyAppSpec struct { Replicas int32 `json:"replicas,omitempty"` Image string `json:"image"` Port int32 `json:"port"` EnvVars []EnvVar `json:"envVars,omitempty"` } type EnvVar struct { Name string `json:"name"` Value string `json:"value"` } type MyAppStatus struct { AvailableReplicas int32 `json:"availableReplicas,omitempty"` Conditions []metav1.Condition `json:"conditions,omitempty"` } // +kubebuilder:object:root=true // +kubebuilder:subresource:status // +kubebuilder:resource:shortName=myapp type MyApp struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` Spec MyAppSpec `json:"spec,omitempty"` Status MyAppStatus `json:"status,omitempty"` } func (r *MyApp) Hub() {} 第二步:生成代码 # 安装 controller-gen go install sigs.k8s.io/controller-tools/cmd/controller-gen@latest # 生成 CRD + RBAC + DeepCopy controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..." # 生成 CRD YAML controller-gen crd:crdVersions=v1 paths="./..." output:crd:artifacts:config=config/crd/bases 第三步:实现 Controller // controllers/myapp_controller.go package controllers type MyAppReconciler struct { Client client.Client Scheme *runtime.Scheme Log logr.Logger } func (r *MyAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := r.Log.WithValues("myapp", req.NamespacedName) // 1. 获取资源 var myapp v1.MyApp if err := r.Get(ctx, req.NamespacedName, &myapp); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } // 2. 构建 Deployment deploy := r.buildDeployment(&myapp) if err := ctrl.SetControllerReference(&myapp, deploy, r.Scheme); err != nil { return ctrl.Result{}, err } // 3. 创建或更新 Deployment found := &appsv1.Deployment{} err := r.Get(ctx, req.NamespacedName, found) if err != nil && errors.IsNotFound(err) { log.Info("Creating Deployment", "name", deploy.Name) err = r.Create(ctx, deploy) } else if err == nil { // 更新(需要对比 spec 差异) if !r.deploymentEqual(found, deploy) { found.Spec = deploy.Spec log.Info("Updating Deployment") err = r.Update(ctx, found) } } // 4. 更新 Status r.updateStatus(&myapp, found) return ctrl.Result{RequeueAfter: 30 * time.Second}, nil } func (r *MyAppReconciler) buildDeployment(app *v1.MyApp) *appsv1.Deployment { replicas := app.Spec.Replicas if replicas == 0 { replicas = 1 } return &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: app.Name, Namespace: app.Namespace, }, Spec: appsv1.DeploymentSpec{ Replicas: &replicas, Selector: &metav1.LabelSelector{ MatchLabels: map[string]string{"app": app.Name}, }, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"app": app.Name}, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{{ Name: "myapp", Image: app.Spec.Image, Ports: []corev1.ContainerPort{{ ContainerPort: app.Spec.Port, }}, Env: r.buildEnvVars(app.Spec.EnvVars), }}, }, }, }, } } 第四步:启动 Controller // main.go func main() { ctrl.SetLogger(zap.New(zap.UseDevMode(true))) mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, }) if err != nil { setupLog.Error(err, "unable to start manager") os.Exit(1) } if err = (&controllers.MyAppReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller") os.Exit(1) } if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { setupLog.Error(err, "problem running manager") os.Exit(1) } } 高级特性 1. Webhook 验证 // webhooks/myapp_webhook.go func (r *MyApp) ValidateCreate() error { if r.Spec.Replicas < 0 { return field.Invalid( field.NewPath("spec").Child("replicas"), r.Spec.Replicas, "replicas must be non-negative", ) } return nil } 2. Finalizer(防止误删) func (r *MyAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { myapp := &v1.MyApp{} r.Get(ctx, req.NamespacedName, myapp) // 删除标记? if myapp.DeletionTimestamp.IsZero() { // 添加 finalizer if !containsString(myapp.GetFinalizers(), "myapp.finalizer") { myapp.Finalizers = append(myapp.GetFinalizers(), "myapp.finalizer") r.Update(ctx, myapp) } } else { // 执行清理逻辑 r.cleanup(myapp) // 移除 finalizer myapp.Finalizers = removeString(myapp.GetFinalizers(), "myapp.finalizer") r.Update(ctx, myapp) } } 测试 import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = Describe("MyApp controller", func() { Context("with basic spec", func() { It("should create a Deployment", func() { myapp := &v1.MyApp{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "default", }, Spec: v1.MyAppSpec{ Replicas: 2, Image: "nginx:latest", Port: 80, }, } Expect(k8sClient.Create(ctx, myapp)).Should(Succeed()) }) }) }) 部署 Operator # config/manager/manager.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-operator spec: replicas: 1 template: spec: containers: - name: operator image: myorg/my-operator:v1.0.0 env: - name: WATCH_NAMESPACE value: "" # OLM (Operator Lifecycle Manager) 安装 operator-sdk olm install operator-sdk run bundle myorg/my-operator-bundle:v1.0.0 总结 Operator 开发的核心: ...

2026年4月4日 · 3 分钟 · BvBeJ

Vue3 Composition API 实战经验

背景 Vue3 发布两年多了,从 Options API 迁移到 Composition API 的项目也有了不少。这里总结一些实战经验。 为什么需要 Composition API Options API 的问题:逻辑关注点分散在一个组件的各个选项里(data、methods、computed、watch…)。 // Options API - 逻辑分散 export default { data() { return { count: 0 } }, methods: { increment() { this.count++ } }, computed: { doubled() { return this.count * 2 } }, watch: { count(newVal) { console.log('count changed:', newVal) } } } <!-- Composition API - 逻辑内聚 --> <script setup> import { ref, computed, watch } from 'vue' const count = ref(0) const doubled = computed(() => count.value * 2) const increment = () => count.value++ watch(count, (newVal) => { console.log('count changed:', newVal) }) </script> 实用技巧 1. ref vs reactive 该用哪个? // primitive types (String, Number, Boolean) -> ref const name = ref('BvBeJ') const age = ref(18) // objects/arrays -> reactive const user = reactive({ name: 'BvBeJ', skills: ['Go', 'Rust', 'C++'] }) // 或者对象也用 ref,通过 .value 访问 const user = ref({ name: 'BvBeJ' }) user.value.name = 'New Name' // 需要 .value 我的习惯: 简单类型用 ref,复杂对象用 reactive。TypeScript 类型推导更清晰。 ...

2026年4月3日 · 2 分钟 · BvBeJ