重载返回值


本文是这篇文章的翻译整理

主要是这个场景

// this is OK
std::string to_string(int i);
std::string to_string(bool b);

std::string si = to_string(0);
std::string sb = to_string(true);

// this is not OK
int from_string(std::string_view s);
bool from_string(std::string_view s);

int i = from_string("7");
bool b = from_string("false");

想让返回值更统一

做法是做一个统一的返回类型,然后重载 类型操作符

struct to_string_t
{
    std::string_view s;

    operator int() const;  // int  from_string(std::string_view s);
    operator bool() const; // bool from_string(std::string_view s);
};

int i = to_string_t{"7"};
bool b = to_string_t{"true"};

每个类型都要写?模版话,给内建类型定义好,自己的类型,自己用sfinae拼

// base template, specialize and provide a static from_string method
template <class T, class = void>
struct to_string_impl 
{
};

namespace detail // hide impl detail
{
template <class T>
auto has_from_string(int) -> decltype(
    to_string_impl<T>::from_string(std::declval<std::string_view>()), 
    std::true_type{});

template <class T>
std::false_type has_from_string(char);
}

// check if T has a from_string
template <class T>
constexpr bool has_from_string = decltype(detail::has_from_string<T>(0))::value;

// return-type overload mechanism
struct to_string_t
{
    std::string_view s;

    template <class T>
    operator T() const 
    {
        static_assert(has_from_string<T>, "conversion to T not supported");
        return to_string_impl<T>::from_string(s); 
    }
};

// convenience wrapper to provide a "return-type overloaded function"
to_string_t from_string(std::string_view s) { return to_string_t{s}; }



//各种类型的实现自己拼好就可以了
template <>
struct to_string_impl<int>
{
    static int from_string(std::string_view s);
};

template <>
struct to_string_impl<bool>
{
    static bool from_string(std::string_view s);
};

//自定义实现
template <class T>
struct my_range { /* ... */ };

template <class T>
struct to_string_impl<my_range<T>, std::enable_if_t<has_from_string<T>>>
{
    static my_range<T> from_string(std::string_view s);
};

就是偏特化+sfinae套路


Read More

线程池/任务队列调研


解决什么问题:资源隔离,不同的任务通过不同的线程(池)/任务队列 来做

需要一个能动态调整(弹性线程数调整),区分优先级,且能做到绑核(一个核一个线程池?) 租户隔离的线程池/任务队列

优先级更进一步:动态调整线程的优先级?如何判定?

更更进一步:租户级别优先级?

解决方案:

  1. 线程池 (内部有任务队列) 比如rocksdb的线程池 每个线程有自己的优先级(IO有IO优先级,CPU有CPU优先级),不同的任务,IO的和cpu的放到不同的池子里,注意rocksdb的线程池是没有主动schedule的,设置线程的优先级,然后通过系统调用来调度(仅支持linux)

  2. 异步事件处理 + future/promise+线程池 ,线程池纯粹一点,就是资源池。资源池分成不同的种类,future/promise调用能穿起不同的资源,比如folly ,没有线程级别的优先级,但可以指定不同的线程池,比如IO线程池,CPU线程池等等,一个future可以串多个线程池,把任务分解掉

  3. 异步事件框架 +线程池 线程池没有别的作用,就是资源池,事件框架可以是reactor/proactor,有调度器 schedule,负责选用资源 比如boost::asio

  4. 异步事件处理(一个主事件线程+一个工作线程+一个无锁队列) + future/promise + 任务队列 比如seastar (侵入比较强,系统级)

以rocksdb的线程池做基线

  动态调整线程池 任务可以区分优先级 内部有队列? 统计指标 使用负担
