grpc 当前版本的 C++ async API, 是基于 completion queue 实现的.
为了解决这个问题, 我只好用一个"全局唯一的 uint64 id"标识对象, 并在分配 id 的时候做了区间划分, 比如 * id + 0 用来标识 "连接创建事件"
然后下一个新的对象 id , 在上一个 id 的基础上 + 4
这是我的 demo:
https://github.com/DinoStray/grpc_example_async_cpp_api
关于这个问题, 几年前就有人开始吐槽(描述的问题和我一模一样)
https://groups.google.com/forum/#!topic/grpc-io/7P8QvBBNq_E
grpc 官方也给了回应, 并表示将优化 api
https://github.com/grpc/grpc/issues/7352
可是 2016 年的回应, 都过去 4 年了, 还没完成....吐血
1
xkeyideal 2020-03-11 09:42:11 +08:00
用亲儿子啊
|
2
codehz 2020-03-11 09:50:42 +08:00 via Android 1
(这设计确实挺恶心的)
不过作为 workaround,我觉得你可以用位运算来简化问题( 其中 id 就表示对象 id,按 1 递增 然后弄个事件类型的 enum (type) (id << 2) | type 来合成最终的 id(设为 x) 提取就可以通过 id = x >> 2 和 type = x & 0b11 来解决了 类似的想法在 windows 的 handle 上也有应用( 你仔细看 win 的进程 id,会发现它永远是 4 的倍数,同样也是尾部的 2 个 bit 用于区分类型) |
3
DinoStray OP @codehz good idea.
可我刚刚发现自己转牛角尖了. 也许作者在设计这套 api 的时候, 就想让使用者每个 rpc 请求都有单独的 completion queue, 也就是最早的 "每个 tcp 连接 一个线程" 模型. 我 epoll 用惯了, 思维方式固化了, 老是想着限制线程数量, |
5
icylogic 2020-03-11 10:33:16 +08:00
这个 tag 是个 void*,不一定非得是 integer 啊?你放个 CallData* ,pair<id, event>或者 callback 指针不可以吗,CallData 里边啥都可以放啊。
|
6
icylogic 2020-03-11 10:38:01 +08:00
|
7
jonah 2020-03-11 10:38:43 +08:00
我一般是这么做的:传进去一个类或者结构体,里面保存请求时的上下文,比如你这里面的事件类型。
目前这个 grpc c++封装是基于 c 的,搞成 Async 异步接口意味着库里面要有线程读 cq,不一定灵活。 |
8
DinoStray OP @icylogic 官方 demo 就是个指针, 我的问题是, 如果用返回值标识指针, 就没办法区分事件类型了, 比如异步读和异步写
|
9
DinoStray OP @icylogic 这个 demo 我研究过了, 问题在于他没用 stream, 我的核心需求是实现 pubsub 模型, 所以必须用 stream, 基于一些设计, 还得是 bi-di stream
|
10
janxin 2020-03-11 11:19:56 +08:00
C++的 gRPC 确实很值得吐槽,要么考虑一下 brpc ?
|
11
tyrantZhao 2020-03-11 11:23:17 +08:00
cpp 的 grpc 槽点太多了
|
12
DinoStray OP @icylogic 这个官方 demo , 如果同时做异步读写, 那这个 demo 的模型就跑不通了, 因为无法区分读完成和写完成
|
14
DinoStray OP @jonah 读和写都是同步的, 不存在先后, 如果读写同步, 只能通过一个 completion queue 返回, 那你在对象成员里也没办法做区分了
|
15
DinoStray OP @jonah 如果先读再写, 或者先写再读, 所有的事件基于线性产生, 那这个 demo 的模型的确可以跑通, 虽然实现有点繁琐. 可我的目标是 bi-di stream, 这时候就不适用了
|
16
DinoStray OP @jonah 再详细点说, 异步读和异步写都开启了, 这时候 completion queue 返回, 你可以根据返回的 tag 知道是哪个对象产生了事件, 可没有办法区分是读完成了, 还是写完成了
|
17
jonah 2020-03-11 11:57:40 +08:00
@DinoStray 可以区分,completion queue Next()返回的指针就是你请求时传递的那个对象指针,请求时你是知道是读还是写的。 demo: https://gist.github.com/byteink/2b998cf1e641ce8ef8965d081f0814e1
|
19
DinoStray OP @jonah 这个 demo 在同一时间, 只能读, 或者只能写, 要么写完了再读, 要么读完了再写. 在 bi-di stream 模型中, 我的需求是随机性有 N 个 request, 随机性有 M 个 reply, request 和 reply 没有任何关联
|
20
jonah 2020-03-11 12:05:31 +08:00
@DinoStray 没理解为啥线性化,你试试吧,completion queue Next 返回的顺序本来就是任意的,哪个先完成就先返回,不然也不需要有个指针来对应了。这个跟你现在的原理是等价的,只是传的结构优雅些。
|
22
DinoStray OP @jonah 你的这个封装, 和我把 session id + event 事件 的解决方式, 是类似的, 不过有一点, 我的代码习惯用 C++11 智能指针管理, 不太想直接传递裸指针
|
23
DinoStray OP @DinoStray 你的意思, 是每次调用异步 api, 都 new 一个新的对象, 新的对象封装了 session 和事件, 异步 api 回调的时候, 就可以通过这个对象知道 session 和产生的 event, 然后直接销毁这个临时的对象, 我的理解没错吧
|
24
jonah 2020-03-11 12:17:43 +08:00
@DinoStray unique_ptr 更安全,传的时候 release 一下,Next 回来就用 unique_ptr 包起来。但是传递给 completion queue 避免不了裸指针的,它调的底层 c 的接口。
对, 是这个意思,我看你主题里说主要问题是需要获取同时多个信息,这种是个通用的做法,可以保存任何必要的信息。 |
25
DinoStray OP @jonah 我一直觉得用了智能指针就不能暴露裸指针了, 这两个东西混太容易导致程序崩溃. 我看 grpc 源码, 到处都是 unique_ptr 和裸指针的混用, 看的蛋疼
|