6

folly utility 简明摘要

 2 years ago
source link: https://lrita.github.io/2021/06/11/cpp-folly-utility/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

folly utility 简明摘要 — 源代码

functional

// 所在头文件:
#include <folly/functional/Partial.h>

template <typename F, typename... Args>
auto partial(F&& f, Args&&... args);

// 跟 std::bind() 相似,但是不需要使用placeholders来进行参数绑定。
// e.g.
//
// auto p = folly::partial(&Foo::method, foo_pointer);
// p();
//
// folly::partial(Foo, 1, 2)(3); // is equivalent to `Foo(1, 2, 3);`

编译期数学方法

// 所在头文件:
#include <folly/ConstexprMath.h>

// 其中包含一些模板类,可以帮助在编译期推导 max、min、abs、pow、ceil、log2_ceil、log2等常用数学方法。

spin 帮助方法

#include <folly/synchronization/detail/Spin.h>

// 非常简单易用的 spin 循环实现,需要用到的之后可以直接引用
folly::detail::spin_pause_until();
folly::detail::spin_yield_until();

folly::MicroLock

#include <folly/MicroLock.h>
// 或者
#include <folly/synchronization/SmallLocks.h>

// 通常从性能角度出发,应该使用 std::mutex(对锁竞争处理的更好), 如果为了节省内存,可以使用 folly::MicroLock ,
// 其只使用 4B 空间。
// 其具有常规的lock()/try_lock()/unlock()方法。

folly::MicroSpinLock、folly::PicoSpinLock、folly::RWSpinLock

#include <folly/MicroSpinLock.h>
// 或者
#include <folly/synchronization/MicroSpinLock.h>

// folly::MicroSpinLock 是一个极简单的 spinlock 实现,采用最简单的while-cas模式。通常不应该被使用。

#include <folly/synchronization/PicoSpinLock.h>

// folly::PicoSpinLock 也是一个最简单的while-cas模式实现的spinlock,通常不应该被使用。但是其使用一个整形数
// 作为spin的载体,其大部分bit可以用来存储数据,剩余bit用来记录lock状态,如果非常需要内存紧凑,可以考虑使用。

#include <folly/synchronization/RWSpinLock.h>

// folly::RWSpinLock 是一个简单的读写spinlock实现,并且支持锁升级、降级逻辑,其升降级遵循boost的 https://www.boost.org/doc/libs/1_47_0/doc/html/thread/synchronization.html#thread.synchronization.mutex_concepts.upgrade_lockable 语义,其可以通过 lock_upgrade() + unlock_upgrade_and_lock() 完成读锁到写锁的升级。

需要明确的是,spinlock类的锁都不太适合在应用层面随意使用,你必须明显进行测试、并且清楚自己了解内在机理,否则你只应该简单的使用 std::mutex或者folly::SharedMutex等,可以参考Spinlocks Considered Harmful等。

baton

// 所在头文件:
#include <folly/synchronization/Baton.h>

// Baton 通常用作线程间同步、等待、通知的标识符号,常用姿势是,一些线程调用 wait() 方法等待另
// 一些线程完成某项工作,其完成以后调用 post() 方法进行通知。 其跟一般PV信号量的区别是,Baton
// 更轻量化、通知策略更简单(没有FILO/FIFO等策略)、仅能够通知一次,在简单场景中更高效。
//
// 声明:MayBlock表示十分会被长时间block,其实就是内部衡量是否需要一直spin的依据,
// 否则会调用 futex 相关 syscall 释放 cpu
// Atom 指示使用什么原子操作的实现,通常使用 std::atomic 即可
template <bool MayBlock = true, template <typename> class Atom = std::atomic>
class Baton;

// 常规用法:
// Baton 用于同步线程间的PV (block/wakeup),但是不像 semaphores
// 信号量那样可以多次pv,baton 仅支持单次pv操作,folly::Future 中的
// block/wakeup 就是使用 Baton<> 来实现的。

Baton<> baton;
Baton<false> spin_baton;

// 其基本方法有:
bool Baton<>::ready(); // 测试是否已经被标记置位
void Baton<>::post();  // 置位
bool Baton<>::try_wait(); // 等同 ready
// 等待直到被置位,可以传入一个 wait_options 来控制 spin 的最大时间,默认2us
void Baton<>::wait(const WaitOptions& opt = wait_options());

folly::SaturatingSemaphore

// 所在头文件:
#include <folly/synchronization/SaturatingSemaphore.h>

