不积小流,无以成江海

海纳百川,有容乃大

获取

看一下 pageheap 针对的是什么问题? 堆调试工具——pageheap的使用和原理分析

pageheap 和 gflags 什么关系?前者被包含到了后者中。

This version of GFlags includes the functionality of PageHeap (pageheap.exe), a tool that enables heap allocation monitoring in Windows.

如何安装 gflags?安装 WinDbg

GFlags is included in the Debugging Tools for Windows 10 (WinDbg).

如何 获取 WinDbg

  • 在安装过程中,通过 win 10 sdk 的方式(无论是通过 msvc2019 还是直接安装 win10 sdk)都没有成功。

  • 最终单独安装的 Windows Driver Kit (WDK)

    Step 2: Install WDK for Windows 10, version 1903

阅读全文 »

在项目中弃用 glog,改用 spdlog。花了三个小时,跟了一遍 example.cpp 中提到 xxx_example(),当底层跳转要进入 fmt 时就不再追踪。

学习 spdlog 源码

  1. 在单参数构造函数中明确使用 explicit 关键词,避免隐式构造,避免隐式类型转换。
  2. log.h 存放模板类声明,但在头文件末尾 #include log_impl.h,后者存放模板类的定义
  3. 颜色默认只针对 level 关键词,不同级别预设了不同的色彩。但也可以使用 %^ %$ 划定收尾。
  4. 线程安全,支持多线程
  5. 注册表工厂模式?
  6. fmt/spdlog 如何输出和二进制的?spdlog::to_hex 与 fmt 自身的二进制格式化输出有什么不同? fmt 对自定义类型的支持
  7. spdlog 本身是线程安全的,async_ 定位?
  8. spdlog 开启 FMT_HEADER_ONLY 预编译宏,使用 fmt
  9. spdlog 以 head-only 方式使用
阅读全文 »

asio 封装了平台差异性,在 windows 下本质就是 IOCP。使用 IOCP 请看这里

ASIO

Your program will have at least one I/O execution context, such as an asio::io_context object, asio::thread_pool object, or asio::system_context. This I/O execution context represents your program’s link to the operating system’s I/O services.

理解 C++ Executor 的设计理念,查看 asio Executor requirements,就只是朴素的 callable thing 的执行器概念

基于 Asio 的 C++ 网络编程

Boost asio 官方教程

Documentation (non-Boost)

查看 asio 源码,可以学到“错误代码 vs 异常”两种策略;可以学到“同步 vs 异步”两种接口形式。

如果依赖 IO 的结果,(单线程)异步 IO 和同步阻塞 IO 都要等到 IO 完成才能继续执行,那么异步的性能是否更好?如果是,为什么?—— 理解 iocp 之后是否就“豁然开朗”了呢

有一点也需要注意, 就是从 request 进来到完成, 应用内各个节点和调用流程都要支持异步 io 调用, 否则一个节点不支持, 就退化成多线程的解决方式。摘自

ps. 如果某个节点特别耗时,阻塞当前线程,其实也就退化了

异步IO模型需要一个消息循环,在消息循环中,主线程不断地重复“读取消息-处理消息”这一过程。摘自

在消息模型中,处理一个消息必须非常迅速,否则,主线程将无法及时处理消息队列中的其他消息,导致程序看上去停止响应。

要理解 asio,其实就是 windows 下的 iocp,就是要理解 Proactor

如果要在连续下载文件中使用 asio 异步模型,那么如何将下载后 sqlite3 入库改为嵌入 handler 中?

例子学习笔记

2019/8/29 16:07:03 因为多数函数都是模板,编码时信息提示本就是短板。结合 co_await 运算符使用时,更是得不到任何提示信息。只能“瞎写”参数数目和类型,编译报错 + 查看源码多次试验究竟怎么写入参。

daytime_client.cpp

结合 c++11 std::future 使用 asio 的异步接口,只是在局部提升效率:每个异步接口返回的 future,在其他异步接口中使用时要传入 future::get() 结果,**而非 future**。从全局来看依旧是阻塞的。

当然,有些业务场景恰好如此。一方面想改进每次同步 IO 的阻塞,因为两次 IO 之间可以做些其余工作;另一方面,下一次 IO 调用依赖前一次 IO 的结果。如果为了局部改进,整体改用「异步 + 回调」形式,开发耗时久:“回调地狱”真的很累,难写,看的人也累。