rocksdb的线程池
可以调整池子大小
✅ rocksdb线程池的优先级是系统级别的优先级,有系统调用的。而不是自定义schedule循环,自己维护优先级的 ✅ std::duque<BGItem> worker线程的各种状态统计idle等待 组件级,可以理解成高级点的任务队列
boost::asio::thread_pool 没有队列,一般使用不需要队列,如果有任务队列需要自己维护
结合post使用静态的池子
X 组件级,但是得配合asio使用,摘出来没什么意义
Folly::threadpoolExecutor 没有队列,add直接选线程调用可以定制各种类型的executor 结合future使用 future then串起队列 worker线程的各种状态统计idle等待 单独用相当于epoll + 多线程worker
seastar 有队列,每个核一个reator一个队列,核间通信靠转发,而不是同步 X 系统级,想用必须得用整个框架来组织应用
grpc的线程池
一般的简单线程池

调整优先级

//cpu 优先级

    if (cpu_priority < current_cpu_priority) {
      TEST_SYNC_POINT_CALLBACK("ThreadPoolImpl::BGThread::BeforeSetCpuPriority",
                               &current_cpu_priority);
      // 0 means current thread.
      port::SetCpuPriority(0, cpu_priority);
      current_cpu_priority = cpu_priority;
      TEST_SYNC_POINT_CALLBACK("ThreadPoolImpl::BGThread::AfterSetCpuPriority",
                               &current_cpu_priority);
    }
//IO优先级
#ifdef OS_LINUX
    if (decrease_io_priority) {
#define IOPRIO_CLASS_SHIFT (13)
#define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data)
      // Put schedule into IOPRIO_CLASS_IDLE class (lowest)
      // These system calls only have an effect when used in conjunction
      // with an I/O scheduler that supports I/O priorities. As at
      // kernel 2.6.17 the only such scheduler is the Completely
      // Fair Queuing (CFQ) I/O scheduler.
      // To change scheduler:
      //  echo cfq > /sys/block/<device_name>/queue/schedule
      // Tunables to consider:
      //  /sys/block/<device_name>/queue/slice_idle
      //  /sys/block/<device_name>/queue/slice_sync
      syscall(SYS_ioprio_set, 1,  // IOPRIO_WHO_PROCESS
              0,                  // current thread
              IOPRIO_PRIO_VALUE(3, 0));
      low_io_priority = true;
    }
#else
    (void)decrease_io_priority;  // avoid 'unused variable' error
#endif

其实实现上都是queue(cond var + mutex) + threads (+ event (reactor/proactor))

cond var可以隐藏在队列上,也可以隐藏在future里,当然,更高级点,不用cond var用原子变量

简单的线程池+队列实现

#include <condition_variable>  
#include <functional>
#include <list>
#include <mutex>  
#include <string>
#include <thread> 
#include <vector>

class ThreadPool {
 private:
  const int num_workers_;
  std::list<std::function<void()> > tasks_;
  std::mutex mutex_;
  std::condition_variable condition_;
  std::condition_variable capacity_condition_;
  bool waiting_to_finish_ = false;
  bool waiting_for_capacity_ = false;
  bool started_ = false;
  int queue_capacity_ = 2e9;
  std::vector<std::thread> all_workers_;

