协程必知必会-系列4-协程本地变量

文章目录

  • 协程本地变量
  • 相关结构体
  • 实现原理
  • 代码实现
  • 代码示例
  • 思考题

协程本地变量

在上一篇文章中,我们介绍了如何通过协程来实现批量并发执行,本篇文章将向大家介绍如何在协程的基础之上,实现协程本地变量。

注意:「为了减轻大家的阅读负担,在文章中只展示必要的代码,和当前讲解内容无关的代码在代码块中采用…进行忽略」。

完整的代码,已经开源在github上,地址为:https://github.com/wanmuc/MyCoroutine 。

在开源库中协程本地变量涉及的代码文件如下所示,文章后续代码的出处就不再说明。

.
├── common.h  // LocalVariable结构体定义
├── localvariable.cpp  // 协程本地变量核心函数实现
├── localvariable.h  // 协程本地变量模版类封装
└── mycoroutine.h // 协程本地变量核心函数声明

相关结构体

协程本地变量该如何实现呢?其实实现原理和线程本地变量一样。

就是使用封装的变量的内存地址作为key,且key的值是唯一的。

虽然所有的协程中拿到的key的值都是相等的,但是在不同的协程中通过这个key可以读写到不同的值。

具体如何实现呢?先来看一下协程本地变量辅助结构体LocalVariable。

// 协程本地变量辅助结构体
typedef struct LocalVariable {
  void *data{nullptr};
  function<void(void *)> free{nullptr};  // 用于释放本地协程变量值的内存
} LocalVariable;

在LocalVariable结构体中只有两个成员变量,一个是data,一个是free,data是指向协程本地变量值的内存指针,free是用于释放协程本地变量值内存的函数。

单有LocalVariable是不够的,还需要在协程核心结构体Coroutine中新增一个成员变量local。

// 协程结构体
typedef struct Coroutine {
  ...
  unordered_map<void *, LocalVariable> local;  // 协程本地变量映射map,key是协程变量的内存地址
  ...
} Coroutine;

local变量是一个unordered_map,「它的key就是一个void*的通用指针,value则是LocalVariable类型的变量」。

实现原理

本小节来介绍一下实现原理,协程本地变量是通过this指针,去不同协程的local中索引不同的LocalVariable类型的变量。

然后再进行读写操作的,从而实现在不同的协程内操作协程本地变量的相互隔离。

下面的简图将使这个逻辑变得清晰易懂。

在这里插入图片描述

代码实现

协程本地变量的实现涉及到2个类,一个是Schedule类,一个CoroutineLocal模版类。

在Schedule类中新增了2个函数的声明。

// 协程调度器
class Schedule {
 public:
  ...
  void LocalVariableSet(void *key, const LocalVariable &local_variable);  // 设置协程本地变量
  bool LocalVariableGet(void *key, LocalVariable &local_variable);        // 获取协程本地变量
  ...
};

LocalVariableSet和LocalVariableGet函数分别用于设置和获取协程本地变量的值,是核心的底层函数。它们的实现如下所示。

void Schedule::LocalVariableSet(void* key, const LocalVariable& local_variable) {
  assert(not is_master_);
  auto iter = coroutines_[slave_cid_]->local.find(key);
  if (iter != coroutines_[slave_cid_]->local.end()) {
    iter->second.free(iter->second.data);  // 之前有值,则要先释放空间
  }
  coroutines_[slave_cid_]->local[key] = local_variable;
}

bool Schedule::LocalVariableGet(void* key, LocalVariable& local_variable) {
  assert(not is_master_);
  auto iter = coroutines_[slave_cid_]->local.find(key);
  if (iter == coroutines_[slave_cid_]->local.end()) {
    int32_t relate_bid = coroutines_[slave_cid_]->relate_bid;
    if (relate_bid == kInvalidBid) {  // 没有关联的Batch,直接返回false
      return false;
    }
    int32_t parent_cid = batchs_[relate_bid]->parent_cid;
    iter = coroutines_[parent_cid]->local.find(key);
    if (iter == coroutines_[parent_cid]->local.end()) {  // 父从协程中也没查找到,直接返回false
      return false;
    }
  }
  local_variable = iter->second;
  return true;
}

