告别回调地狱:Folly异步编程模型轻松掌握

告别回调地狱:Folly异步编程模型轻松掌握

【免费下载链接】folly An open-source C++ library developed and used at Facebook. 【免费下载链接】folly 项目地址: https://gitcode.com/GitHub_Trending/fol/folly

你是否还在为复杂的异步回调逻辑焦头烂额?是否在寻找一种简单高效的方式来处理并发任务?Facebook开源的C++库Folly(Folly)提供了强大的异步编程框架,让你轻松驾驭并发编程,告别回调地狱。本文将带你深入了解Folly的Futures/Promises模式与执行器框架,读完你将能够:

  • 理解Futures和Promises的核心概念及工作原理
  • 掌握Folly异步编程的基本使用方法
  • 学会使用执行器框架管理线程和任务调度
  • 解决实际开发中常见的异步编程问题

什么是Futures/Promises模式?

Futures/Promises模式是一种用于处理异步操作的编程范式,它将异步操作的结果与获取结果的过程分离,使得代码更加清晰和可维护。

在Folly中,Promise(承诺)是异步操作的生产者,它负责生成结果;而Future(未来)是异步操作的消费者,它负责获取结果。这种分离使得我们可以在不阻塞当前线程的情况下执行耗时操作,并在操作完成后得到通知。

Futures与Promises的关系

Promise和Future是一一对应的关系:一个Promise对应一个Future。当Promise设置了结果后,对应的Future就会得到通知,然后可以继续执行后续的回调函数。

Futures/Promises关系

Folly的Futures/Promises实现主要定义在以下头文件中:

快速上手:Folly异步编程基础

让我们通过一个简单的例子来了解Folly异步编程的基本用法。

创建Promise和Future

首先,我们需要创建一个Promise和对应的Future:

#include <folly/futures/Promise.h>
#include <folly/futures/Future.h>

// 创建一个Promise和对应的SemiFuture
auto [promise, future] = folly::makePromiseContract<int>();

这里使用了folly::makePromiseContract函数来创建一个Promise<int>和一个SemiFuture<int>SemiFuture是一种不包含执行器的轻量级Future,我们将在后面介绍如何将其转换为可以执行回调的Future

设置Promise的结果

接下来,我们可以在另一个线程中设置Promise的结果:

#include <thread>

// 在新线程中执行耗时操作
std::thread([p = std::move(promise)]() mutable {
  // 模拟耗时操作
  std::this_thread::sleep_for(std::chrono::seconds(1));
  
  // 设置成功结果
  p.setValue(42);
  
  // 如果发生错误,可以设置异常
  // p.setException(std::make_exception_ptr(std::runtime_error("操作失败")));
}).detach();

获取Future的结果

现在,我们可以通过Future来获取异步操作的结果。有两种方式可以获取结果:阻塞等待或者设置回调函数。

阻塞等待结果
try {
  // 阻塞等待结果,会抛出异常如果Promise设置了异常
  int result = std::move(future).via(folly::getGlobalCPUExecutor()).get();
  std::cout << "异步操作结果: " << result << std::endl;
} catch (const std::exception& e) {
  std::cout << "异步操作失败: " << e.what() << std::endl;
}

这里使用via(folly::getGlobalCPUExecutor())SemiFuture转换为Future,并指定了执行回调的 executor。

设置回调函数

更常见的做法是设置回调函数,在结果可用时自动执行:

// 设置回调函数,当结果可用时执行
std::move(future)
  .via(folly::getGlobalCPUExecutor())
  .thenValue([](int result) {
    std::cout << "异步操作成功,结果: " << result << std::endl;
  })
  .thenError([](const std::exception& e) {
    std::cout << "异步操作失败: " << e.what() << std::endl;
  });

thenValue方法用于处理成功的结果,thenError方法用于处理异常。这种链式调用的方式使得代码非常清晰。

SemiFuture与Future的区别

在Folly中,有两种类型的Future:SemiFutureFuture

SemiFuture

SemiFuture是一种轻量级的Future,它不包含执行器(Executor),因此不能直接执行回调函数。它主要用于在不同的执行器之间传递异步操作的结果。

SemiFuture的主要特点:

  • 不包含执行器,因此更轻量
  • 可以转换为Future
  • 主要用于表示异步操作的结果,而不关心如何执行回调

Future

Future是在SemiFuture的基础上增加了执行器的概念,它可以在指定的执行器上执行回调函数。

要将SemiFuture转换为Future,可以使用via方法:

SemiFuture<int> semiFuture = ...;
// 将SemiFuture转换为Future,使用全局CPU执行器
Future<int> future = std::move(semiFuture).via(folly::getGlobalCPUExecutor());

Folly提供了多种执行器,定义在folly/executors/目录下,包括:

  • CPUThreadPoolExecutor:用于CPU密集型任务
  • IOThreadPoolExecutor:用于IO密集型任务
  • GlobalExecutor:全局执行器,提供了默认的线程池

高级用法:链式操作与组合

Folly的Futures提供了丰富的操作符,可以轻松地组合多个异步操作,构建复杂的异步工作流。

链式操作

使用thenValue可以将多个异步操作串联起来:

// 链式操作示例
fetchData()  // 返回Future<Data>
  .thenValue([](Data data) {
    // 处理数据,返回新的Future
    return processData(data);  // 返回Future<ProcessedData>
  })
  .thenValue([](ProcessedData result) {
    // 保存结果,返回Future<Unit>表示完成
    return saveResult(result);  // 返回Future<Unit>
  })
  .thenValue([]() {
    std::cout << "所有操作完成!" << std::endl;
  })
  .thenError([](const std::exception& e) {
    std::cerr << "操作失败: " << e.what() << std::endl;
  });

并行操作

使用collect可以等待多个Future完成:

// 并行操作示例
std::vector<Future<int>> futures;
for (int i = 0; i < 5; ++i) {
  futures.push_back(fetchSomeData(i));
}

// 等待所有Future完成
collectAll(futures)
  .thenValue([](std::vector<int> results) {
    int sum = 0;
    for (int result : results) {
      sum += result;
    }
    std::cout << "所有结果之和: " << sum << std::endl;
  });

除了collectAll,Folly还提供了其他组合多个Future的函数:

  • whenAll:等待所有Future完成,不关心结果
  • firstCompletedOf:等待第一个完成的Future
  • anyOf:等待任意一个Future完成
  • allOf:等待所有Future完成,返回一个表示成功或失败的Future

异常处理

在异步编程中,异常处理尤为重要。Folly的Futures提供了多种处理异常的方式。

使用thenError捕获异常

thenError方法可以捕获前一个Future抛出的异常:

fetchData()
  .thenValue([](Data data) {
    // 处理数据
  })
  .thenError(folly::tag_t<std::runtime_error>(), [](const std::runtime_error& e) {
    // 处理运行时错误
    std::cerr << "运行时错误: " << e.what() << std::endl;
    return defaultData();  // 返回默认数据
  })
  .thenError(folly::tag_t<std::logic_error>(), [](const std::logic_error& e) {
    // 处理逻辑错误
    std::cerr << "逻辑错误: " << e.what() << std::endl;
    throw;  // 重新抛出异常
  });

使用thenTry处理结果和异常

thenTry方法可以同时处理正常结果和异常:

fetchData()
  .thenTry([](folly::Try<Data> tryData) {
    if (tryData.hasValue()) {
      // 处理正常结果
      return processData(tryData.value());
    } else {
      // 处理异常
      std::cerr << "获取数据失败: " << tryData.exception().what() << std::endl;
      return defaultData();
    }
  });

folly::Try<T>是一个可以保存值或异常的类,定义在folly/Try.h中。

执行器框架

Folly的执行器框架负责管理线程和任务调度,它是Futures能够异步执行回调的基础。

执行器的类型

Folly提供了多种执行器,以满足不同的需求:

执行器类型用途头文件
CPUThreadPoolExecutorCPU密集型任务folly/executors/CPUThreadPoolExecutor.h
IOThreadPoolExecutorIO密集型任务folly/executors/IOThreadPoolExecutor.h
GlobalExecutor全局默认执行器folly/executors/GlobalExecutor.h
InlineExecutor内联执行任务(同步执行)folly/executors/InlineExecutor.h

使用全局执行器

Folly提供了全局执行器,可以直接使用:

// 获取全局CPU执行器
auto executor = folly::getGlobalCPUExecutor();

// 将SemiFuture转换为Future,使用全局执行器
Future<int> future = semiFuture.via(executor);

自定义执行器

我们也可以创建自定义的执行器:

// 创建一个CPU线程池执行器,包含4个线程
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);

// 使用自定义执行器
future.via(executor->keepAlive())
  .thenValue([](int result) {
    // 在自定义执行器的线程中执行
  });

实际应用:异步日志系统

让我们通过一个实际的例子来展示Folly异步编程的强大之处。我们将实现一个简单的异步日志系统。

异步日志系统设计

异步日志系统的核心思想是将日志写入操作与业务逻辑分离,避免日志写入阻塞业务线程。

#include <folly/futures/Promise.h>
#include <folly/futures/Future.h>
#include <folly/executors/CPUThreadPoolExecutor.h>
#include <queue>
#include <mutex>
#include <fstream>
#include <string>

class AsyncLogger {
public:
  AsyncLogger(const std::string& filename) 
    : executor_(std::make_shared<folly::CPUThreadPoolExecutor>(1)) {
    // 启动日志写入线程
    startWriterThread(filename);
  }
  
  // 异步写入日志
  folly::Future<folly::Unit> log(const std::string& message) {
    auto [promise, future] = folly::makePromiseContract<folly::Unit>();
    
    std::lock_guard<std::mutex> lock(queueMutex_);
    logQueue_.emplace(std::move(promise), message);
    
    // 通知写入线程有新日志
    cv_.notify_one();
    
    return future.via(executor_);
  }
  
private:
  struct LogItem {
    folly::Promise<folly::Unit> promise;
    std::string message;
  };
  
  void startWriterThread(const std::string& filename) {
    // 在单独的线程中写入日志
    writerThread_ = std::thread([this, filename]() {
      std::ofstream logFile(filename, std::ios::app);
      if (!logFile.is_open()) {
        std::cerr << "无法打开日志文件: " << filename << std::endl;
        return;
      }
      
      std::queue<LogItem> localQueue;
      
      while (running_) {
        {
          std::unique_lock<std::mutex> lock(queueMutex_);
          cv_.wait(lock, [this]() {
            return !logQueue_.empty() || !running_;
          });
          
          if (!running_) break;
          
          // 将日志项移动到本地队列,减少锁持有时间
          logQueue_.swap(localQueue);
        }
        
        // 写入所有日志
        while (!localQueue.empty()) {
          auto item = std::move(localQueue.front());
          localQueue.pop();
          
          // 写入日志
          logFile << "[" << getCurrentTime() << "] " << item.message << std::endl;
          
          // 通知日志写入完成
          item.promise.setValue();
        }
        
        // 刷新文件
        logFile.flush();
      }
    });
  }
  
  // 获取当前时间字符串
  std::string getCurrentTime() {
    auto now = std::chrono::system_clock::now();
    auto time = std::chrono::system_clock::to_time_t(now);
    return std::ctime(&time);
  }
  
  std::shared_ptr<folly::CPUThreadPoolExecutor> executor_;
  std::thread writerThread_;
  std::queue<LogItem> logQueue_;
  std::mutex queueMutex_;
  std::condition_variable cv_;
  bool running_ = true;
};

使用异步日志系统

// 创建异步日志器
AsyncLogger logger("app.log");

// 异步写入日志
logger.log("应用启动")
  .thenValue([]() {
    std::cout << "日志写入成功" << std::endl;
  })
  .thenError([](const std::exception& e) {
    std::cerr << "日志写入失败: " << e.what() << std::endl;
  });

// 业务逻辑...

总结与展望

Folly的Futures/Promises模式为C++异步编程提供了强大而灵活的工具。通过将异步操作的结果与获取结果的过程分离,我们可以编写出更加清晰、可维护的并发代码。

本文介绍了Folly异步编程的基础知识,包括Futures/Promises模式、执行器框架、异常处理等内容,并通过实际例子展示了如何应用这些知识解决实际问题。

未来,随着C++标准的不断发展,Folly的异步编程模型也将不断演进。我们可以期待更多新特性的加入,如对C++20协程的更好支持,以及更高效的任务调度算法。

参考资料

希望本文能够帮助你更好地理解和应用Folly的异步编程模型。如果你有任何问题或建议,欢迎在评论区留言讨论!

点赞、收藏、关注三连,获取更多C++异步编程技巧!下期预告:《Folly并发容器实战指南》

【免费下载链接】folly An open-source C++ library developed and used at Facebook. 【免费下载链接】folly 项目地址: https://gitcode.com/GitHub_Trending/fol/folly

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值