  void RunWorker(void* data) {
    ThreadPool* const thread_pool = reinterpret_cast<ThreadPool*>(data);
    std::function<void()> work = thread_pool->GetNextTask();
    while (work != NULL) {
      work();
      work = thread_pool->GetNextTask();
    }

 public:
  ThreadPool(const std::string& prefix, int num_workers)
      : num_workers_(num_workers) {}

  ~ThreadPool() {
    if (started_) {
      std::unique_lock<std::mutex> mutex_lock(mutex_);
      waiting_to_finish_ = true;
      mutex_lock.unlock();
      condition_.notify_all();
      for (int i = 0; i < num_workers_; ++i) {
        all_workers_[i].join();
      }
    }
  }

  void SetQueueCapacity(int capacity) {
    queue_capacity_ = capacity;
  }

  void StartWorkers() {
    started_ = true;
    for (int i = 0; i < num_workers_; ++i) {
      all_workers_.push_back(std::thread(&RunWorker, this));
    }
  }

  std::function<void()> GetNextTask() {
    std::unique_lock<std::mutex> lock(mutex_);
    for (;;) {
      if (!tasks_.empty()) {
        std::function<void()> task = tasks_.front();
        tasks_.pop_front();
        if (tasks_.size() < queue_capacity_ && waiting_for_capacity_) {
          waiting_for_capacity_ = false;
          capacity_condition_.notify_all();
        }
        return task;
      }
      if (waiting_to_finish_) {
        return nullptr;
      } else {
        condition_.wait(lock);
      }
    }
    return nullptr;
  }

  void Schedule(std::function<void()> closure) {
    std::unique_lock<std::mutex> lock(mutex_);
    while (tasks_.size() >= queue_capacity_) {
      waiting_for_capacity_ = true;
      capacity_condition_.wait(lock);
    }
    tasks_.push_back(closure);
    if (started_) {
      lock.unlock();
      condition_.notify_all();
    }
  }
};

怎么动态增删线程?判断依据是啥?

#include <condition_variable>
#include <mutex>
#include <thread>
#include <queue>
#include <list>

class DynamicThreadPool
{
public:
    explicit DynamicThreadPool(int reserve_threads)
      :shutdown_(false), reserve_threads_(reserve_threads), nthreads_(0), threads_waiting_(0){
          for (int i = 0; i < reserve_threads_; i++) {
              std::lock_guard<std::mutex> lock(mu_);
              nthreads_++;
              new DynamicThread(this);
          }
      }
  
    ~DynamicThreadPool() {
        std::unique_lock<std::mutex> lock_(mu_);
        shutdown_ = true;
        cv_.notify_all();

        while (nthreads_ != 0) {
            shutdown_cv_.wait(lock_);        
        }

        ReapThreads(&dead_threads_);    
    }

    void Add(const std::function<void()> &callback) {
        std::lock_guard<std::mutex> lock(mu_);

        // Add works to the callbacks list
        callbacks_.push(callback);

        // Increase pool size or notify as needed
        if (threads_waiting_ == 0) {
            // Kick off a new thread
            nthreads_++;
            new DynamicThread(this);
        } else {
            cv_.notify_one();
        }

        // Also use this chance to harvest dead threads
        if (!dead_threads_.empty()) {
            ReapThreads(&dead_threads_);
        }
    }

private:
    class DynamicThread {
    public:
        DynamicThread(DynamicThreadPool* pool):pool_(pool),thd_(new std::thread(&DynamicThreadPool::DynamicThread::ThreadFunc, this)){}
        ~DynamicThread() {
            thd_->join();
            thd_.reset();    
        }

    private:
        DynamicThreadPool* pool_;
        std::unique_ptr<std::thread> thd_;
        void ThreadFunc() {
          pool_->ThreadFunc();

          // Now that we have killed ourselves, we should reduce the thread count
          std::unique_lock<std::mutex> lock(pool_->mu_);
          pool_->nthreads_--;

          // Move ourselves to dead list
          pool_->dead_threads_.push_back(this);

          if ((pool_->shutdown_) && (pool_->nthreads_ == 0)) {
              pool_->shutdown_cv_.notify_one();
          }
      }
    };
    
    std::mutex mu_;
    std::condition_variable cv_;
    std::condition_variable shutdown_cv_;
    bool shutdown_;
    std::queue<std::function<void()>> callbacks_;
    int reserve_threads_;
    int nthreads_;
    int threads_waiting_;
    std::list<DynamicThread*> dead_threads_;

    void ThreadFunc() {
        for (;;) {
            std::unique_lock<std::mutex> lock(mu_);
            // Wait until work is available or we are shutting down.
            if (!shutdown_ && callbacks_.empty()) {
                // If there are too many threads waiting, then quit this thread
                if (threads_waiting_ >= reserve_threads_)
                    break;
                threads_waiting_++;
                cv_.wait(lock);
                threads_waiting_--;
            }

            // Drain callbacks before considering shutdown to ensure all work gets completed.
            if (!callbacks_.empty()) {
                auto cb = callbacks_.front();
                callbacks_.pop();
                lock.unlock();
                cb();            
            } else if (shutdown_)
                break;            

        }
    }
  