在上述场景中,如果没有 两次 IO 之间可以做些其余工作 的需求,其实再次“退化”改用同步接口即可。

协程

Boost ASIO supports 3 coroutine types:

无栈协程 ☆

Boost 库中的协程支持两种方式:

  • 一种是封装了 Boost.Coroutinespawn,是一个 stackful 类型的协程;
  • 一种是 asio 作者写出的 stackless 协程。

asio 的作者通过 Duff’s Device 技术 实现的无栈协程,用到了好多奇技淫巧。结合 coroutine 类 的手册和 官方 example 学习过程中有以下问题或心得:

std::function<> 模板约定的签名是无法适配默认参数的:即 void handler(asio::error_code, size_t len = 0) 是无法赋值给 std::function<void(asio::error_code)> 类型的。

一方面从 c++11 才开始支持 std::function<>,另一方面在 c++03 example 中 async_accept()/async_read_some()/async_write() 竟然全部可以使用回调对象:void operator()( asio::error_code ec = asio::error_code(), std::size_t length = 0) ,要知道 async_accpet() 的回调签名可是 void(asio::error_code)

几个关键的** 伪-关键词**,其实都是宏。宏是不支持断点调试的,除非手动把所有的宏展开。不过作为成熟的网络库,这些宏都是经过千锤百炼的,调试中根本无需展开。

达夫设备的 fall-through 理解起来太难了,我能理解简单的使用案例,但前述伪-关键词还是看不懂。查看 fallthrough

成员全部交给 std::shared_ptr 托管,因为函数对象需要频繁的拷贝:一方面拷贝开销不能太大;另一方面,回调重入时成员必须有效且正确。虽然 fork server(*this)() 浅拷贝,但实际上 所有 智能指针陆续通过 ptr.reset(new xx) 全部新申请了内存,而 socket_ 的浅拷贝本就是最佳解决方案。每个主动 socket 封装都携有一份用不到 acceptor_ 也只是个瑕不掩瑜的、无法避免的小问题。

刚刚介绍的协程,不需要任何编译器/底层库的支持。只使用 C 语言本身就支持的 Duff’s Device 技术就能实现。唯一的缺点是局部变量无法跨 yield 。所以所有变量都要定义为函数对象的成员变量。 另外需要把协程定义为函数对象,需要额外编写不少代码。

更多请参考:Boost中的协程—Boost.Asio中的coroutine类

有栈协程 ☆

Boost.Context 提供的协程包括两类:非对称型协程 asymmetric_coroutine 的和对称型协程 symmetric_coroutine,前者de协程知道唤醒自己的协程,当需要暂停的时候控制流转换给那个特定的协程;对称协程中所有的协程都是相等的,协程可以把控制流给任何一个其它的协程。

c++03 examplesc++11 examplesspawn 实例演示涉及的 Boost.Coroutine 库已经 被标记为 Deprecated,推荐使用 Boost.Coroutine2。尴尬的地方在于 asio::spawn() 并未就新的 Boost.Coroutine2 封装新的实现,只能凑合着使用 deprecated 特性。

Coroutines TS Support ☆☆☆

DEMO cpp17_examples

即将进入 c++20 的协程。二百多行代码就能写个聊天室,佩服。

Coroutines TS Support,这是唯一能找到的有效介绍,虽然内容不多,但说清楚了入参、出参的用法和意义。

Support for the Coroutines TS is provided via the awaitable class template, the use_awaitable completion token, and the co_spawn() function.

co_spawn

查看 co_spawn 函数的功能与签名:通过持有协程的返回值(第二个入参)以便 resume 协程。asio 库封装的协程,协程的调度对用户都是透明的(用户只提供了某些定制点,用户无需 resume 协程),完全由 co_spawn 底层实现。类比 thread,完全是 os 的定位。

Spawn a new coroutined-based thread of execution. 生成一个新的基于协程的执行线程。

对于用户,压下好奇心,不要执着于它是如何实现的,直接理解为“新启执行线程”是直观且无误的。专注业务而非库的功能。

对称协程只是非对称协程的一个特例,我们可以通过添加一个中立的第三方调度中心的方式将非对称协程转换成对称协程(只需要在所有协程“暂停”时将控制权转交给调度中心统一调度即可)引用来源

推测 co_spawn 内部就存在这么个“调度中心”。

coroutines_ts 中给出了协程的定义等。在 asio 中协程的显著标识是函数返回值:awaitable<void> foo()

The return type of a coroutine or asynchronous operation.

所以 co_spawn() 的第二个入参使用 lambda expression 时要显式地指出返回类型:要么 return xx 语句;要么 -> ret。因为缺省使用 void

-> ret, where ret specifiers the return type. If trailing-return-type is not present, the return type is implied by the function return statements (or void if it doesn’t return any value)

总结:协程是特异的函数,特征就是返回签名;协程也不能像普通函数那样直接调用,而是通过 co_spawn() 以便 suspend 后由调度中心 resume。

executor

在协程函数体的实现中,当需要 io_context 对象时,既可以通过函数参数传进来,也可以在函数内使用以下语句获取:

1
2
// asio::any_io_executor
auto executor = co_await this_coro::executor;

一度非常困惑两者的关系,context 和 executor 分别是什么概念

在 asio 中,execution context 是一个重量级的对象,不允许拷贝。executor 是对一个轻量级的句柄(handle)对象指向对应的execution context。

另外两个特殊值,co_spawn() 的第三个参数 asio::detached,和 async_xx() 的末尾参数 asio::use_awaitable,都是固定用法。

上述三个特殊值的类型定义都是空的,模板依赖其类型进行特例化,类型的定义反而不重要。

asio::use_awaitable 用于模板特例化之外有更多的用途吗?

顺序 co_spawn() 创建 1-2-3-4 协程,在各协程中 co_await 之前的逻辑也是 1-2-3-4 顺序执行!——这是个浅显的现象,了解协程的定义和实现后,这也是理所当然的:在第一次 co_await 时函数才返回。

Special Values

This completion token that can be passed as a handler to an asynchronous operation:

  • asio::use_awaitable
  • asio::detached
  • asio::use_future

asio::use_awaitable

The use_awaitable_t class, with its value use_awaitable, is used to represent the currently executing coroutine. This completion token may be passed as a handler to an asynchronous operation. For example:

1
2
3
4
5
awaitable<void> my_coroutine()
{
std::size_t n = co_await my_socket.async_read_some(buffer, use_awaitable);
...
}

When used with co_await, the initiating function (async_read_some in the above example) suspends the current coroutine. The coroutine is resumed when the asynchronous operation completes, and the result of the operation is returned.

asio::detached

The detached_t class is used to indicate that an asynchronous operation is detached. That is, there is no completion handler waiting for the operation’s result. A detached_t object may be passed as a handler to an asynchronous operation, typically using the special value asio::detached. For example:

my_socket.async_send(my_buffer, asio::detached);

asio::use_future

The use_future_t class is used to indicate that an asynchronous operation should return a std::future object. A use_future_t object may be passed as a handler to an asynchronous operation, typically using the special value asio::use_future. For example:

1
2
std::future<std::size_t> my_future
= my_socket.async_read_some(my_buffer, asio::use_future);

The initiating function (async_read_some in the above example) returns a future that will receive the result of the operation. If the operation completes with an error_code indicating failure, it is converted into a system_error and passed back to the caller via the future.

this_coro::executor

Awaitable object that returns the executor of the current coroutine.

constexpr executor_t executor;

协程里外的异常

1
2
3
4
5
6
7
#include <asio/redirect_error.hpp>

asio::error_code ec;
co_await async_write(socket, asio::buffer(msg), asio::redirect_error(use_awaitable, ec) );
if (ec)
{
}

redirect_error_t

Completion token type used to specify that an error produced by an asynchronous operation is captured to an error_code variable.

asio::redirect_err()缺点 无伤大雅

The redirect_error token transformation recovers the option to use the error_code interface, but it suffers from the same drawbacks that make pure error codes unappealing in the synchronous case.

co_await 和线程锁

协程中禁用线程锁!

不和谐的 / 疑惑

asio::async_read() 没有 asio::use_awaitable 版本的重载 (•_•)? boost beast 提供了

The secret of stackless coroutines is that they can suspend themselves only from the top-level function.

socket::async_read_some() 无法以 asio::streambuf 作为入参

1
2
3
4
5
6
7
8
9
10
11
12
auto size = response_.m_response_buf.size();
const auto remain = content_length - size;
//auto len = co_await asio::async_read(*ssocket_, response_.m_response_buf, asio::transfer_at_least(content_length - size),
// asio::redirect_error(asio::use_awaitable, ec));
for (size_t len = 0; !ec && len < remain; )
{
auto buf = response_.m_response_buf.prepare(std::min(remain, 1024ul));
auto bytes = co_await ssocket_->async_read_some(asio::buffer(buf),
asio::redirect_error(asio::use_awaitable, ec));
response_.m_response_buf.commit(bytes);
len += bytes;
}

在 msvc2015 中断点调试无法监视变量 (ÒωÓױ)!

如何编写协程类型

请移步 coroutine 学习总结

在 github 上我们能找到 protobuf 的 最新发布版本

在发布的压缩包中,我们只需要关注 protobuf-cpp-version.zip/tar.gz。有时 protobuf-version-win32.zip 也会让人疑惑,我们到底需不需要这个文件呢?让我们看一下其中的 readme.txt

This package contains a precompiled binary version of the protocol buffer compiler (protoc). This binary is intended for users who want to use Protocol Buffers in languages other than C++ but do NOT want to compile protoc themselves.

简单说,是给 C++ 开发者之外的码农用的。

但是,If you intend to use the included well known types,我们还是需要下载后者的。毕竟前者中 .proto 原型混杂在了大量的头文件中,而这里的原型文件一目了然。

二进制

在解析文件服务器上的历史 k 线时,碰到了比较特殊的问题:

阅读全文 »

强调:安装 IDE 时不要变更默认路径!否则,使用 vcpkg 磨难重重,vcpkg issue#12488我安装 Language English 就失败了,后面也就没尝试

从 visual studio 2015 的“选项”跳转到的中文网站,选择英文语言包下载到的其实是中文语言包,需要更换到对应的英文网站下载才是正确的。仍旧需要使用默认安装路径!

网速要好,偶尔也需要翻墙。如果使用系统自带的 powershell 碰到下载失败、下载慢,且版本陈旧。建议下载 powershell core 试一试,可能会有惊喜。 在 Windows 终端中设置代理 解决问题:$Env:https_proxy="http://127.0.0.1:10809"

使用 vcpkg 安装库后,如果较长时间未更新,再次更新时可能报错:

1
2
3
4
5
6
PS F:\vcpkg> .\vcpkg.exe update
Using local portfile versions. To update the local portfiles, use `git pull`.
Error: while loading boost-signals:
The port directory (F:\vcpkg\ports\boost-signals) does not exist
Error: failed to load port from F:\vcpkg\ports\boost-signals
Note: Updating vcpkg by rerunning bootstrap-vcpkg may resolve this failure.

此时按照提示重新执行多次 bootstrap-vcpkg 也是无效的,我们需要清理掉过时的库:

1
2
3
4
5
6
7
8
PS F:\vcpkg> .\vcpkg.exe list boost-signals
boost-signals2:x86-windows-static-md 1.71.0 Boost signals2 module
boost-signals:x86-windows 1.68.0 Boost signals module
PS F:\vcpkg> .\vcpkg.exe remove boost-signals:x86-windows
The following packages will be removed:
boost-signals:x86-windows
Removing package boost-signals:x86-windows...
Removing package boost-signals:x86-windows... done

之后就使用正常了:

1
2
3
4
5
6
7
8
PS F:\vcpkg> .\vcpkg.exe update
Using local portfile versions. To update the local portfiles, use `git pull`.
The following packages differ from their port versions:
abseil:x86-windows 2020-03-03-4 -> 2020-09-23#3
abseil:x86-windows-static-md 2020-03-03-4 -> 2020-09-23#3
asio:x86-windows 1.12.2 -> 1.18.0
asio:x86-windows-static-md 1.12.2-2 -> 1.18.0
boost-accumulators:x86-windows-static-md 1.71.0 -> 1.75.0

除特别备注,全部摘自 vcpkg: A C++ package manager for Windows, Linux and MacOS。此章节类似读书(博客)笔记。

阅读全文 »

streambuf

中文版 https://zh.cppreference.com/w/cpp/io/basic_streambuf,偶有误差,可参考英文版本纠正。

英文版 https://en.cppreference.com/w/cpp/io/basic_streambuf

类 basic_streambuf 控制字符序列的输入与输出。它包含下列内容并提供到它们的访问:

  1. 受控字符序列(The controlled character sequence),也称作 buffer,可包括:input sequence (also called get area) 用于缓冲输入操作;以及 output sequence (also called put area) 用于缓冲输出操作。

  2. 关联字符序列,又称作源(对于输入)或池(对于输出)。它可以是通过 OS API 访问的实体(文件、 TCP 接头、串行端口、其他字符设备),或者可以是能转译成字符源或池的对象( std::vector 、数组、字符串字面量)

受控制序列中的字符表示和编码可以异于关联序列中的字符表示,该情况下典型地用 std::codecvt 本地环境进行转换。常见的例子是通过 std::wfstream 对象访问 UTF-8 (或其他多字节编码)文件:受控制字符序列由 wchar_t 字符组成,但关联序列由字节组成。

阅读全文 »

automake 系列工具友好性的确不咋地,而且只限于 *inux 平台。跨平台还是 cmake 为佳,也有越来越多的项目在使用 cmake,比如 google 的 gRPC,比如 zlib 也支持 cmake。

如果只是说要编译三方库为自己所用,一般来说不需要掌握怎么编写 CMakeLists.txt;只有当自己的项目需要用 cmake 管理、组织编译时才需要弄懂 CMakeLists.txt 中的“语言”怎么编写。

使用 cmake

mkdir build && cd build && cmake .. && cmake --build . --target install

cmake --build . 在 linux 下等价于 make,在 windows 下等同于 msvc 生成解决方案

阅读全文 »

之前专门整理过 C++ 怎么写单例类,平日项目中涉及的场景大多简单,使用最基本的实现就完全能够应付过来。但是,还是有些复杂的场景需要考虑。

相互引用的单例们

也就是 singletonA、singletonB 两者的构造相互调用。

使用常用的局部静态变量方式(lazy_result_loop.cpp),也是懒汉模式一种:相互依赖初始化,死循环,阻塞

1
2
Object_2B_1::Object_2B_1() this:[00BFC14C] data_2b_1_ [1].
Object_2B_2::Object_2B_2() this:[00BFC154] data_2b_2_ [2].

使用全局静态变量方式(hungry_result_different.cpp),饿汗模式:不会死循环,但奇葩在 Debug、Release 模式执行结果不一致

代码摘抄自 BOOST 的 Singleton 模版详解,以上就是“2B 实现有问题”。虽然作者自述“BOOST 的实现如何规避问题”,但在 msvc2015 中实测其代码并未解决问题,依旧是阻塞结果。

阅读全文 »

自从看了蒋鑫的《Git权威指南》之后就开始使用Git Submodule功能,团队也都熟悉了怎么使用,多个子系统(模块)都能及时更新到最新的公共资源,把使用的过程以及经验和容易遇到的问题分享给大家。

Git Submodule功能刚刚开始学习可能觉得有点怪异,所以本教程把每一步的操作的命令和结果都用代码的形式展现给大家,以便更好的理解。

对于公共资源各种程序员的处理方式

每个公司的系统都会有一套统一的系统风格,或者针对某一个大客户的多个系统风格保持统一,而且如果风格改动后要同步到多个系统中;这样的需求几乎每个开发人员都遇到,下面看看各个层次的程序员怎么处理:

假如对于系统的风格需要几个目录:css、images、js。

普通程序员,把最新版本的代码逐个复制到每个项目中,如果有N个项目,那就是要复制N x 3次;如果漏掉了某个文件夹没有复制…@(&#@#。

文艺程序员,使用Git Submodule功能,执行:git submodule update,然后冲一杯咖啡悠哉的享受着。

阅读全文 »

在使用 git 版本仓库时,难免会出现默认配置不符合我们需要的地方。尤其是在 windows 上使用 git 时,不如意的地方就会更频繁的出现。这时需要我们自己手动微调 git 的默认配置,汇总如下:

别名

经常输入 git status 查看库当前状态,如果终端环境不支持 tab 自动补全,频繁输入 status 完整单词就会变得很繁琐,考验耐心;即便支持自动补全,git statusgit stash 也是前缀相同。这时我们可以考虑 使用别名

1
2
3
4
$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.ci commit
$ git config --global alias.st status
阅读全文 »
0%