Sylar C++高性能服务器学习记录07 【协程模块-知识储备篇】

早在19年5月就在某站上看到sylar的视频了,一直认为这是一个非常不错的视频,由于本人一直是自学编程,基础不扎实,也没有任何人的督促,没能坚持下去,每每想起倍感惋惜。恰逢互联网寒冬,在家无事,遂提笔再续前缘。

为了能更好的看懂sylar,本套笔记会分两步走,每个系统都会分为两篇博客。
分别是【知识储备篇】和【代码分析篇】
(ps:纯粹做笔记的形式给自己记录下,欢迎大家评论,不足之处请多多赐教)
QQ交流群:957100923


协程模块-知识储备篇

知识点01: 理解【void** array = (void**)malloc(sizeof(void*) * size);】

背景:

在sylar中有这么一行代码:void** array = (void**)malloc(sizeof(void*) * size);你理解它的含义吗?
我敢说大部分人其实并不理解,甚至有部分工作几年的可能只是:“会用!但是没有深思过。”
屏幕前的你觉得该如何解释以上代码呢?

由于此知识点比较多,以下是我为解释这行代码所做的准备。
后面的知识你可能会觉得有点突兀,但是我希望你能看下去。
正如学习方法一样:
1.有些人会用倒推法学习,从表象一层一层往里剥。
2.有些人会用公式法学习(比如化学,将最基本的物理现象抽象成化学现象,再抽象成公式学习,这其实很适合当今应试教育的大环境)
3.我属于愚公类型,喜欢从最原始的往上学(这其实对于当今应试教育环境下是比较吃亏的)


1.【名词】与【动词】:

先要解释一下什么是【名词】,什么是【动词】:
【名词】主要表示人、事物、地点或抽象概念的名称。
【动词】则表示人或事物的动作、存在或变化。

举几个例子:
【名词】:歌曲、舞蹈、篮球…
【动词】:唱、跳、rap、打篮球…


2.【int a = 1;】是【名词】还是【动词】?

1.大部分人都会说:有一个值为1的整型变量a。【名词】
2.少部分人会说:让计算机在栈空间上,开辟一块名称为a,大小为sizeof(int)个字节的空间,将这块空间的值设置为1。【动词】

显然1号说法将int a = 1;看做一个【名词】,理解上是不深刻的。2号说法起码将其理解为【动词】了,而且有了内存堆栈的概念了,但是描述的依旧很欠缺,不能描述a的存在,CPU不知道什么是名称为a,CPU只知道具体地址。

3.我说:int a = 1;首先对于计算机来说这是一个动作,所以是【动词】。
其次我们写的是C++语言,是写给C++编译器看的,不是直接给CPU下达指令的。

所以这行代码的解释:
告诉编译器,请将字符a与一个栈内存的地址,假设是:[0x986FA]绑定映射关系
让CPU在内存地址为[0x986FA]后面开辟4或8个字节的空间,(具体字节数是多少比较复杂,后面解释)
将这段内存的值设置为1。

总结:
很明显,我们面向的是C++编译器,这样就能解释a的存在了,因为编译器会将a和对应地址做映射。
很多人说 a就是地址,却一直不解释为什么,而我就是因此困扰了好多年。

(ps:本人属于不太聪明的一类人,以上问题确实困扰了我好多年,如今的解释也只是如今的解释。)


3.内存地址的最大表示范围是多少?(一条街上最多能有几个门牌号,门牌号设计成百位还是千位还是更高?)

要确定内存地址的最大表示范围,我们就要了解CPU和内存之间是怎么交互的。

首先CPU与内存交互需要三个总线分别是:【控制总线】【地址总线】【数据总线】。
【控制总线】的作用:告诉CPU是读取内存的操作还是写入内存的操作。
【地址总线】的作用:告诉CPU要操作的内存地址。
【数据总线】的作用:CPU一次数据传输能传输的最大信息。

何为总线?总线就是由若干根线捆在一起就叫总线。
如果说一根电线根据通电断电能表示0和1,那么他最多能展示2种状态。
如果有两根线,那么他最大能表示的状态就是2的2次方就是最多展示4个状态。
32根线就是2的32次方,就是4G,也就是最多展示4G个状态。
64根线就是2的64次方,就是17179869184G,也就是最多展示这么多的状态。

所以如果【地址总线】由32根电线捆成一股,那么他就最多能给内存编2的32次方=4G个号码牌(内存地址)。
如果【数据总线】由32根电线捆成一股,那么他就能一次给CPU传输32个电信号。