    static void ReapThreads(std::list<DynamicThread*>* tlist) {
        for (auto t = tlist->begin(); t != tlist->end(); t = tlist->erase(t)) {
            delete *t;
        }
    }
};

work steal 概念

taskflow https://github.com/taskflow/work-stealing-queue

taskflow 文档 https://taskflow.github.io/taskflow/chapter2.html#C2_CreateAnExecutor


ref

  • https://www.jianshu.com/p/abf15e5e306b
  • https://blog.csdn.net/weixin_36145588/article/details/78545778

Read More

又招人啦

不招了。腾讯云架构平台部门。JD留着吧

我在这混了几年。没啥意思

Read More

future promise实现程度调研

Future promise
接口实现程度
std::future boost::future seastar::future Folly::Future
continuations/then() X
when_all X √*
when_any X X
whenN X X X
future超时 X*
指定Executor/线程池 X* X* √*
Executor动态调度
(包括增删线程/主动调度)
X X X* X*
异步文件io X X √* √*
  • 关于continuations,folly的when_all/when_any接口叫collect*

  • 关于超时,api不太一样,std::future::wait_for (从wait来的) 没有回调接口,folly::future::onTimeout

  • 关于指定Executor, boost::future 可以在then里指定 , 有接口但是没有样例。executor和future暂时不能结合使用

  • folly指定Executor通过via接口,不同的异步流程交给不同的executor来工作,,避免引入数据竞争,Executor可以说线程池(ThreadPollExecutor)也可以一个事件循环EventBase(封装libevent)

makeFutureWith(x)
  .via(exe1).then(y)
  .via(exe2).then(z);

可以中途变更executor,改执行策略,这也是标准库演进的方向,尽可能的泛型

  • std::future 和executor配合使用本来计划本来concurrency-ts中实现,规划c++20,后来作废了。支持std::experiental::executor 和std::experiential::future没有编译器实现,ASIO作者有个实现但是是很久以前的了

    新的executor得等到c++23了,目前标准库还在评审,一时半会是用不上了

但是ASIO是实现了executor了的,这里的异步抽象更高一些,和future接口没啥关系

void connection::do_read() 
{
  socket_.async_read_some(in_buffer_, 
    wrap(strand_, [this](error_code ec, size_t n)
      {
        if (!ec) do_read();
      }));
}
//strand_ 是asio中的概念,可以简单理解成executor,换成pool之类的也是可以的 
  • seastar 可以使用scheduling_group来规划不同的future,分阶段调度

    • 文件异步io AIO 系统api封装

ref

  • https://www.cnblogs.com/chenyangyao/p/folly-future.html
  • https://engineering.fb.com/developer-tools/futures-for-c-11-at-facebook/
  • https://www.modernescpp.com/index.php/std-future-extensions
  • https://www.modernescpp.com/index.php/a-short-detour-executors
  • https://stackoverflow.com/questions/44355747/how-to-implement-stdwhen-any-without-polling
  • asio的概念 executor/strandhttps://www.cnblogs.com/bbqzsl/p/11919502.html

Read More


十月待读

https://unixism.net/2019/04/linux-applications-performance-introduction/

https://www.moritz.systems/blog/mastering-unix-pipes-part-1/

https://neilmadden.blog/2020/11/25/parse-dont-type-check/

https://my.oschina.net/evilunix/blog/3003736

https://danlark.org/2020/11/11/miniselect-practical-and-generic-selection-algorithms/

https://github.com/y123456yz/reading-and-annotate-mongodb-3.6

https://zhuanlan.zhihu.com/p/265701877

https://blog.csdn.net/baijiwei/article/details/80504715

https://blog.csdn.net/baijiwei/article/details/78070355

qbe 一个小编译器后端 https://c9x.me/compile/

https://github.com/jsoysouvanh/Refureku

https://github.com/foonathan/lexy

http://stlab.cc/2020/12/01/forest-introduction.html

https://github.com/stlab/libraries/blob/develop/stlab/forest.hpp

https://medium.com/build-and-learn/fun-with-text-generation-pt-1-markov-models-in-awk-5e1b55fe560c

这个我估计以后也不会看。。。

Cli 程序设计规范

https://clig.dev/

https://danlark.org/2020/06/14/128-bit-division/

https://brevzin.github.io/c++/2020/12/01/tag-invoke/

Read More

std::future 为什么没有then continuation

本来concurrency-ts是做了future::then的,本计划要放在<experimental/future>

asio作者的实现 最终还是没合入

参考链接1里提到,这个方案作废了

参考链接2 的视频里 eric说了这个then contination方案的缺陷,future-promise的都要求太高

由于future-promise之间是需要通信且共享状态的,需要一些资源

