- 析构的安全性
在多线程中,两个裸指针指向同一个对象是不安全的,无法保证析构的正确性,一个线程在析构一个对象而同时另一个线程在使用它,即一个线程不能确定是否对象已经完全死掉,有可能另一处析构函数正在执行。
shared_ptr可以保证在所有地方都不再使用时才释放,之前保证资源的有效性。
2.shared_ptr的线程安全
shared_ptr可以实现线程安全的释放对象,但shared_ptr本身不是线程安全的!如果一个shared_ptr对象可以被几个线程同时看到,那么读写这个对象就需要加锁!因为它内部有两个数据成员,读写操作不能原子化。
shared_ptr的复制开销比原始指针高,一个线程只需要在最外层函数有一个实体对象,之后就可以传引用。
将shared_ptr绑定到回调函数里会延长对象的生命周期,有时不是我们希望的,可以绑定weak_ptr到回调函数,在使用时尝试lock提升为shared_ptr,提升不成功就不调用,即陈硕所说的弱回调。 - 关于http可以看这篇文章:https://segmentfault.com/a/1190000011172823,关于http1.1和http2.0同时发送多个请求的区别。
- 每个文件描述符只由一个线程操作,既能解决收发顺序问题,也避免关闭文件描述符引起的race condition, 一个线程可以操作多个fd,但一个线程不能操作别的线程拥有的文件描述符。
epoll也一样,应该把同一个epoll fd的操作都放到同一个线程中执行。
4. !!!
POSIX分配文件描述符的方式在不注意的情况下会出现串话,比如说在处理一个连接的请求过程中,另一个线程将这个fd close了,然后又来了新的连接分配了相同的fd,这样处理之前的请求的响应会发错。
解决办法是RAII,用class去管理fd, 在析构函数中关闭文件描述符,这样的话只要这个class对象还活着,对应的fd就不会close,也就不会串话了。
当对方客户端断开连接之后,这个tcp connection也即将走到尽头,但这时我们不能马上delete这个对象,因为其他地方可能还持有这个对象,贸然delete可能会造成悬空指针,只有确保其他地方没有持有该对象引用的时候才能安全销毁–>第二条里说的shared_ptr或weak_ptr
shared_ptr可以保证原fd不会被关闭,weak_ptr提升失败也能知道原资源已经销毁。
所以可以用shared_ptr或shared_ptr+weak_ptr管理TcpConnection的生命周期。
考虑这样的一种情况:
假如在监听一个fd的EPOLLERR和EPOLLOUT事件,当这个fd的请求在处理过程中,对端关闭了连接,触发EPOLLERR事件然后直接被另一个线程关闭了,但是上一个请求还在处理,这样就会串话。 - signalfd函数
- 对一个对端已经关闭的socket调用两次write, 第二次将会生成SIGPIPE信号, 该信号默认结束进程.
具体的分析可以结合TCP的”四次握手”关闭. TCP是全双工的信道, 可以看作两条单工信道, TCP连接两端的两个端点各负责一条. 当对端调用close时, 虽然本意是关闭整个两条信道, 但本端只是收到FIN包. 按照TCP协议的语义, 表示对端只是关闭了其所负责的那一条单工信道, 仍然可以继续接收数据. 也就是说, 因为TCP协议的限制, 一个端点无法获知对端的socket是调用了close还是shutdown.
对一个已经收到FIN包的socket调用read方法, 如果接收缓冲已空, 则返回0, 这就是常说的表示连接关闭. 但第一次对其调用write方法时, 如果发送缓冲没问题, 会返回正确写入(发送). 但发送的报文会导致对端发送RST报文, 因为对端的socket已经调用了close, 完全关闭, 既不发送, 也不接收数据. 所以, 第二次调用write方法(假设在收到RST之后), 会生成SIGPIPE信号, 导致进程退出.
为了避免进程退出, 可以捕获SIGPIPE信号, 或者忽略它, 给它设置SIG_IGN信号处理函数
6. 减少mutex临界区的方法:
在加锁之前先准备好一个空的容器,如vector,加锁后用空的vector swap要处理的vector,然后解锁,在临界区外处理swap出来的数据。