CPU内部有许多寄存器,寄存器是用来存放准备运算的数据的,寄存器的大小决定了CPU每次运算能处理的最大信息。
也就是如果一个寄存器有32个格子,那么他一次能处理32位的数据,也就是4字节的数据。

好!我们描述一下CPU从内存读取数据的流程:
1.控制总线告诉CPU当前是从内存取数据的操作
2.地址总线将地址信息传递给CPU,告诉它要取的数据在内存中的地址
3.CPU将对应地址编号存储到对应寄存器中,然后进行取数据的操作
4.CPU通过数据总线将数据从内存中获取到,并将数据放到对应寄存器中用于后续的运算

了解完以上信息后我们再来谈一个地址的大小是由谁决定的?注意我这里说的是地址的大小,不是指针哦。
如果CPU要从内存中取一个数据,那么CPU需要通过地址总线获取到地址:
1.如果CPU中寄存器是32位的,地址总线也有32根,那么OK刚刚好,地址总线一次传递的信息就能正好让一个寄存器接收,那么一个地址的大小就是32位也就是4个字节。
2.如果寄存器是64位的但是地址总线只有32根,那么一次传递的信息也就只有32位,所以一个地址的大小依旧是32位也就是4个字节。
所以内存地址的最大表示范围目前看来只需要考虑CPU内寄存器的大小地址总线的大小,两者取小的就行。
但是如果有以下使用指针的情况:

int a = 1;
int* p = &a;

我们站在C++编译器的角度来看,这两行代码大致会有以下操作:
0.假设我们现在一个int大小是8个字节(就是64位)。
1.在栈内存中开辟8个字节的空间,假设起始地址是[0x09FA]
2.将这段内存的内容设置为1(也就是31个0和一个1)
3.将字符’a’与[0x09FA]绑定映射关系
4.将字符’a’打上int类型标签
5.在栈内存中开辟8个字节的空间,假设起始地址是[0x08FB]
6.将这段内存的内容设置为 0x09FA (也就是变量a的地址起始编号)
7.将字符’p’与[0x8FB]绑定映射关系
8.将字符’p’打上int*类型的标签

我们站在CPU的角度来看,这两行代码大致会有以下操作:
先转换为汇编:

mov dword ptr [a], 1   ; 初始化变量a
lea eax, [a]           ; 将x的地址加载到eax寄存器
mov dword p [p], eax   ; 将eax寄存器的内容(即a的地址)存储到p中

0.假设现在寄存器大小和数据总线根数和地址总线根数一致都是64位也就是8字节
1.控制总线标记现在为写入操作
2.CPU通过地址总线找到地址内存地址[0x09FA]
3.CPU通过数据总线将数字1传送到对应地址,覆盖内容
4.控制总线标记现在为写入操作
5.CPU根据地址总线获取到要读取的内存地址[0x08FB]
6.CPU通过数据总线将数据0x09FA写入到地址为[0x08FB]的内存中

通过以上步骤分析,特别是站在CPU角度看,第5和第6步可以看出,数据总线会将表示地址的数据传递到指定内存中。所以在使用指针的情况下,数据总线也将参与地址的传递

总结:内存地址的最大表示范围 和【数据总线】、【地址总线】、【寄存器】的位数息息相关!他们的最小值确定了 内存地址的位数,也就确定了该环境下指针的大小
在这里插入图片描述


4.分析【int* array = (int*)malloc(sizeof(int) * 10);】

在知道指针大小(内存地址位数)是如何计算的之后,我们来看这行代码。
0.在c++中,指针的大小和int类型的大小是一样的
1.使用malloc分配了10个大小为int大小的空间
2.由于malloc是直接调用系统的内存分配方法,所以没有对应的类型标记,所以需要强制转换类型为int*
3.所以这里相当于分配了一个数组,该数组中的每一个值都表示一个int类型。


5.分析【void** array = (void**)malloc(sizeof(void*) * 10);】

分析这行代码的时候,其实只要替换4中的 int 为 void* 就可以了,void表示无类型,*表示指针类型。
所以这里相当于分配了一个数组,该数组中的每个值都表示一个内存地址。


6.知识点01总结

以上弯弯绕绕一大堆,多少有些跑题,但是C++就是这样,不清楚指针的时候代码中任何一个"*"符号就会阻碍你的理解。
要想读懂代码,就要搞明白指针。
想要搞明白指针,就要搞清楚内存地址。
想要搞清楚内存地址,就要搞清楚CPU是如何操作内存的。
要想搞清楚CPU是如何操作内存的,就要搞清楚【寄存器】、【数据总线】、【地址总线】、【控制总线】的关系。

这里也是泛泛而谈,有机会我会详细的出一个【汇编系列】。


知识点02:【backtrace】

在Linux上的C/C++编程环境下,我们可以通过如下三个函数来获取程序的调用栈信息。

#include <execinfo.h>
 
/* Store up to SIZE return address of the current program state in
   ARRAY and return the exact number of values stored.  */
int backtrace(void **array, int size);
 
/* Return names of functions from the backtrace list in ARRAY in a newly
   malloc()ed memory block.  */
char **backtrace_symbols(void *const *array, int size);
 
/* This function is similar to backtrace_symbols() but it writes the result
   immediately to a file.  */
void backtrace_symbols_fd(void *const *array, int size, int fd);

知识点03:【std::atomic】

在多线程(协程)编程中,当多个线程(协程)同时访问同一块数据时,可能会导致数据竞争和不确定的行为。
举例:

#include <iostream>
#include <thread>
#include <mutex>
 
int counter = 0;
 
void incrementCounter(){
    for (int i = 0; i < 100000; ++i){
        counter++;
    }
}
 
int main(int argc, char** argv){
    std::thread t1(incrementCounter);
    std::thread t2(incrementCounter);
 
    t1.join();
    t2.join();
 
    std::cout << counter << std::endl;
 
    return 0;
}

可以看到以下输出:

140418

在线程模块中我们有遇到过这个情况,我们的处理方式就是加锁。例如:

int counter = 0;
std::mutex mutex;
 
void incrementCounter(){
    for (int i = 0; i < 100000; ++i){
        std::lock_guard<std::mutex> lock(mutex);
        counter++;
    }
}
 
int main(int argc, char** argv){
    std::thread t1(incrementCounter);
    std::thread t2(incrementCounter);
 
    t1.join();
    t2.join();
 
    std::cout << counter << std::endl;
 
    return 0;
}

以下是输出:

200000

当然也可以使用 std::atomic 来实现

#include <atomic>
 
std::atomic<int> counter(0);
 
void incrementCounter(){
    for (int i = 0; i < 100000; ++i)
    {
        counter++;
    }
}
 
int main(int argc, char** argv){
    std::thread t1(incrementCounter);
    std::thread t2(incrementCounter);
 
    t1.join();
    t2.join();
 
    std::cout << counter << std::endl;
 
    return 0;
}

可以看到也能正确输出:

200000

知识点04:【ucontext】

在头文件< ucontext.h > 中
定义了两个结构类型,mcontext_t和ucontext_t
和四个函数:getcontext(),setcontext(),makecontext(),swapcontext()
利用它们可以在一个进程中实现用户级的线程切换。

getcontext获取当前上下文

//初始化ucp结构体,将当前的上下文保存到ucp中

int getcontext(ucontext_t *ucp);

setcontext设置当前上下文

//设置当前的上下文为ucp,setcontext的上下文ucp应该通过getcontext或者makecontext取得,如果调用成功则不返回。
//如果上下文是通过调用getcontext()取得,程序会继续执行这个调用。
//如果上下文是通过调用makecontext取得,程序会调用makecontext函数的第二个参数指向的函数,
//如果func函数返回,则恢复makecontext第一个参数指向的上下文第一个参数指向的上下文context_t中指向的uc_link.
//如果uc_link为NULL,则线程退出。

int setcontext(const ucontext_t *ucp);

makecontext创建一个新的上下文

//makecontext修改通过getcontext取得的上下文ucp(这意味着调用makecontext前必须先调用getcontext)。
//然后给该上下文指定一个栈空间ucp->stack,设置后继的上下文ucp->uc_link.
//当上下文通过setcontext或者swapcontext激活后,执行func函数,argc为func的参数个数,后面是func的参数序列。
//当func执行返回后,继承的上下文被激活,如果继承上下文为NULL时,线程退出。

void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);

swapcontext切换上下文

//保存当前上下文到oucp结构体中,然后激活upc上下文。
//如果执行成功,getcontext返回0,setcontext和swapcontext不返回;
//如果执行失败,getcontext,setcontext,swapcontext返回-1,并设置对于的errno.

int swapcontext(ucontext_t *oucp, ucontext_t *ucp);

ucontext实现的一个简单的例子:

#include <stdio.h>
#include <ucontext.h>
#include <unistd.h>
 
int main(int argc, const char** argv){
    ucontext_t context;
 
    getcontext(&context);
    puts("Hello world");
    sleep(1);
    setcontext(&context);
    return 0;
}

可以发现以下输出:

Hello world
Hello world
Hello world
Hello world
Hello world
Hello world
Hello world
^C

我们可以看到,程序在输出第一个“Hello world"后并没有退出程序,而是持续不断的输出”Hello world“。
其实是程序通过getcontext先保存了一个上下文,
然后输出"Hello world",
在通过setcontext恢复到getcontext的地方,
重新执行代码,所以导致程序不断的输出”Hello world“。
有点汇编中的jmp指令的味道。

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

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

相关文章

vim 插件01:插件管理神器pathogen

1、pathogen简介 Vim 插件 pathogen 是一款历史比较悠久的 Vim 插件管理器。Pathogen 的主要功能是提供一种模块化的方式来管理和加载 Vim 插件。说人话&#xff1a;vim是一款管理各类插件的插卡&#xff0c;使用它会让插件的安装和使用非常方便。 以下是 Pathogen 的主要特点…

【大模型应用篇5】应对裁员潮,突发奇想,打造“收割offer”智能体.......

前段时间飞书大裁员, 不禁让人感到危机四伏,加上《【大模型应用篇4】普通人构建智能体的工具》之前文章介绍了普通人打造智能体的工具, 这节课就带大家利用字节产品coze构建“程序员智能体”, 方便应对裁员,随时做好找工作的准备.打造一款面试智能体,方便各位程序员面试, 这个智…

错误代码126:加载d3dcompiler_43.dll失败,分享多种解决方法

在正常使用电脑的过程中&#xff0c;当我尝试启动并运行一款心仪的游戏时&#xff0c;系统却突然弹出一个令人困扰的错误提示“错误代码126:加载d3dcompiler_43.dll失败”&#xff0c;它会导致游戏无法正常运行。为了解决这个问题&#xff0c;我经过多次尝试和总结&#xff0c;…

22年全国职业技能大赛——Web Proxy配置(web 代理)

前言&#xff1a;原文在我的博客网站中&#xff0c;持续更新数通、系统方面的知识&#xff0c;欢迎来访&#xff01; 系统服务&#xff08;22年国赛&#xff09;—— web Proxy服务&#xff08;web代理&#xff09;https://myweb.myskillstree.cn/114.html 目录 RouterSrv …

OGG extract进程占据大量虚拟内存导致服务器内存异常增长分析

现象 oracle服务器一节点内存&#xff0c;一个月来持续升高&#xff0c;近一月上涨10%左右。 问题分析 OS内存使用情况 使用内存最大的10个进程如下&#xff0c;PID为279417占用最大的内存。 查询279417&#xff0c;发现是ogg相关进程。 发现ogg的extract进程占用了大量的虚拟内…

软件测试(Web自动化测试)(二)

一.Selenium WebDriver的基本应用 &#xff08;一&#xff09;安装浏览器驱动 1.关闭浏览器的自动更新功能 以Windows7&#xff08;64位&#xff09;操作系统为例&#xff0c;讲解如何关闭Chrome浏览器的自动更新。首先按下快捷键“WinR”&#xff0c;打开运行对话框&#x…

【备战软考(嵌入式系统设计师)】02-计算机指令

指令集 我们计算机要执行程序&#xff0c;本质上是执行一条条的指令&#xff0c;而指令是从指令集中取出的&#xff0c;目前常见的指令集有CISC&#xff08;Complex Instruction Set Computer&#xff0c;复杂指令集&#xff09;和RISC&#xff08;Reduced Instruction Set Co…

2024最新智慧医疗智慧医院大数据展示,医院数据采集概况、医院指标分析、医院就诊趋势分析等。源代码免费下载。

系列文章目录 【复制就能用1】2分钟玩转轮播图,unslider的详细用法 【复制就能用2】css实现转动的大风车&#xff0c;效果很不错。 【复制就能用3】2分钟自己写小游戏&#xff1a;剪刀石头布小游戏、扫雷游戏、五子棋小游戏 【复制就能用4】2024最新智慧医疗智慧医院大数据…

c++并查集

文章目录 前言一、并查集1、并查集原理2、并查集实现3、并查集应用1.省份数量2.等式方程的可满足性 前言 一、并查集 1、并查集原理 在一些应用问题中&#xff0c;需要将n个不同的元素划分成一些不相交的集合。开始时&#xff0c;每个元素自成一个单元素集合&#xff0c;然后…

应急行业的智能安全帽(高端)