  • condvar/mutex同步
  • 堆内存使用
  • 共享状态(shared_future)搞不好还得用引用计数
  • 保存不同种类的future需要type-erasure技术(类似std::any),这也是一笔浪费

作者的观点是lazy-future ,把资源动作全放在最后,把调用指定好,于是就有了一个泛化的std::then函数

Lazy future advantages

  • Async tasks can be composed…
    • … without allocation
    • … without synchronization
    • … without type-erasure
  • Composition is a generic algorithm
  • Blocking is a generic algorithm

展示的代码里没有future,就是各种lambda和execute和then 的结合

eric的作品链接在这里

https://github.com/facebookexperimental/libunifex 还在开发中。很有意思


ref

  • https://stackoverflow.com/questions/63360248/where-is-stdfuturethen-and-the-concurrency-ts
  • https://www.youtube.com/watch?v=tF-Nz4aRWAM
    • ppt https://github.com/CppCon/CppCon2019/blob/master/Presentations/a_unifying_abstraction_for_async_in_cpp/a_unifying_abstraction_for_async_in_cpp__eric_niebler_david_s_hollman__cppcon_2019.pdf
  • ASIO作者的设计 http://chriskohlhoff.github.io/executors/ 还挺好用的,用post取代std::async生成future,可以指定不同的executor,然后executor切换可以通过wrap来换,就相当于folly里的via 基本功能和folly差不太多了
  • 一个concurrency-ts future实现 https://github.com/jaredhoberock/future
  • executor设计还在推进中,我看计划是c++23,变化可能和eric说的差不多,https://github.com/executors/executors
    • http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0443r14.html
    • 文档看不下去?这有个介绍写的不错 https://cor3ntin.github.io/posts/executors/
  • 有个介绍实现无需type erasure的future C++Now 2018: Vittorio Romeo “Futures Without Type Erasure” https://www.youtube.com/watch?v=Avvhs3PLP7o 简单说就是编译期确定调用链结构,用模版
    • 还有个文档解说 https://www.maxpagani.org/2018/07/31/it18-zero-allocation-and-no-type-erasure-futures/

Read More

grpc介绍以及原理

介绍ppt,需要了解protobuf的相关概念

整体架构如图

底层通过http2传输,也就带了流式传输功能 单向流双向流,只要proto消息带上 stream修饰符就行了

service Greeter {
  rpc SayHello3 (stream HelloRequest) returns (stream HelloReply) {}
}

http2是必要的么?还是google内网服务全体推http2?所有服务全切到http?如果存量服务没有怎么办?http2能带来哪些优点?

  • 针对http1 链接共享,数据压缩,流量控制,低延迟,还支持流式

官方回应

HTTP2 is used for many good reasons: HTTP2 is a standard and HTTP protocol is well known to proxies, firewalls and many software tools. The streaming nature of HTTP2 suits our needs very well, so no need to reinvent the wheel.

感觉还是主推http2, google没有内部各种各样的协议交互的问题

直接基于tcp socket或者websocket一样性能不差,我猜google内部组件对接上都推http就没有考虑这些方案

使用接口

