brpc的futex:
需要系统调用去看有没有人竞争 释放时也需要系统调用去看看有没有人等 性能开销
但很多时候不存在竞争 这种情况下 不系统调用
futex 位于用户态(自旋去CAS成功最好)若失败,有竞争,再用系统调用挂起进程
用户态需要维护一个等待队列:(便于释放时唤醒)
实际的类叫simufutex:内部有一个mutex和cond
Brpc源码中 有系统实现的futex和自己实现的futex
协程:结构体,要存寄存器的值(上下文),有指针指向自己的空间 yield resume
不是N:1是M(协程):N(线程) 可以work-steal 可(协程)级别的挂起和唤醒
Butex实现了(协程)级别的挂起和唤醒:里面封了一个mutex或futex
里面的Mutex和futex是用来挂起线程,协程挂起在用户层实现的,加锁就是CAS并yield,没有走系统调用。相当于挂起就是从任务队列移出去,唤醒就是把这个协程加入任务队列。
真正的监听在epoll_create和epoll_add后就开始了,epoll_wait就是去查询一下内核的就绪链表并返回,它有阻塞和非阻塞两种,阻塞就是要设一个超时时间。我看muduo和linya和brpc都是用的阻塞 会挂起吗?
我的理解是 会的 会让出cpu 应该是线程级别的挂起
从内核缓存区读到应用层缓冲区耗时:指的是,可以把网络内容理解成文件,内核缓存区理解成文件缓存区,估计说的耗时指的是从网络到内核缓冲区。
Bthread1 epoll_wait
Bthread2 读取数据 每解析一个完整的应用层报文 ,就会为每个报文的处理再专门创建一个bthread
M:N 1:1 N:1
M:N相比N:1 协程可以在多个线程上切换 一会在线程1上 一会在线程2上 所以可以利用多核的特点
多个线程上的bthread向一个fd写
每个fd维护一个链表,第一个来写的写,后面的就把要写的放入链表并返回 bthread执行结束,挂起等待回复
第一个bthread写如尽可能多数据 (其他人放入链表的它也写) 但是最多只能把内核缓冲区写满,如果还写 就启动个bthread写 自己挂起等待回复 我分析原因是 如果内核已满还写会线程挂起
新的bthread写 但当内核满后,挂起当前bthread 等到epoll通知fd可写时,该thread再被唤起,继续写入。这个bthread要负责把链表上东西写完才结束
这是作者的一句话"bthread 1不可能去等待fd的内核inode输出缓存是否有可用空间,否则会令bthread 1所在的整个pthread线程卡顿 KeepWrite bthread在向fd写数据时,fd一般被设为非阻塞,如果fd的内核inode输出缓存已满,对fd调用::write(或者::writev)会返回-1且errno为EAGAIN"
让RPC使用同步方式,这里的同步是指bthread间的同步:负责发送数据的bthread完成发送操作后,不能结束,而是需要挂起,等待负责接收服务器响应的bthread将其唤醒后,会有bthread是在epoll_wait,而且它会创建bthread去接收数据,被创建的bthread接收完后再去唤醒,再恢复执行。挂起时会让出cpu,pthread可继续执行任务队列中的其他bthread。
有done是异步 没done是同步?
Bthread是同步 但是是协程级别的阻塞
不用bthread的话 就是线程级别的阻塞
摘自源码的一句话:// Because `done’(last parameter) is NULL, this function waits until the response comes back or error occurs(including timedout).
Done是从外面传入的 所以可以在本次调用结束后用done 所以能异步
我们的rgpu 服务端是异步(有done)但是我们是在函数返回之前调用的if (rpc_done) {
rpc_done->Run();
}所以相当于同步
客户端是同步(没done) 且客户端是线程级别的阻塞 优化方向:引入bthread 搞成协程级别的阻塞
example中对于多线程向同一fd写有use_bthread选项可以选择,use_bthread为false是创建多个线程(这种情况下多个线程写也不用加大锁,内部是保证了同步机制的,且Brpc底层有stub机制 哪个线程的哪个request发的 就会把回复放回对应的response里面) 为true为创建bthread,且内部会自动维护
摘自源码的一句话:// A Channel represents a communication line to a Server. Notice that
// Channel is thread-safe and can be shared by all threads in your program.
bthread_start_background才是用了bthread bthread_join
每个pthread启动后以自己的TaskGroup对象的run_main_task函数作为主工作函数,在该函数内执行无限循环,不断地从TaskGroup的任务队列中取得bthread id、通过id找到bthread对象、去执行bthread任务函数。
Controller是在函数里创建的:
example::EchoRequest request;
example::EchoResponse response;
baidu::rpc::Controller cntl; 比如设置重连次数 超时时间这些
在Controller对象中有标识一次RPC过程的唯一id correlation_id
与Controller 相关联的 Id对象(内部有butex) 用来同步一次rpc的多个pthread上的多个bthread 因为一次rpc是需要多个pthread上的多个bthread共同完成的 这个Controller就会被这么多bthread同时访问 就需要有一种同步机制
在rpc结束后 Id对象会进入资源池 其他rpc可以复用