当前位置:网站首页>多线程问题:为什么不应该使用多线程读写同一个socket连接?
多线程问题:为什么不应该使用多线程读写同一个socket连接?
2022-07-23 21:58:00 【_夕】
问题的产生
经典的单reactor多线程模式采用的是用主线程处理连接事件以及socket读写事件,业务逻辑的处理则是让线程池里的线程各自竞争处理。
既然多线程这么方便,为什么不让线程池里的线程也参与到read和send这个过程中呢?
在发送数据的过程中,即使TCP的发送缓存满了,我们也可以记录下当前成功发送了多少字节,然后再次注册一个EPOLLOUT事件,只需等待下次可写事件,继续让子线程发送数据即可,岂不是美哉?
解释
陈硕大佬的解释
对于 TCP,通常多线程读写同一个 socket 是错误的设计,因为有 short write 的可能。假如你加锁,而又发生 short write,你是不是要一直等到整条消息发送完才解锁(无论阻塞IO还是非阻塞IO)?如果这样,你的临界区长度由对方什么时候接收数据来决定,一个慢的 peer 就把你的程序搞死了。
知乎 陈硕的回答
陈硕大佬提到,多线程读写同一个socket是错误的设计,网络上也有这样的说法:如果你让多线程读写同一个socket,这将是你噩梦的开始。
why?首先解释一下陈硕提到的short write:我是这样理解的,由于内核的TCP发送缓存已满(或者快要满),本次发送的数据小于预期要发送的数据。
如果为了追求每次发送一个完整的数据包,可以采用加锁的方式,让其他线程无法访问这个socket,直到整个数据包完成发送。实际上,这是一个非常危险的设计:在等待发送完整数据包的过程中,整个线程是处于一个阻塞的状态。从效率上来说,与单线程阻塞send无异;从安全性来说,就像陈硕所说的,如果客户端设定接收窗口极其小,或者接收速度非常慢,你的线程将会阻塞相当一段长的时间。如果多来几个这样的客户端,你的所有线程都会被阻塞,你的程序也就死掉了。
以上是陈硕对多线程读写socket不应该加锁的解释,但仍然没有满足我的所有疑问。
对于Linux内核来说,write和read都是会自动对内核缓冲区加锁的,无需担心线程安全的问题。我们也没有必要追求一次性发送完整个数据包,只需要再次注册写事件,等待下一次可写事件发生就行,为何不应该读写同一个socket?
网络博客的回答
A、B两个线程,其中A每次写入32k,32k可能会被拆分成多次写入(根据buffer剩余空间决定真正能写入多少数据);B每次写入10bytes。如果内存不足(图中的wait_for_sndbuf和wait_for_memory)只写入一部分数据那么内核会调用sk_stream_wait_memory等待内存,而这个函数里面会释放sk。完整的调用链sk_stream_wait_memory->sk_wait_event->lock_sock。
当A写入数据的时候资源不足所以写入不完整于是释放资源,而B此时有机会被执行后刚好资源得到释放,于是写入成功;而A再次被执行的时候继续写入未完成的数据时,B已经“乱入”成功。
这篇文章算是解答了我的不少疑惑。
当有两个线程同时写一个socket时,当第一个线程A获得了内核缓冲区的使用权,写了一半发现满了,无奈停止写入。当第二次可写事件发生的时候,程序并不知道线程A有没有完整写完数据,这时候另一个线程B抢在A之前竞争得到了内核缓冲区的使用权,开始往里面写数据。
这样导致了一个严重的后果,线程A的数据仅仅写了一半,就被线程B的数据“乱入”。对于解析应答数据的客户端来说,AB混杂的数据完全没有办法解析,那么这次通信就是完全失败的。
如果只有一个线程负责读写的话,当processor处理完数据后,会按照指定的顺序(需要一些线程同步手段)提交给主线程,让主线程的handler按顺序老老老实实发送数据。
这个文章解决了我大部分问题,我还有最后一个问题:什么情况下才会发生这么奇怪的情况?要在什么条件下,才会有多个线程各自准备好了一个数据包想要发送呢?
我的解释
我之所以会有最后一个问题,是因为被我浅薄的编程经历给限制住了思想。在网络编程中,比较普遍的做法是一个socket fd对应一个事件,通常是用数组实现的,fd对应数组的下标。这实际上是一种很巧妙也很安全的做法,保证了同一个socket的所有操作只会在一个对象中发生,辅以线程安全的一些限制,一般不会出现两个线程操作同一个fd的情况。
但如果事件与fd不是一一对应的呢?也许可以有一个fd对应多个事件,那么就会出现这样的情况:多个线程同时处理同一个fd的不同请求,并且同时处理完成,将要发送数据,就会出现数据“乱入”的情况。自此,问题就算全部解决了
总结
问题1:为什么tcp连接socket不应该加锁?
在等待发送完整数据包的过程中,整个线程是处于一个阻塞的状态。从效率上来说,与单线程阻塞send无异;从安全性来说,就像陈硕所说的,如果客户端设定接收窗口极其小,或者接收速度非常慢,你的线程将会阻塞相当一段长的时间。如果多来几个这样的客户端,你的所有线程都会被阻塞,你的程序也就死掉了。
问题2:多线程读写同一个socket会出现什么问题?
当有两个线程同时写一个socket时,当第一个线程A获得了内核缓冲区的使用权,写了一半发现满了,无奈停止写入。当第二次可写事件发生的时候,程序并不知道线程A有没有完整写完数据,这时候另一个线程B抢在A之前竞争得到了内核缓冲区的使用权,开始往里面写数据。
这样导致了一个严重的后果,线程A的数据仅仅写了一半,就被线程B的数据“乱入”。对于解析应答数据的客户端来说,AB混杂的数据完全没有办法解析,那么这次通信就是完全失败的。
问题3:什么情况会出现上面所说的问题?
多个线程同时处理一个同一个fd的不同请求,并且同时处理完成,将要发送数据,就会出现数据“乱入”的情况。
最后最后一个问题
如何实现不同线程操作同一个事件的线程安全问题?正如前文所说,常规的做法是一个fd对应一个Event,如果一个子线程正在处理当前fd的Event,此时该fd又发送来一个请求,那么线程池分配的另一个子线程会对同一个Event进行操作,我认为这是十分危险的。具体的解决方案,我相信会在不久以后得到答案,届时再来补充上这个问题的答案。
边栏推荐
- Cluster chat server: how to solve the problem of cross server communication | redis publish subscribe
- Uniapp uses canvas to write a circular progress bar
- Why cluster chat server introduces load balancer
- Application of performance test knowledge to actual combat
- Complete set of official openlayers instances
- Openlayers instance advanced view positioning advanced view positioning
- Apprentissage Lambda (utilisation du comparateur après tri, regroupement après collecte avec collectors.groupingby)
- Principle and implementation of hash table, unordered set and mapping
- Pulsar open source message queue_ Understand pulsar --- pulsar work notes 001
- Improving performance with explicit rendering
猜你喜欢

Altium designer—Arduino UNO原理图&PCB图(自制Arduino板)

DBSCAN point cloud clustering

Jmeter性能综合实战——签到及批量签到

Deep learning - NLP classic papers, courses, papers and other resources sorting and sharing

Use Gaode map JS API 2.0 to load the starting and ending path tracks

Application of performance test knowledge to actual combat

Openlayers instance advanced view positioning advanced view positioning

Day109. Shangyitong: integrate Nacos, hospital list, drop-down list query, hospital online function, hospital details query

I, AI doctoral student, online crowdfunding research topic

A stack of digital robots were selected in Gartner's China AI market guide
随机推荐
Leaderboard design in game server
U++ learning notes control object scale
Union and union all of Hana SQL
& 9 nodemon automatic restart tool
-2021 sorting and sharing of the latest required papers related to comparative learning
University database creation and query practice -- database table design
Neo4j应用
Storage structure and management disk. It's a bit like installing Win98. You need to partition and format the hard disk first
JMeter performance comprehensive practice - sign in and batch sign in
Yushu A1 robot dog gesture control
阿里onedate分层思想
Sixty final review questions of software architecture
节流和防抖的说明和实现
A stack of digital robots were selected in Gartner's China AI market guide
PCL error: error c2589 "(": "::" illegal mark on the right)
存储结构和管理盘。有点像装win98要先分区格式化硬盘
Complete set of official openlayers instances
Openlayers instances advanced mapbox vector tiles advanced mapbox vector maps
初探POC编写
lambda学习(sort后面的Comparator的使用,collection后使用Collectors.groupingBy分组)