LocalVariableSet函数的逻辑如下:

  • 在local中查询,如果已经存在值,则先调用free函数来释放空间。
  • 最后把协程本地变量最新的值,保存在local中。

LocalVariableGet函数的逻辑如下:

  • 在local中查询,如果查询到了,则直接返回值。
  • 查询不到,则判断当前协程是否为批量并发执行的子从协程。
  • 如果是子从协程,再在父从协程中查询,查询到了,则直接返回值。
  • 父从协程中查询不到,则返回不存在。

注意:「想了解批量并发执行,可以移步阅读本系列的第三篇文章」

从易用性的角度出发,协程本地变量做了易用性封装,相关的代码如下所示。

// 协程本地变量模版类封装
template <typename Type> 
class CoroutineLocal {
public:
  CoroutineLocal(Schedule &schedule) : schedule_(schedule) {}
  static void free(void *data) {
    if (data)
      delete (Type *)data;
  }

  Type &Get() {
    MyCoroutine::LocalVariable local_variable;
    bool result = schedule_.LocalVariableGet(this, local_variable);
    assert(result == true);
    return *(Type *)local_variable.data;
  }
  // 重载类型转换操作符,实现协程本地变量直接给Type类型的变量赋值的功能
  operator Type() {
    return Get();
  }
  // 重载赋值操作符,实现Type类型的变量直接给协程本地变量赋值的功能
  CoroutineLocal &operator=(const Type &value) {
    Set(value);
    return *this;
  }

private:
  void Set(Type value) {
    Type *data = new Type(value);
    MyCoroutine::LocalVariable local_variable;
    local_variable.data = data;
    local_variable.free = free;
    schedule_.LocalVariableSet(this, local_variable);
  }

private:
  Schedule &schedule_;
};

CoroutineLocal是一个模版类,只有一个schedule_成员变量,是指向Schedule类对象的引用。

CoroutineLocal类中的Get和Set函数,是对LocalVariableGet和LocalVariableSet函数调用的简单封装。

CoroutineLocal类还重载了类型转换操作符和赋值操作符,「从而实现CoroutineLocal类对象和Type类型变量的相互赋值」。

代码示例

最后我们来看一下协程本地变量使用的一个示例。

#include "mycoroutine.h"
#include "localvariable.h"
#include <iostream>

using namespace std;
using namespace MyCoroutine;

void LocalVar1(Schedule &schedule, CoroutineLocal<int32_t> &local_var,
               int &sum) {
  local_var = 100;
  schedule.CoroutineYield();
  assert(100 == local_var);
  sum += local_var;
}
void LocalVar2(Schedule &schedule, CoroutineLocal<int32_t> &local_var,
               int &sum) {
  local_var = 200;
  schedule.CoroutineYield();
  assert(200 == local_var);
  sum += local_var;
}

int main() {
  // 创建一个协程调度对象,并自动生成大小为1024的协程池
  Schedule schedule(1024);
  int sum = 0;
  CoroutineLocal<int32_t> local_var(schedule);
  schedule.CoroutineCreate(LocalVar1, ref(schedule), ref(local_var), ref(sum));
  schedule.CoroutineCreate(LocalVar2, ref(schedule), ref(local_var), ref(sum));
  schedule.Run();  // Run函数完成从协程的自行调度,直到所有的从协程都执行完
  cout << "sum = " << sum << endl;
  return 0;
}

上面的代码中,在main函数中创建了一个协程本地变量local_var,然后分别在两个不同的协程中对local_var进行读写操作,在两个不同的协程中分别读写到了不同的值。

思考题

如果实现的是协程互斥锁,该如何实现呢?在评论区给出你的想法。

本文是大厂后端技术专家万木春原创。作者更多技术干货,见下方的书籍。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/897519.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Docker基础部署

一、安装Ubuntu系统 1.1 新建虚拟机 打开VMware Workstation&#xff0c;选择文件->新建虚拟机->典型&#xff08;推荐T&#xff09;->安装程序光盘映像文件->输入虚拟的名字->一直下一步即可 安装程序光盘映像文件 注意&#xff1a;选择CentOS-7-x86_64-DVD-…

Springboot 使用EasyExcel导出Excel文件

Springboot 使用EasyExcel导出Excel文件 Excel导出系列目录&#xff1a;引入依赖创建导出模板类创建图片转化器 逻辑处理controllerservice 导出效果遗留问题 Excel导出系列目录&#xff1a; 【Springboot 使用EasyExcel导出Excel文件】 【Springboot 使用POI导出Excel文件】 …

大模型带来新安全机遇

当前网络空间安全面临攻击隐蔽难发现、数据泄露风险高和违法信息审核难等挑战。大模型展现出强大的信息理解、知识抽取、意图和任务编排等能力&#xff0c;为网络空间安全瓶颈问题提供了新的解决思路和方法。与此同时&#xff0c;大模型发展也催生了恶意软件自动生成、深度伪造…

vue 项目i18n国际化,快速抽离中文,快速翻译

国际化大家都知道vue-i18n 实现的&#xff0c;但是有个问题&#xff0c;就是繁杂的抽离中文字符的过程&#xff0c;以及翻译中文字符的过程&#xff0c;关于这个有些小工具可以希望可以帮到大家 1.安装vue-i18n npm i vue-i18n8.22.22.ElementUI多语言配置 在src目录下创建…

《Python基础教程》笔记(ch0-1)

前言 在Python生态系统中&#xff0c;各种包轮番登场&#xff0c;各种编码实践大行其道后又日渐式微。 引言 Python是什么&#xff1f;为何要使用它&#xff1f;官方宣传说&#xff1a;Python是一种面向对象的解释性高级编程语言&#xff0c;具有动态语义。 这句话的要点在…

Java网络编程-简单的API调用

Get请求 - 无参数 安装依赖库 首先需要安装一个库&#xff1a; Okhttp3&#xff0c;这是一个非常流行的 HTTP 库&#xff0c;可以简单、快速的实现 HTTP 调用。 安装 Okhttp3 的方式是在 pom.xml 文件中增加依赖&#xff1a; <!-- https://mvnrepository.com/artifact/c…

08 实战:色彩空间展示(本程序以视频为主)

程序效果如下: 我在这里讲解RGB和YCbCr的原理: 一、RGB颜色空间 1.1 基本概念 RGB颜色空间是一种最基础和常用的颜色表示方式,它基于人眼感知色彩的三原色原理。RGB分别代表: R(Red):红色G(Green):绿色B(Blue):蓝色通过这三种基本颜色的不同组合,可以产生人眼…

计算机毕业设计Spark+大模型动漫推荐系统 动漫视频推荐系统 漫画分析可视化大屏 漫画爬虫 漫画推荐系统 漫画爬虫 知识图谱 大数据

《Spark大模型动漫推荐系统》开题报告与任务书 一、引言 随着互联网技术的飞速发展&#xff0c;动漫产业的数据量急剧增长。用户面临着海量动漫作品的选择难题&#xff0c;如何从这些数据中高效地提取有价值的信息&#xff0c;为用户推荐符合其喜好的动漫作品&#xff0c;成为…

如何快速生成大量有意义的测试数据?

如何获取 MySQL 的测试数据&#xff0c;这是个很经典的问题&#xff0c;在开发、测试和性能优化的各个环节中&#xff0c;获取合适的测试数据都是必不可少的。MySQL 官方还特地提供了示例库 employees&#xff0c;用于测试用途&#xff0c;但 employees 并不是万能的&#xff0…

为您的 WordPress 网站打造完美广告布局 A5广告单元格插件

一个为 WordPress 网站量身定制的强大工具,它将彻底改变您展示广告的方式 灵活多变的布局设计 A5 广告单元格插件的核心优势在于其无与伦比的灵活性。无论您是想要创建整齐的网格布局,还是希望打造独特的不规则设计,这款插件都能满足您的需求。 自定义网格数量&#xff1a;从 2…

vue 页面导出gif图片 img 导出gif 超简单~ 可修改播放速度

