C/C++编程:libfiber学习入门

1060 篇文章 295 订阅

介绍

https://github.com/iqiyi/libfiber

本协程库来自于 acl 工程 协程模块。

  • 目前支持的操作系统有:Linux,FreeBSD,MacOS 和 Windows
  • 支持的事件类型有:select,poll,epoll,kqueue,iocp 及 Windows GUI 窗口消息。

通过 libfiber 网络协程库,用户可以非常容易地写出高性能、高可靠的网络通信服务。

因为使用了同步顺序编程的思维方式,相对于异步模式(无论是 reactor 模型还是 proactor 模型),编写网络应用更加简单。

libfiber 不仅支持常见的 IO 事件引擎,而且支持 Win32 GUI 界面消息引擎,这样当你使用 MFC,wtl 或其它 GUI 界面库编写界面网络应用时,也会变得异常简单,这的确是件令人兴奋的事。

为什么使用协程

协程除了可以提供更高并发的支持,还可以使用同步的方式写异步执行的代码,使得代码逻辑更加简洁和清晰

支持的事件引擎

在这里插入图片描述

协程的栈

目前我看到的示例代码里,libfiber的协程都是使用独立的栈,也就是属于stackfull类型的协程。这点和腾讯的协程库libco默认模式是一样的。

对应的还有一种stackless的协程,也就是共享栈的模式,这种模式下同样物理内存可支持更多的协程(所以支持更高的并发),但是切换效率低一些。libco据说也支持的,我没试过。

一般为一个协程提供128K的栈空间,可以自己调大或调小。对libfiber来说,在128K模式下,1GB内存支持8192个协程,对于普通的业务,足够了。

调度模型

首先,libfiber的协程不能跨线程调度,这点和golang不太一样。但是这样的处理方式也更简单,我个人不认为这是多大的缺点。况且libfiber实现了线程和协程的同步方式,“多线程+多协程”也不在话下

其次,libfiber的协程切换不需要返回上一层,也就是说libfiber的协程是“对称”的,没有一个中心化的调度协程,这样调度起来效率更高。

同步、通信方式

channel

相当于一个管道,最大容量100(可以自己修改代码修改这个固定容量)。
应该只能用于同一线程里的协程间通信。

fiber_mutex

仅能用于同一线程内部的协程之间进行互斥的互斥锁

fiber_event

可用于协程之间、线程之间以及协程和线程之间,通过事件等待/通知方式进行同步的事件混合锁

fiber_cond

基于fiber_event,可用在协程之间,线程之间,协程与线程之间的条件变量

fiber_sem和fiber_sbox

fiber_sem是一个协程间的信号量。fiber_sbox是基于fiber_sem的一个消息管道。

感觉其作用类似于channel,但是未限制容量。

fiber_tbox

用于协程之间,线程之间以及协程与线程之间的消息通信,通过协程条件变量及协程事件锁实现。

前面的channel和fiber_sbox都是用于同一线程里的协程间通信,这里可以跨线程

使用(linux)

编译生成库

方法1

在 Linux/Unix 平台上的编译非常简单,可以选择使用 make 方式或 cmake 方式进行编译。

  • make 方式编译:
    在 acl 项目根目录下运行:make && make packinstall,则会自动执行编译与安装过程,安装目录默认为系统目录:libacl_all.a, libfiber_cpp.a, libfiber.a 将被拷贝至 /usr/lib/ 目录,头文件将被拷贝至 /usr/include/acl-lib/ 目录。

  • cmake 方式编译:
    在 acl 项目根目录下创建 build 目录,然后:cd build && cmake … && make

方法2

分别在代码根目录和示例代码目录下执行make命令即可编译生成静态库:

$ cd libfiber
$ make

由于libfiber同时支持c和c++ 接口,所以会默认生成两个静态库。如果需要动态库,需要在根目录下执行:

$ cd cpp
$ make shared rpath=xxx
$ cd ../c
$ make shared rpath=xxx

Makefile

mytest: mytest.cpp
	g++ -o mytest mytest.cpp -lfiber_cpp -lacl_all -lfiber -ldl -lpthread -lz

cmakelist.txt

cmake_minimum_required(VERSION 3.16)
project(acl_fiber)

set(CMAKE_CXX_STANDARD 14)

aux_source_directory(. SRC_LIST)
add_executable(${PROJECT_NAME} ${SRC_LIST})
target_link_libraries (${PROJECT_NAME} fiber_cpp acl_all fiber dl pthread z)
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

在这里插入图片描述

注意在各个库的依赖顺序: libfiber_cpp.a 依赖于 libacl_all.a 和 libfiber.a,其中 libacl_all.a 为 acl 的基础库,libfiber.a 为 C 语言协程库(其不依赖于 libacl_all.a),libfiber_cpp.a 用 C++ 语言封装了 libfiber.a,且使用了 libacl_all.a 中的一些功能。

使用

回显服务器

下面为一个acl库编写的简单线程的例子:

#include <acl-lib/acl_cpp/lib_acl.hpp>
#include <zconf.h>
class mythread : public acl::thread
{
public:
    mythread(void) {}
    ~mythread(void) {}
private:
    // 实现基类中纯虚方法,当线程启动时该方法将被回调
    // @override
    void* run(void) {
        for (int i = 0; i < 10; i++) {
            printf("%d - thread-%lu: running ...\r\n", i, acl::thread::self());
        }
        return NULL;
    }
};

int main(void)
{
    std::vector<acl::thread*> threads;
    for (int i = 0; i < 10; i++) {
        acl::thread* thr = new mythread;
        threads.push_back(thr);
        thr->start();  // 启动线程
    }

    for (auto it = threads.begin(); it != threads.end(); ++it) {
        (*it)->wait();  // 等待线程退出
        delete *it;
    }
    return 0;
}

#include <acl-lib/acl_cpp/lib_acl.hpp>
#include <acl-lib/fiber/libfiber.hpp>

class myfiber : public acl::fiber
{
public:
    myfiber(void) {}
    ~myfiber(void) {}
private:
    // 重现基类纯虚方法,当调用 fiber::start 时,该方法将被调用
    // @override
    void run(void) {
        for (int i = 0; i < 10; i++) {
            printf("hello world! the fiber is %d\r\n", acl::fiber::self());
            acl::fiber::yield();  // 让出CPU运行权给其它协程
        }
    }
};

int main(void)
{
    std::vector<acl::fiber*> fibers;
    for (int i = 0; i < 10; i++) {
        acl::fiber* fb = new myfiber;
        fibers.push_back(fb);
        fb->start();  // 启动一个协程
    }

    acl::fiber::schedule();  // 启用协程调度器

    for (std::vector<acl::fiber*>::iterator it = fibers.begin();
         it != fibers.end(); ++it) {
        delete *it;
    }
}

上面示例演示了协程的创建、启动及运行的过程,与前一个线程的例子非常相似,非常简单(简单实用是 Acl 库的目标之一)。
协程调度其实是应用层面多个协程之间通过上下文切换形成的协作过程,如果一个协程库仅是实现了上下文切换,其实并不具备太多实用价值,当与网络事件绑定后,其价值才会显现出来。下面一个简单的使用协程的网络服务程序:

#include <acl-lib/acl_cpp/lib_acl.hpp>
#include <acl-lib/fiber/libfiber.hpp>

// 客户端协程处理类,用来回显客户发送的内容,每一个客户端连接绑定一个独立的协程
class fiber_echo : public acl::fiber{
public:
    explicit fiber_echo(acl::socket_stream* conn) : conn_(conn){}
private:
    acl::socket_stream * conn_;
    ~fiber_echo() override{ delete  conn_;}
    void run() override{
        char buf[8192];
        while (true){
            // 从客户端读取数据(第三个参数为false表示不必填满整个缓冲区才返回)
            int ret = conn_->read(buf, sizeof(buf), false);
            if(ret == -1){
                printf("%s\n", "ret == -1");
                break;
            }
            // 向客户端写入读到的数据
            if(conn_->write(buf, ret) != ret){
                printf("%s\n", "conn_->write(buf, ret) != ret");
                break;
            }
        }
        delete this; // 自销毁动态创建的协程对象
    }
};

// 独立的协程过程,接收客户端连接,并将接收的连接与新创建的协程进行绑定
class fiber_listen : public acl::fiber{
public:
    explicit fiber_listen(acl::server_socket& listener) : listener_(listener){};
private:
    acl::server_socket& listener_;
    ~fiber_listen() override= default;;
    void run() override {
        while (true){
            acl::socket_stream * conn = listener_.accept(); // 等待客户端连接
            if (conn == nullptr) {
                printf("accept failed: %s\r\n", acl::last_serror());
                break;
            }
            // 创建并启动单独的协程处理客户端连接
            acl::fiber* fb = new fiber_echo(conn);
            // 启动独立的客户端处理协程
            fb->start();
        }
        delete this;
    }
};


int main()
{
    const char * addr = "127.0.0.1:8000";
    acl::server_socket listener;
    // 监听本地地址
    if (!listener.open(addr)) {
        printf("listen %s error %s\r\n", addr, acl::last_serror());
        return 1;
    }

    // 创建并启动独立的监听协程,接受客户端连接
    acl::fiber* fb = new fiber_listen(listener);
    fb->start();

    // 启动协程调度器
    acl::fiber::schedule();
    return 0;
}

这是一个简单的支持回显功能的网络协程服务器,可以很容易修改成线程模式。使用线程或线程处理网络通信都可以采用顺序思维模式,不必象非阻塞网络编程那样复杂,但使用协程的最大好处可以创建大量的协程来处理网络连接,而要创建大量的线程显示是不现实的(线程数非常多时,会导致操作系统的调度能力下降)。如果你的网络服务应用不需要支持大并发,使用协程的意义就没那么大了。

使用多核

acl协程的调度过程是基于单CPU的(然后可以修改成多核调度,但考虑到很多原因,最终还是采用了单核调度模式),即创建一个线程,所创建的所有协程都在这个线程空间中运行。为了使用多核,充分利用CPU资源,可以创建多个线程/进程,每个线程为一个独立的协程运行容器,各个线程之间的协程相互隔离,互不影响。

下面先修改一下上面的例子,改成多线程的协程方式:

#include <acl-lib/acl_cpp/lib_acl.hpp>
#include <acl-lib/fiber/libfiber.hpp>

// 客户端协程处理类,用来回显客户发送的内容,每一个客户端连接绑定一个独立的协程
class fiber_echo : public acl::fiber
{
public:
    explicit fiber_echo(acl::socket_stream* conn) : conn_(conn) {}
private:
    acl::socket_stream* conn_;
    ~fiber_echo() override { delete conn_; }
    // @override
    void run() override {
        char buf[8192];
        while (true) {
            int ret = conn_->read(buf, sizeof(buf), false);
            if (ret == -1) {
                break;
            }
            if (conn_->write(buf, ret) != ret) {
                break;
            }
        }
        delete this; // 自销毁动态创建的协程对象
    }
};

// 独立的协程过程,接收客户端连接,并将接收的连接与新创建的协程进行绑定
class fiber_listen : public acl::fiber
{
public:
    explicit fiber_listen(acl::server_socket& listener) : listener_(listener) {}
private:
    acl::server_socket& listener_;
    ~fiber_listen() override = default;
    // @override
    void run() override {
        while (true) {
            acl::socket_stream* conn = listener_.accept();  // 等待客户端连接
            if (conn == nullptr) {
                printf("accept failed: %s\r\n", acl::last_serror());
                break;
            }
            // 创建并启动单独的协程处理客户端连接
            acl::fiber* fb = new fiber_echo(conn);
            fb->start();
        }
        delete this;
    }
};

// 独立的线程调度类
class thread_server : public acl::thread
{
public:
    explicit thread_server(acl::server_socket& listener) : listener_(listener) {}
    ~thread_server() override = default;
private:
    acl::server_socket& listener_;
    // @override
    void* run() override {
        // 创建并启动独立的监听协程,接受客户端连接
        acl::fiber* fb = new fiber_listen(listener_);
        fb->start();
        // 启动协程调度器
        acl::fiber::schedule(); // 内部处于死循环过程
        return NULL;
    }
};

int main(void)
{
    const char* addr = "127.0.0.1:8800";
    acl::server_socket listener;
    // 监听本地地址
    if (!listener.open(addr)) {
        printf("listen %s error %s\r\n", addr, acl::last_serror());
        return 1;
    }

    std::vector<acl::thread *> threads;
    // 创建多个独立的线程对象,每个线程启用独立的协程调度过程
    for (int i = 0; i < 4; i++) {
        acl::thread* thr = new thread_server(listener);
        threads.push_back(thr);
        thr->start();
    }
    for (auto & thread : threads) {
        thread->wait();
        delete thread;
    }
    return 0;
}

在这里插入图片描述
经过修改,上面的例子即可以支持大并发,又可以使用多核。

多核同步

上面的例子中涉及到了通过创建多线程使用多核的过程,但肯定会有人问,在多个线程中的协程之间如果想要共享某个资源怎么办?Acl 协程库提供了可以跨线程使用同步原语:线程协程事件同步及条件变量。

首先介绍一下事件同步对象类:acl::fiber_event,该类提供了三个方法:

	/**
	 * 等待事件锁
	 * @return {bool} 返回 true 表示加锁成功,否则表示内部出错
	 */
	bool wait(void);

	/**
	 * 尝试等待事件锁
	 * @return {bool} 返回 true 表示加锁成功,否则表示锁正在被占用
	 */
	bool trywait(void);

	/**
	 * 事件锁拥有者释放事件锁并通知等待者
	 * @return {bool} 返回 true 表示通知成功,否则表示内部出错
	 */
	bool notify(void);

下面给出一个例子,看看多个线程中的协程之间如何进行互斥的:

#include <acl-lib/acl_cpp/lib_acl.hpp>
#include <acl-lib/fiber/libfiber.hpp>

class myfiber : public acl::fiber
{
public:
	myfiber(acl::fiber_event& lock, int& count): lock_(lock), count_(count) {}
private:
	~myfiber(void) {}
	// @override
	void run(void) {
		for (int i = 0; i < 100; i++) {
			lock_.wait();
			count_++;
			lock_.notify();
			//acl::fiber::delay(1);  // 本协程休息1毫秒
		}
		delete this;
	}
private:
	acl::fiber_event& lock_;
	int& count_;
};

class mythread : public acl::thread
{
public:
	mythread(acl::fiber_event& lock, int& count): lock_(lock), count_(count) {}
	~mythread(void) {}
private:
	// @override
	void* run(void) {
		for (int i = 0; i < 100; i++) {
			acl::fiber* fb = new myfiber(lock_, count_);
			fb->start();
		}
		acl::fiber::schedule();
		return NULL;
	}
private:
	acl::fiber_event& lock_;
	int& count_;
};

int main(void)
{
	acl::fiber_event lock;  // 可以用在多个线程之间、各个线程中的协程之间的同步过程
	int count = 0;
	std::vector<acl::thread*> threads;
	for (int i = 0; i < 4; i++) {
		acl::thread* thr = new mythread(lock, count);
		threads.push_back(thr);
		thr->start();
	}
	for (std::vector<acl::thread*>::iterator it = threads.begin();
		it != threads.end(); ++it) {
		(*it)->wait();
		delete *it;
	}

	printf("all over, count=%d\r\n", count);
	return 1;
}

上面的协程为什么没有运行!!!!!!!!!!!!!!!!

消息传递

Acl 网络协程框架编程指南

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值