  • 同步一元调用 Unary /流式 stream
    • 一元调用就是request respons one shot的形式,流式就是一个弱化的tcp流的感觉
  • 异步,得用个消费队列来接收消息
// 记录每个 AsyncSayHello 调用的信息
struct AsyncClientCall {
    HelloReply reply;
    ClientContext context;
    Status status;
    std::unique_ptr<ClientAsyncResponseReader<HelloReply>> response_reader;
};
class GreeterClient 
{
public:
    GreeterClient(std::shared_ptr<Channel> channel)
        : stub_(Greeter::NewStub(channel)) {}
    void SayHello(const std::string& user) 
    {
        HelloRequest request;
        request.set_name(user);
        AsyncClientCall* call = new AsyncClientCall;
        // 异步调用,非阻塞
        call->response_reader = stub_->AsyncSayHello(&call->context, request, &cq_);
        // 当 RPC 调用结束时,让 gRPC 自动将返回结果填充到 AsyncClientCall 中
        // 并将 AsyncClientCall 的地址加入到队列中
        call->response_reader->Finish(&call->reply, &call->status, (void*)call);
    }
    void AsyncCompleteRpc() 
    {
        void* got_tag;
        bool ok = false;
        // 从队列中取出 AsyncClientCall 的地址,会阻塞
        while (cq_.Next(&got_tag, &ok)) 
        {
            AsyncClientCall* call = static_cast<AsyncClientCall*>(got_tag);
            if (call->status.ok())
                std::cout << "Greeter received: " << call->reply.message() << std::endl;
            else
                std::cout << "RPC failed" << std::endl;
			
            delete call;  // 销毁对象 
        }
    }
private:
    std::unique_ptr<Greeter::Stub> stub_;
    CompletionQueue cq_;    // 队列
};
int main()
{
    auto channel = grpc::CreateChannel("localhost:5000", grpc::InsecureChannelCredentials());
    GreeterClient greeter(channel);
    // 启动新线程,从队列中取出结果并处理
    std::thread thread_ = std::thread(&GreeterClient::AsyncCompleteRpc, &greeter);
    for (int i = 0; i < 100; i++) {
        auto user = std::string("hello-world-") + std::to_string(i);
        greeter.SayHello(user);
    }
    return 0;
}

不像trpc会生成异步的客户端代码,future/promise

rpc从简单到复杂

主要做三件事儿

  • 服务端如何确定客户端要调用的函数;
    • 在远程调用中,客户端和服务端分别维护一个函数名id <-> 函数的对应表, 函数名id在所有进程中都是唯一确定的。客户端在做远程过程调用时,附上这个ID,服务端通过查表,来确定客户端需要调用的函数,然后执行相应函数的代码。
  • 如何进行序列化和反序列化;
    • 客户端和服务端交互时将参数或结果转化为字节流在网络中传输,那么数据转化为字节流的或者将字节流转换成能读取的固定格式时就需要进行序列化和反序列化,序列化和反序列化的速度也会影响远程调用的效率。
  • 如何进行网络传输(选择何种网络协议)
    • 多数RPC框架选择TCP作为传输协议,也有部分选择HTTP。如gRPC使用HTTP2。不同的协议各有利弊。TCP更加高效,而HTTP在实际应用中更加的灵活。

服务端注册函数,客户端调用名字对应的函数演化成 定义IDL文件描述接口,服务端实现接口,客户端调用接口 ,名字-函数注册信息全部隐藏在框架背后,并且,接口参数复杂化

protobuf或者thift能提供接口参数的codegen/parse

最简单的rpc ,拿nanorpc举例子

服务端,注册函数名字 函数(存一个map)

auto server = nanorpc::http::easy::make_server("0.0.0.0", "55555", 8, "/api/",
                                               std::pair{"test", [] (std::string const &s) { return "Tested: " + s; } }
                                              );

std::cout << "Press Enter for quit." << std::endl;
std::cin.get();

客户端,调用名字函数

auto client = nanorpc::http::easy::make_client("localhost", "55555", 8, "/api/");
std::string result = client.call("test", std::string{"test"});
std::cout << "Response from server: " << result << std::endl;

中间有个解析组件,把客户端发的函数名和参数拿到,从函数map里拿到函数,调用,结束

grpc这些框架,如何处理这些步骤?

首先,函数表,框架自身保存好,生成接口函数,既然客户端都直接调用接口函数了,这还算rpc么?

框架的客户端调用大多是这个样子的 grpc cpp为例子