// folly::SaturatingSemaphore 是一个经典的信号量实现,可以支持多个poster和多个waiter同时调用PV。
// 而且可以对一个SaturatingSemaphore可以多次设置post状态,而且幂等(但是不累积,设置成post状态后,
// 所有wait()调用都会通过,简而言之就是只有PV状态,而没有对应的计数),然后可以通过reset()方法进行恢复。
//
// 其跟Baton的主要区别就是Baton只支持有且仅有一个poster,SaturatingSemaphore支持多个,且可以并发调用post()

///  方法:
///   bool ready():
///     Returns true if the flag is set by a call to post, otherwise false.
///     Equivalent to try_wait, but available on const receivers.
///   void reset();
///     Clears the flag.
///   void post();
///     Sets the flag and wakes all current waiters, i.e., causes all
///     concurrent calls to wait, try_wait_for, and try_wait_until to
///     return.
///   void wait(
///       WaitOptions opt = wait_options());
///     Waits for the flag to be set by a call to post.
///   bool try_wait();
///     Returns true if the flag is set by a call to post, otherwise false.
///   bool try_wait_until(
///       time_point& deadline,
///       WaitOptions& = wait_options());
///     Returns true if the flag is set by a call to post before the
///     deadline, otherwise false.
///   bool try_wait_for(
///       duration&,
///       WaitOptions& = wait_options());
///     Returns true if the flag is set by a call to post before the
///     expiration of the specified duration, otherwise false.

folly::LifoSem

#include <folly/synchronization/LifoSem.h>

// folly::LifoSem 相对于 folly::SaturatingSemaphore 维护了一个通知的顺序,后入先出,主要是尽快通知较活跃的线程。
// 并且post()可以指定通知的个数,不像 folly::SaturatingSemaphore post()会通知全部的wait().
//
// 其内部用一个对象池维护了waiter的相对顺序,然后按顺序进行通知。

Hazard Pointer

// 所在头文件:
#include <folly/synchronization/Hazptr.h>

// hazard pointer 支持一写多读
// 其实现依赖thread local机制。
// 其原始的 hazard pointer 并没有直接暴露给用户来进行操作,而是给用户提供一个 hazptr_holder 对象进行持有 hazard pointer

Atomic

folly::AtomicStruct

// AtomicStruct 可以原子地操作一个大小小于等于8byte的对象,其内部原理就是把对象转换成对应大小的int类型,
// 使用std::atmoic<int>来操作。是一个比较有助的封装。对象大小大于8byte的则无法使用该封装。
#include <folly/synchronization/AtomicStruct.h>

struct A {
  int32_t a;
};

folly::AtomicStruct<A> a;
auto xx = a.load();

对象管理、单例等

线程维度的单例对象

folly 的folly/SingletonThreadLocal.h文件中提供了宏FOLLY_DECLARE_REUSED以创建简便易用的线程维度的单例对象,可以减少逻辑上临时对象的反复创建、销毁的开销,要求该对象有一个clear()方法(通常是 STL 容器)即可。可以用这个宏优化线程池中对应函数中反复创建、销毁的临时容器。

其实现,就是创建一个对应对象的thread_local的单例,离开作用域的时候,调用对象的clear()方法:

// in folly/SingletonThreadLocal.h
#define FOLLY_DECLARE_REUSED(name, ...)                                        \
  struct __folly_reused_type_##name {                                          \
    __VA_ARGS__ object;                                                        \
  };                                                                           \
  auto& name =                                                                 \
      ::folly::SingletonThreadLocal<__folly_reused_type_##name>::get().object; \
  auto __folly_reused_g_##name = ::folly::makeGuard([&] { name.clear(); })
#include <folly/SingletonThreadLocal.h>

void traverse_perform(int root);
template <typename F>
void traverse_each_child_r(int root, F const&);
void traverse_depthwise(int root) {
  // preserves some of the memory backing these per-thread data structures
  FOLLY_DECLARE_REUSED(seen, std::unordered_set<int>);
  FOLLY_DECLARE_REUSED(work, std::vector<int>);
  // example algorithm that uses these per-thread data structures
  work.push_back(root);
  while (!work.empty()) {
    root = work.back();
    work.pop_back();
    seen.insert(root);
    traverse_perform(root);
    traverse_each_child_r(root, [&](int item) {
      if (!seen.count(item)) {
        work.push_back(item);
      }
    });
  }
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK