c++开源协程库libgo介绍及使用

news2024/11/18 15:49:24

协程这个概念,最近这几年可是相当地流行了。尤其 go 语言问世之后,内置的协程特性,完全屏蔽了操作系统线程的复杂细节。甚至使 go 开发者“只知有协程,不知有线程”了。当然 C++也有高性能的协程库,比如我了解到的微信的libco、魅族的libgo、以及libcopp,开源libaco、boost的 coroutine,cppcoro,阿里的雅兰亭库(基于c++20特性封装好用的库)。

协程简介

协程不是进程或线程,其执行过程更类似于子例程。一个程序可以包含多个协程,我们来比较下协程和线程,加深下对协程的理解。我们知道多个线程相对独立,有自己的上下文,切换受操作系统控制;而协程也相对独立,有自己的上下文,但是其切换由自己控制,由当前协程切换到其他协程由当前协程来控制。

协程的上下文切换相较线程有哪些提升?

 协程上下文切换只涉及CPU上下文切换,而所谓的CPU上下文切换是指少量寄存器(PC / SP / DX)的值修改,协程切换非常简单,就是把当前协程的 CPU 寄存器状态保存起来,然后将需要切换进来的协程的 CPU 寄存器状态加载的 CPU 寄存器上就 ok 了。而对比线程的上下文切换则需要涉及模式切换(从用户态切换到内核态)、以及 16 个寄存器、PC、SP…等寄存器的刷新。线程栈空间通常是 2M, 协程栈空间最小 2K。

CPU 调度切换的是进程和线程。尽管线程看起来很美好,但实际上多线程开发设计会变得更加复杂,要考虑很多同步竞争等问题,如锁、竞争冲突等。

多进程、多线程已经提高了系统的并发能力,但是在当今互联网高并发场景下,为每个任务都创建一个线程是不现实的,因为会消耗大量的内存 (进程虚拟内存会占用 4GB [32 位操作系统],而线程也要大约 4MB)。大量的进程 / 线程出现了新的问题:系统线程会占用非常多的内存空间,过多的线程切换会占用大量的系统时间。

协程刚好可以解决上述2个问题。协程运行在线程之上,当一个协程执行完成后,可以选择主动让出,让另一个协程运行在当前线程之上。并且,协程并没有增加线程数量,只是在线程的基础之上通过分时复用的方式运行多个协程,而且协程的切换在用户态完成,切换的代价比线程从用户态到内核态的代价小很多。

c++20是把协程这一特性加入到语法中了。只是过于灵活,使用上不太友好。如果是c++20以上推荐阿里的雅兰亭库(yalantinglibs)。雅兰亭库这名字起的够优雅,只是百度一搜,还以为是酒店或乡村名居呢,但这并不代表这库不行或不出名。为了更好使用c++20的协程,咨询c++大佬祁宇,《深入应用c++11》作者,推荐c++20以上可使用雅兰亭库,里面不但有封装好用的协程库,还有高性能的coro_rpc 远程过程调用库,方便分布式和解耦应用的开发。

libgo介绍

libgo 是一个使用 C++ 编写的协作式调度的stackful有栈协程库, 同时也是一个强大的并行编程库。支持linux平台,MacOS和windows平台,在c++11以上的环境中都能用。

之前用过go,喜欢它的协程使用方式既简单又方便。而在c++中libgo,不但性能高,使用也简单,比其它几个更好用一些,使用起来比较顺手。这里着重介绍下libgo及其使用。

libgo库的github地址:GitHub - yyzybb537/libgo: Go-style concurrency in C++11

目前也收获了2.8k的星,也算是挺火了。使用libgo编写并行程序,即可以像golang一样开发迅速且逻辑简洁,又有C++原生的性能优势。它的特点有:

1.提供golang一般功能强大协程,基于corontine编写代码,可以以同步的方式编写简单的代码,同时获得异步的性能。

2.支持海量协程, 创建100万个协程只需使用2GB内存。

3.允许用户自由控制协程调度点,随时随地变更调度线程数。

4.支持多线程调度协程,极易编写并行代码,高效的并行调度算法,可以有效利用多个CPU核心。

5.可以让链接进程序的同步的第三方库变为异步调用,大大提升其性能。再也不用担心某些DB官方不提供异步driver了,比如hiredis、mysqlclient这种客户端驱动可以直接使用,并且可以得到不输于异步driver的性能。

6.动态链接和静态链接全都支持,便于使用C++11的用户静态链接生成可执行文件并部署至低版本的linux系统上。

7.提供协程锁(co_mutex), 定时器, channel等特性, 帮助用户更加容易地编写程序。

8.网络性能强劲,在Linux系统上超越ASIO异步模型,尤其在处理小包和多线程并行方面非常强大。

在源码的samples目录下有很多示例代码,内含详细的使用说明,让用户可以很轻易地学会使用libgo。与golang的性能对比:

 与腾讯开源的ligco相比,不说性能高低,在易用性方面libgo完胜。 在这里插入图片描述

它的使用有多简单呢,可以看一个简单的例子。有种golang的感觉,难怪名字都叫libgo,像极了对golang协程的模仿。

#include <stdio.h>
#include <libgo/coroutine.h>
  
int main(int argc, char **argv)
{
    go []{
        printf("1\n");
        co_yield;
        printf("2\n");
    };
      
    go []{
        printf("3\n");
        co_yield;
        printf("4\n");
    };
      
    return 0;
}
 

 libgo安装配置

引入和使用libgo也是很简单的,如果有vcpkg的话,可直接使用vcpkg安装。如:

$vcpkg.exe install libgo

在linux平台下,可以使用cmake编译和安装libgo。

$ mkdir build
$ cd build
$ cmake ..

在cmake下的libgo使用,CMakeLists.txt中增加以下配置就可以了。

find_package(libgo CONFIG REQUIRED)
target_link_libraries(main PRIVATE libgo::libgo)

libgo简单使用 

#include <libgo/coroutine.h>
#include <stdio.h>
#include <thread>

void foo()
{
    printf("function pointer\n");
}

struct A {
    void fA() { printf("std::bind\n"); }
    void fB() { printf("std::function\n"); }
};

int main()
{
    //----------------------------------
    // 使用关键字go创建协程, go后面可以使用:
    //     1.void(*)()函数指针, 比如:foo.
    //     2.也可以使用无参数的lambda, std::bind对象, function对象, 
    //     3.以及一切可以无参调用的仿函数对象
    //   注意不要忘记句尾的分号";".
    go foo;

    go []{
        printf("lambda\n");
    };

    go std::bind(&A::fA, A());

    std::function<void()> fn(std::bind(&A::fB, A()));
    go fn;

    // 也可以使用go_stack创建指定栈大小的协程
    //   创建拥有10MB大栈的协程
    go co_stack(10 * 1024 * 1024) []{
        printf("large stack\n");
    };

    // 协程创建以后不会立即执行,而是暂存至可执行列表中,等待调度器调度。
    // co_sched是默认的协程调度器,用户也可以使用自创建的协程调度器。 
    // 当仅使用一个线程进行协程调度时, 协程地执行会严格地遵循其创建顺序.

    // 仅使用主线程调度协程.
    // co_sched.Start();

    // 以下代码可以使用等同于cpu核心数的线程调度协程.(包括主线程)
    // co_sched.Start(0);

    // 以下代码允许调度器自由扩展线程数,上限为1024.
    // 当有线程被协程阻塞时, 调度器会启动一个新的线程, 以此保障
    // 可用线程数总是等于Start的第一个参数(0表示cpu核心数).
    // co_sched.Start(0, 1024);

    // 如果不想让调度器卡住主线程, 可以使用以下方式:
    std::thread t([]{ co_sched.Start(); });
    t.detach();
    co_sleep(100);
    //----------------------------------

    //----------------------------------
    // 除了上述的使用默认的调度器外, 还可以自行创建额外的调度器,
    // 协程只会在所属的调度器中被调度, 创建额外的调度器可以实现业务间的隔离.

    // 创建一个调度器
    co::Scheduler* sched = co::Scheduler::Create();

    // 启动4个线程执行新创建的调度器
    std::thread t2([sched]{ sched->Start(4); });
    t2.detach();

    // 在新创建的调度器上创建一个协程
    go co_scheduler(sched) []{
        printf("run in my scheduler.\n");
    };

    co_sleep(100);
    return 0;
}

libgo定时器使用

有时候需要定时执行一些任务,libgo的定时器使用真清爽,有一种使用高级语言的感觉。

/************************************************
 * libgo库原生提供了一个线程安全的定时器
 * 还提供了休眠当前协程的方法co_sleep,类似于系统调用sleep, 不过时间单
 * 位是毫秒.
 * 同时HOOK了系统调用sleep、usleep、nanosleep, 在协程中使用这几个系统
 * 调用, 会在等待期间让出cpu控制权, 执行其他协程, 不会阻塞调度线程.
************************************************/
#include <libgo/coroutine.h>

int main()
{
    // 创建一个定时器
    // 第一个参数: 精度
    // 第二个参数: 绑定到一个调度器(Scheduler)
    // 两个参数都有默认值, 可以简便地创建一个定时器: co_timer timer; 
    co_timer timer(std::chrono::milliseconds(1), &co_sched);

    // 使用timer.ExpireAt接口设置一个定时任务
    // 第一个参数可以是std::chrono中的时间长度,也可以是时间点。
    // 第二个参数是定时器回调函数
    // 返回一个co_timer_id类型的ID, 通过这个ID可以撤销还未执行的定时函数
    co_timer_id id1 = timer.ExpireAt(std::chrono::seconds(1), []{
            printf("Timer Callback.\n");
            });

    // co_timer_id::StopTimer接口可以撤销还未开始执行的定时函数
    // 它返回bool类型的结果,如果撤销成功,返回true;
    //     如果未来得及撤销,返回false, 此时不保证回调函数已执行完毕。
    bool cancelled = id1.StopTimer();
    printf("cancelled:%s\n", cancelled ? "true" : "false");

    timer.ExpireAt(std::chrono::seconds(2), [&]{
            printf("Timer Callback.\n");
            co_sched.Stop();
            });

    for (int i = 0; i < 100; ++i)
        go []{
            // 休眠当前协程 1000 milliseconds.
            // 不会阻塞线程, 因此100个并发的休眠, 总共只需要1秒.
            co_sleep(1000);
        };

#if !defined(_WIN32)
    // 系统调用提供的sleep usleep nanosleep都使用HOOK技术,
    // 使其在协程中运行时, 能达到和co_sleep相同的效果.
    go []{
        // 休眠当前协程 1 second
        sleep(1);
    };

    go []{
        // 休眠当前协程 100 milliseconds
        usleep(100 * 1000);
    };
#endif

    co_sched.Start();
    return 0;
}

需要注意的一点是协程的调度是协作式调度,需要协程主动让出执行权,推荐在耗时很长的循环中插入一些yield。除网络IO、sleep等这些是已经被libgo封装hook过的系统调用不会产生阻塞,其它耗时操作会阻塞调度线程的运行,这时请使用co_await, 并启动几个线程去Run内置的线程池中。

编译报错问题

需要注意的一个坑是编译报错问题,这并非是作者的原因。由UTF8和UTF8-BOM编码引起的vc工具链编译报错问题,这真是个巨坑,如果没想到的话,会让人百思不得姐。

VC++ 编译器默认对源文件要求是使用UTF8 BOM模式的。微软vs套件vc++为什么使用GBK和UTF8BOM模式? vs不能识别无BOM头的unicode文件编码,所以如果使用utf-8记得要加上BOM(Byte Order Mark 字节流标记 utf-8 的BOM是 0xEFBBBF).vs新建工程默认的编码也是这个。如果你的源代码想在多个平台上编译,mac, unix等,那么在windows平台上需要在命令行中加入这个参数 /utf-8。

很多人经常需要把代码分别在linux、windows上编译。在linux中gcc编译的时候,文件格式为utf-8无bom格式,可是如果将文件拿到windows上,用vs编译的时候,发现各种报错,且都是不知道原因的错。这个时候就要考虑代码中注释部分含有中文汉字,导致的。  

\libgo\common\util.h: warning C4819: The file contains a character that cannot be represented in the current code page (936)
libgo\common\util.h(28): error C2061: syntax error: identifier 'RefObject'

 解决方式
在cmake编译命令指定UTF-8,参考如下:

-D CMAKE_CXX_FLAGS="/utf-8"

 CLion 默认使用 UTF-8 编码,MSVC 继承了 MS 家族的一贯传统,除非明确指定否则要么 UTF-8 with BOM 要么当前代码页。解决办法也简单,加上命令行开关就行了: ​​\utf-8​

或者CMakeLists.txt增加以下内容:

if(MSVC)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8")
endif()

还有个可能出现的编译报错问题是,从git拉取下来的代码直接编译可能出现的。需要配置下:

git config --global core.autocrlf true 

未做git config --global core.autocrlf true 设置时,checkout出来的代码行尾是LR,而在windows下,有效的换行符是CRLF。

还有个编译报错:

libgo.lib(processer.cpp.obj) : error LNK2019: unresolved external symbol __imp__WSASetLastError@4

原因是需要链接 ws2_32.lib。代码中加入 #pragma comment(lib, "ws2_32.lib")

或者CMakeLists.txt文件中增加 link_libraries(ws2_32),需要注意的是,必须在add_executable之前添加才行。如:

link_libraries(ws2_32)
add_executable(${PROJECT_NAME} ${SRC_FILES} )

 最后附上我的CMakeLists.txt配置:

cmake_minimum_required(VERSION 3.12)
project(untitled VERSION 0.0.1)

set(CMAKE_CXX_STANDARD 11)

set(BUILD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/build)
set(STATIC_LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib)

set(SRC_PATH
        ${CMAKE_CURRENT_SOURCE_DIR}/src
        )

include_directories(
        ${SRC_PATH}
        ${CMAKE_CURRENT_SOURCE_DIR}/include
)

add_definitions(

)
if(MSVC)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8")
endif()

####################  scan source files ####################
foreach (path ${SRC_PATH})
    aux_source_directory(${path} SRC_FILES)
endforeach ()

link_libraries(ws2_32)

add_executable(${PROJECT_NAME} ${SRC_FILES} )

set(LIBGO_LIB ${STATIC_LIB_DIR}/libgo.lib)

target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBGO_LIB} )

#find_package(libgo CONFIG REQUIRED)
#target_link_libraries(${PROJECT_NAME} PRIVATE libgo::libgo)

引用

C++libco协程_adce9的博客-CSDN博客_c++协程库

一文彻底弄懂C++开源协程库libco——原理及应用 - 知乎

libgo 源码剖析(1. libgo简介与调度浅谈)_尚先生的博客的博客-CSDN博客

C++|并发|libco协程剖析 - 腾讯云开发者社区-腾讯云

什么是协程?_瘦弱的皮卡丘的博客-CSDN博客_协程

https://github.com/lewissbaker/cppcoro

GitHub - yyzybb537/libgo: Go-style concurrency in C++11

GitHub - alibaba/yalantinglibs: A collection of C++20 libraries, include async_simple, coro_rpc and struct_pack

C/C++ 协程库boost.coroutine2、魅族libgo、腾讯libco、开源libaco详解_Hello,C++!的博客-CSDN博客_c++ 协程库

/utf-8 (Set source and execution character sets to UTF-8) | Microsoft Learn

C/C++报错——关于utf-8 BOM的问题_aRooooooba的博客-CSDN博客_c++ bom文件 结尾

关于vc对utf8源码文件的CRLF、LF 换行格式,出现的BOM问题的大坑_RelicOfTesla的博客-CSDN博客_crlf 缺点 uft-8 BOM 导致编译问题_jeff-wall的博客-CSDN博客

https://www.jianshu.com/p/78c451b214c4

git换行符之autoCRLF配置的意义 - 一人浅醉- - 博客园

git中配置autocrlf来正确处理crlf

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于微信小程序的企业职工薪资查询系统小程序

文末联系获取源码 开发语言&#xff1a;Java 框架&#xff1a;ssm JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7/8.0 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 浏览器…

卷积神经网络(CNN)详细介绍及其原理详解

文章目录前言一、什么是卷积神经网络二、输入层三、卷积层四、池化层五、全连接层六、输出层七、回顾整个过程总结前言 本文总结了关于卷积神经网络&#xff08;CNN&#xff09;的一些基础的概念&#xff0c;并且对于其中的细节进行了详细的原理讲解&#xff0c;通过此文可以十…

自动(智能)驾驶 | 4D雷达的数据集

上篇文章分享了关于Oculii 4D雷达的两篇报告。数据集是一个非常重要的研究工具&#xff0c;对于4D雷达领域来说&#xff0c;处于一个研究前沿的位置&#xff0c;鲜有公开的数据集&#xff0c;目前能找到的数据集有&#xff1a; 这些文章中的数据集有不少博主也写过&#xff0c…

S1000D规范导读

S1000D最初是由欧洲航空工业联盟开发的技术出版物规范&#xff0c;它主要为具有较长生命的复杂产品运行和维修而设计。这些年不断发展&#xff0c;已经扩展到这些行业的产品&#xff1a;国防系统 - 包括海、陆、空的产品&#xff0c;民用航空产品&#xff0c;基建行业产品和船舶…

15/365 java static final

1.static属性,方法 类内属性或方法用static修饰&#xff0c;表示该属性或方法属于类&#xff0c;不依赖于实例对象&#xff0c;所以不需要用对象调用&#xff0c;而是直接用类名调用。 static方法只能调用其他static方法&#xff0c;而普通方法可以调用其他的普通方法和stati…

Vue3商店后台管理系统设计文稿篇(二)

记录使用vscode构建Vue3商店后台管理系统&#xff0c;这是第二篇&#xff0c;主要记录Vue3中生命周期钩子&#xff0c;模板语法&#xff0c;以及相关的代码 文章目录一、Vue3生命周期二、Vue3模板语法三、代码展示正文内容&#xff1a; 一、Vue3生命周期 每个 Vue 实例在被创建…

拆机详解2:比Macintosh还早?苹果Lisa拆解

hello大家好&#xff0c;我是每天&#xff08;实际并不是每天&#xff0c;你们点的赞太少了&#xff0c;每人点一个赞我就日更&#xff09;给你们讲解的Eric_Bells.这里感谢博主半身风雪的支持&#xff0c;我会更新的&#xff01;看到的麻烦点个关注谢谢拉 今天唠唠一台比Maci…

【蓝桥杯基础题】2017年省赛—九宫幻方

&#x1f451;专栏内容&#xff1a;&#x1f449;蓝桥杯刷题&#x1f448;⛪个人主页&#xff1a;&#x1f449;子夜的星的主页&#x1f448;&#x1f495;座右铭&#xff1a;前路未远&#xff0c;步履不停 目录一、题目背景二、题目描述1.问题描述2.输入格式3.输出格式4.一个例…

CAN通信----(创芯科技)CAN分析仪使用----CANTest安装和驱动安装

前言 我在调试CAN通信时&#xff0c;使用的是在淘宝买的CAN分析仪。 CAN分析仪的实物如下&#xff1a; 使用CAN分析仪&#xff0c;调试CAN通信&#xff0c;PC电脑端需要使用CANTest测试软件&#xff0c;还需要安装驱动。 一、创芯科技 CAN分析仪资料包下载 步骤1&#xff1…

测开-基础篇

一、软件测试的生命周期 先来回顾软件的生命周期 &#x1f351;软件的生命周期 需求分析--》计划--》设计--》编码--》测试--》运营维护 需求分析&#xff1a;进行市场分析&#xff0c;这个需求量大不大&#xff1f;投入与盈利的占比&#xff1f;技术上 能否实现或者说实现的…

深度学习 10 神经网络简介

1. 深度学习和机器学习的主要区别在于对数据的处理, 机器学习主要通过算法直接进行推断, 而深度学习主要通过神经网络对各种算法进行加权, 然后汇总得出结论. 2. 常用的激活函数: tanh函数relu函数leaky relu函数1.1 深度学习介绍 1.1.1 区别 机器学习的特征工程步骤是要靠手…

Effective Objective-C 2.0学习记录(五)

23.通过委托和数据源协议进行对象间通信 使用委托模式&#xff1a;获取网络数据的类含有一个“委托对象”&#xff0c;在获取完数据后&#xff0c;它会回调这个委托对象。 利用协议机制&#xff0c;很容易就 能以OC代码实现此模式&#xff0c;在图中演示的情况下。可以这样定义…

【Java AWT 图形界面编程】Container 容器总结

文章目录一、AWT 简介二、AWT 核心类继承体系三、Container 容器类子类四、Container 容器常用 API五、Frame 窗口示例六、Panel 示例七、窗口中文乱码处理八、ScrollPane 可滚动容器示例一、AWT 简介 Java 中 使用 AWT 和 Swing 进行 图形界面开发 , AWT 是 抽象窗口工具集 , …

线程安全问题(3)

线程不安全:在多线程的调度情况下&#xff0c;导致出现了一些随机性&#xff0c;随机性是代码中出现了一些BUG&#xff0c;导致我们的线程是不安全的 造成线程不安全的原因: 1)操作系统抢占式执行&#xff0c;线程调度随机&#xff0c;这是万恶之源&#xff0c;我们无能为力 2)…

Web进阶:Day7 响应式、BootStrap、实战演练

Web进阶&#xff1a;Day7 Date: January 10, 2023 Summary: 响应式、BootStrap、实战演练 响应式 媒体查询 目标&#xff1a;能够根据设备宽度的变化&#xff0c;设置差异化样式 媒体特性常用写法 媒体特性常用写法&#xff1a; max-width&#xff08;从小到大&#xff0…

transformers包介绍——nlp界最顶级的包——可以不用 但不能不知道——python包推荐系列

背景1 现在在AI行业&#xff0c;什么最火&#xff1f;计算机视觉还是自然语言处理&#xff1f;其实不得不说&#xff0c;现在nlp很火。还有人记得上个月很多科技爱好者都在玩的chatgpt么&#xff1f;那个就是nlp技术的一大应用。现在都在觉得AI赚钱&#xff0c;工资高&#xf…

深度学习 12 正则化

1. 对于高方差(过拟合)&#xff0c;有以下几种方式&#xff1a; 获取更多的数据&#xff0c;使得训练能够包含所有可能出现的情况 正则化&#xff08;Regularization&#xff09; 寻找更合适的网络结构 2. 对于高偏差(欠拟合)&#xff0c;有以下几种方式&#xff1a; 扩大网…

【C语言进阶】只看此篇,让你学会动态内存管理

目录 前言 一、为什么存在动态内存分配 二、动态内存函数的介绍 1 、malloc和free 2、 calloc 3 、realloc 三、常见的动态内存错误 四、动态内存管理笔试题 1 题目1&#xff1a; 2 题目2&#xff1a; 3 题目3&#xff1a; 4 题目4&#xff1a; 五、C/C程序的…

5.10回溯法--圆排列问题--排列树

圆排列问题描述 给定n个大小不相等的圆&#xff0c;要将这n个大小不相等的圆排进一个矩形框中&#xff0c;且要求个个圆都与矩形框的最底边相切。要找出最小长度的圆排列。 问题分析 排列排列&#xff0c;解空间是一个排列树。 设开始时&#xff0c;a[n]储存n个圆的半径&…

笔试强训48天——day26

文章目录一. 单选1.在单处理器系统中&#xff0c;如果同时存在有12个进程&#xff0c;则处于就绪队列中的进程数量最多为&#xff08;&#xff09;2.在系统内存中设置磁盘缓冲区的主要目的是&#xff08;&#xff09;3.下列选项中&#xff0c;会导致进程从执行态变为就绪态的事…