 Status status = stub_->SayHello(&context, request, &reply);

生成的客户端接口和服务端的实现接口不一样,代码生成会生成一个代理类(stub), 中间框架自身根据注册信息和context调用服务端的实现,只有request response是完全一致的,函数是不一致的

劣势

  • grpc没有服务自治的能力,也没有整体插件化,不支持插件注入

现在公司使用框架,各种组件组合使用的场景非常多

比如公司级别统一的日志框架,公司级别统一的名字服务,公司级别统一的指标收集平台,统一的配置文件管理/下发平台

提供统一的访问平台来查阅,而不是傻乎乎的登到机器上,等登到机器上事情就闹大了

假设你要做一个新的rpc框架,你需要

  • proto文件要能管理起来
  • 生成文件组合到构建脚本之中 smfrpc框架的codegen很有意思,有机会可以研究一下
  • 服务治理相关的能力,预留logger接口,预留的指标采集接口
  • rpc消息染色怎么做?
  • 熔断机制/限流机制
  • 要不要做协议无关的rpc?tcp/http2 各有优点,某些场景协议特殊,比如sip,需要自己加seq管理
  • rpc的io模型,future/promise?merge? Coroutine?

假设你要做一个使用rpc的服务,你需要

  • 灵活的配置文件更新组件,查觉配置文件变动,重新加载 (可能需要公司级别的配置文件下发更新平台)
  • 域名名字服务,最好用统一的
  • 预留好日志logger,指标采集接口方便注入
  • 灰度倒入流量怎么做?tcpcopy?

ref

  • 知乎有个专栏,列了很多,可以参考 https://www.zhihu.com/column/c_1099707347118718976

  • https://colobu.com/2017/04/06/dive-into-gRPC-streaming/

  • 一篇grpc-go的分析 https://segmentfault.com/a/1190000019608421

  • protobuf指南 https://blog.csdn.net/u011518120/article/details/54604615

  • 一个future-promise rpc https://github.com/loveyacper/ananas/blob/master/docs/06_protobuf_rpc.md

  • 腾讯的phxrpc https://github.com/Tencent/phxrpc

  • 腾讯的tarsrpc 腾讯rpc真多啊 https://github.com/TarsCloud/TarsCpp

  • https://github.com/pfan123/Articles/issues/76 这个介绍了thrift


Read More


类型擦除技术 type erasure以及std::function设计实现


本文是type erased printabledesign space for std::function 的整理总结

说是类型擦除技术,不如说是多态技术

函数指针多态 几种做法

  • void* 传统的万能参数
  • 继承接口值多态,dynamic_cast
  • 值语意的多态,type erasure 也就是类型擦除
    • std::function boost::any_range boost::any

来举个例子, type erased printable

打印托管的值 godbolt链接

#include <memory>
#include <ostream>

struct PrintableBase {
    virtual void print(std::ostream& os) const = 0;
    virtual ~PrintableBase() = default;
};

template<class T>
struct PrintableImpl : PrintableBase {
    T t_;
    explicit PrintableImpl(T t) : t_(std::move(t)) {}
    void print(std::ostream& os) const override { os << t_; }
};

class UniquePrintable {
    std::unique_ptr<PrintableBase> p_;
public:
    template<class T>
    UniquePrintable(T t) : p_(std::make_unique<PrintableImpl<T>>(std::move(t))) { }

    friend std::ostream& operator<<(std::ostream& os, const UniquePrintable& self) {
        self.p_->print(os);
        return os;
    }
};

#include <iostream>

void printit(UniquePrintable p) {
    std::cout << "The printable thing was: " << p << "." << std::endl;
}

int main() {
    printit(42);
    printit("hello world");
}

直接打印值(传引用) Godbolt.

#include <ostream>

class PrintableRef {
    const void *data_;
    void (*print_)(std::ostream&, const void *);
public:
    template<class T>
    PrintableRef(const T& t) : data_(&t), print_([](std::ostream& os, const void *data) {
        os << *(const T*)data;
    }) { }

    friend std::ostream& operator<<(std::ostream& os, const PrintableRef& self) {
        self.print_(os, self.data_);
        return os;
    }
};

#include <iostream>

void printit(PrintableRef p) {
    std::cout << "The printable thing was: " << p << "." << std::endl;
}

int main() {
    printit(42);
    printit("hello world");
}

这两种类型擦除,一个是统一接口,记住值类型,然后打印方法和类型绑定

一个是干脆在一开始就吧打印方法实例化,值类型 void* 存地址

也就是上面说到的两种技术的展开

第一种虚函数的方法是有开销的

说到std::function和std::any,标准库为这种虚函数做了优化,也叫SBO

首先从std::function的设计谈起

  • 函数要不要保存?保存就是std::function,不保存就是function_ref. (一种view,提案中)

    • 需求,需不需要管理这个函数,生命周期等,function_ref只用不管
  • 需不需要拷贝?需要就是std::function,不需要拷贝就是std::unique_function. (一种unique guard,提案中) 也就是move-only

  • 需不需要共享?共享带来函数副作用了

    uto f = [i=0]() mutable { return ++i; };
    F<int()> alpha = f;
    F<int()> beta = alpha;
    F<int()> gamma = f;
    assert(alpha() == 1);
    assert(beta() == 2);  // beta shares alpha's heap-managed state
    assert(gamma() == 1);  
    

    可能会有个shared_function的东西(我感觉多余)

  • SBO相关设计 类似SSO 就是在栈上开个buffer存,不分配堆资源

    • buffer大小?要不要可定制? 当前不同的标准库用的buffer不一样,clang libc++ 是24 gcc libstdc++是16

    自己设计,可能会定制化

    template<class Signature, size_t Capacity = 24, size_t Align = alignof(std::max_align_t)>
    class F;
      
    using SSECallback = F<int(), 32, 32>;  // suitable for lambdas that capture MMX vector type
    

    这点子没人想过?不可能,已经有人实现了 inplace_function.

  • 如果SBO优化不了怎么办?是不是需要支持alloctor接口?

  • 强制SBO,不能SBO的直接编译爆错,inplace_function.做了

  • SBO优化,要保证对象nothrow

    • static_assert(std::is_nothrow_move_constructible_v<T>) inside the constructor of F.
  • SBO优化针对not trivially copyable如何处理

    • libc++ 保证可以SBO,但是libstdc++没有
    • 通过static_assert(is_trivially_relocatable_v && sizeof(T) <= Capacity && alignof(T) <= Align) inside the constructor of F控制
  • function能不能empty,能不能从nullptr构造

  • function之间能不能转换类型?

还有很多角落,不想看了,这也是有各种function提案补充的原因


ref

  • https://www.newsmth.net/nForum/#!article/Programming/3083 发现个02年的介绍boost::any的帖子卧槽,历史的痕迹
  • https://akrzemi1.wordpress.com/2013/11/18/type-erasure-part-i/
  • 历史的痕迹 any_iterator http://thbecker.net/free_software_utilities/type_erasure_for_cpp_iterators/any_iterator.html
  • std::function实现介绍 gcc源码级https://www.cnblogs.com/jerry-fuyi/p/std_function_interface_implementation.html
  • std::function实现介绍,由浅入深 https://zhuanlan.zhihu.com/p/142175297
  • 这个文章写的不错。我写了一半发现有人写了。。。 直接看这个就好了https://fuzhe1989.github.io/2017/10/29/cpp-type-erasure/

Read More

^