前面介绍了低端、中端安全帽&#xff0c;接着再讲讲高端安全帽。做高端安全帽的企业非常少&#xff0c;估计一只手都数的出来。确实也和智能安全帽这个领域体量有关系&#xff0c;并且他有一个新的“劲敌”——智能眼镜从其他领域瓜分原属于他的市场&#xff0c;这些都是题外话…

SpringBoot引入Layui样式总是出现404

一般出现Layui样式文件如css&#xff0c;js404的错误 解决方案 &#xff08;1&#xff09;首先将其中的静态资源下载resources/static中 &#xff08;2&#xff09;在启动类中重写方法 package com.gq.booksystem;import org.mybatis.spring.annotation.MapperScan; import …

【ETAS CP AUTOSAR工具链】RTE层基本概念与开发流程

本篇文章续接上篇文章【ETAS CP AUTOSAR工具链】基本概念与开发流程&#xff0c;继续按上篇文章描述的ETAS CP工具链进行开发的基本框架&#xff0c;讲述了“RTE集成与配置”这部分的基本概念与开发流程。 RTE&#xff08;Runtime Environment&#xff09;处于应用层与基础软件…

【Godot4.2】自定义Todo清单类 - myTodoList

概述 在写myList类的时候&#xff0c;就想到可以写一个类似的Todo清单类。 基础思路 本质还是在内部维护一个数组&#xff0c;在其基础上进行增删改查操作的封装为了方便存储数据&#xff0c;编写一个自定义内置类TodoItem&#xff0c;内部数组就变成了Array[TodoItem]类型的…

Git | 远程操作

Git | 远程操作 文章目录 Git | 远程操作0、分布式版本控制系统概念1、创建远程仓库2、克隆远程仓库https方式ssh方式 3、推送至远程仓库4、本地拉取远程仓库5、配置Git忽略特殊文件给命令配置别名 6、标签管理创建标签操作标签 0、分布式版本控制系统概念 Git是一个分布式版本…

Git--分布式版本控制系统

目录 一、理解分布式版本控制系统二、远程仓库三、克隆远程仓库四、向远程仓库推送五、拉取远程仓库六、配置Git七、给命令配置别名八、创建标签九、操作标签 一、理解分布式版本控制系统 我们⽬前所说的所有内容&#xff08;⼯作区&#xff0c;暂存区&#xff0c;版本库等等&a…

24深圳杯AC题完整思路+可执行代码+参考论文!!!!

比赛题目的完整版思路可执行代码数据参考论文都会在第一时间更新上传的&#xff0c;大家可以参考我往期的资料&#xff0c;所有的资料数据以及到最后更新的参考论文都是一次付费后续免费的。注意&#xff1a;&#xff08;建议先下单占坑&#xff0c;因为随着后续我们更新资料数…

three.js 学习笔记 | 光线投射技术 - 包围盒(碰撞检测)

文章目录 three.js 学习笔记光线投射技术实现3D场景交互事件 THREE.Raycaster坐标系的转换案例&#xff1a;选中的模型变为红色 包围盒Box3 - 碰撞检测AABB包围盒辅助器Box3Helper案例1&#xff1a;创建AABB包围盒/包围球computeBoundingBox与boundingBox 搭配使用&#xff0c;…

【数据结构】二叉树(带图详解)

文章目录 1.树的概念1.2 树的结构孩子表示法孩子兄弟表示法 1.3 相关概念 2.二叉树的概念及结构2.1 二叉树的概念2.2 数据结构中的二叉树-五种形态2.3 特殊的二叉树2.4 二叉树的存储结构顺序存储链式存储 2.5 二叉树的性质 3. 堆3.1 堆的定义3.2 堆的实现堆的结构堆的插入向上调…

springcloud按版本发布微服务达到不停机更新的效果

本文基于以下环境完成 spring-boot 2.3.2.RELEASEspring-cloud Hoxton.SR9spring-cloud-alibaba 2.2.6.RELEASEspring-cloud-starter-gateway 2.2.6.RELEASEspring-cloud-starter-loadbalancer 2.2.6.RELEASEnacos 2.0.3 一、思路 实现思路&#xff1a; 前端项目在请求后端接…

SVN--基本原理与使用(超详细)

目录 一、SVN概述二、SVN服务端软件安装三、SVN服务端配置四、SVN客户端软件安装与使用五、SVN三大指令六、SVN图标集与忽略功能6.1 图标集6.2 忽略功能 七、SVN版本回退八、SVN版本冲突九、SVN配置多仓库与权限控制9.1 配置多仓库9.2 权限控制 十、服务配置与管理十一、模拟真…
最新文章