1.首先需要新建一个文件件 新建gif文件夹。这两个文件在文章最后面需要可自提 2.出gif分为两种情况 第一种情况 页面是img标签&#xff0c;直接导出图片作为gif 第二种情况 页面是div标签&#xff0c;需要导出div里面的图片作为gif 2.1页面是img标签&#xff0c;直接导出图…

博弈论:博弈类型空间集合;三层博弈拓展式;

目录 博弈论:博弈类型空间集合 θ(Dss-1=1 )就是博弈类型空间集合; 一、博弈的基本要素 二、博弈的主要类型 三、博弈类型空间集合的构建 三层博弈拓展式: 博弈论:博弈类型空间集合 这的博弈类型空间集合:指一方选择的策略,用符号进行表达:SDss-2(θDss-1=1) = …

手机玩使命召唤21:黑色行动6?GameViewer远程玩使命召唤教程

使命召唤21&#xff1a;黑色行动 6这个第一人称射击游戏&#xff0c;将于10月25号上线&#xff01;如果你是使命召唤的老玩家&#xff0c;是不是也在期待这部新作&#xff1f;其实这个游戏不仅可以用电脑玩&#xff0c;还可以用手机玩&#xff0c;使用网易GameViewer远程就能让…

Golang | Leetcode Golang题解之第502题IPO

题目&#xff1a; 题解&#xff1a; func findMaximizedCapital(k, w int, profits, capital []int) int {n : len(profits)type pair struct{ c, p int }arr : make([]pair, n)for i, p : range profits {arr[i] pair{capital[i], p}}sort.Slice(arr, func(i, j int) bool {…

FileLink跨网文件摆渡系统:重构跨网文件传输新时代

在数字化浪潮的推动下&#xff0c;企业对于数据的高效利用和安全管理提出了前所未有的要求。面对不同网络环境间的文件传输难题&#xff0c;传统方法往往显得力不从心&#xff0c;不仅效率低下&#xff0c;还存在极大的安全隐患。而FileLink跨网文件摆渡系统的出现&#xff0c;…

http服务器的实现和性能测试

http服务器的实现 本文使用上一篇博文实现的epollreactor百万并发的服务器实现了一个使用http协议和WebSocket协议的WebServer。 完整代码请看我的github项目 1. 水平触发(Level Trigger)与边沿触发(Edge Trigger) 1.1 水平触发 水平触发是一种状态驱动机制。当文件描述符&a…

【学习AI-相关路程-mnist手写数字分类-python-硬件:jetson orin NX-自我学习AI-基础知识铺垫-遇到问题(1) 】

【学习AI-相关路程-mnist手写数字分类-python-硬件&#xff1a;jetson orin NX-自我学习AI-基础知识铺垫-遇到问题&#xff08;1&#xff09; 】 1、前言2、先行了解&#xff08;1&#xff09;学习基础知识-了解jetson orin nx 设备&#xff08;2&#xff09;学习python&AI…

AUTOSAR_EXP_ARAComAPI的6章笔记(2)

☞返回总目录 相关总结&#xff1a;AutoSar AP CM实例说明符的使用方法总结 6.2 实例说明符的使用方法 一、InstanceSpecifier 的概念 InstanceSpecifier 是在 [3] 中定义的一个核心概念&#xff0c;它由符合特定模型元素绝对路径的模型元素 shortName 组成&#xff0c;表现…

自定义中文排序在Java中的实现与注意事项

目录 前言1. 基本知识2. 实战 前言 #1024程序员节 | 征文# 对于Java的基本知识推荐阅读&#xff1a; java框架 零基础从入门到精通的学习路线 附开源项目面经等&#xff08;超全&#xff09;【Java项目】实战CRUD的功能整理&#xff08;持续更新&#xff09; 原先的Java中文…

Ubuntu(22.04)本地部署Appsmith

Ubuntu&#xff08;22.04&#xff09;安装Appsmith 简要介绍 Appsmith 是一个开源的低代码开发平台&#xff0c;旨在帮助开发者和非开发者快速构建定制化的内部应用程序和管理工具。通过直观的拖拽界面和丰富的预配置组件&#xff0c;Appsmith 让用户无需编写大量代码即可创建…