偶爾討論到了socket發送數據時是否應該加鎖的問題,就在網上查了一下,下面是大神陳碩的答案
- 對於 UDP,多線程讀寫同一個 socket 不用加鎖,不過更好的做法是每個線程有自己的 socket,避免 contention,可以用 SO_REUSEPORT 來實現這一點。
- 對於 TCP,通常多線程讀寫同一個 socket 是錯誤的設計,因為有 short write 的可能。假如你加鎖,而又發生 short write,你是不是要一直等到整條消息發送完才解鎖(無論阻塞IO還是非阻塞IO)?如果這樣,你的臨界區長度由對方什么時候接收數據來決定,一個慢的 peer 就把你的程序搞死了。
這里解釋下什么是short write
對於一個非阻塞的TCP套接口,如果其發送緩沖區中根本沒有空間,輸出函數調用將立即返回一個EWOULDBLOCK錯誤。如果其發送緩沖區中有一些空間,返回值將是內核能夠拷貝到該緩沖區中的字節數。這個字節數也稱為不足計數(應該就是short write的意思)
那么陳碩大神的意思是 如果接收方的滑動窗口一直為0,則發送方一直不會發送數據,則會一直持有鎖,就算是非阻塞的,立刻返回后我們就不對接下來的數據進行發送了么?則一直等待在那里
socket分為阻塞模式和非阻塞模式。
阻塞 | 非阻塞 | |
read | 接收緩沖區只要有數據就立即返回,只有當接收緩沖區為空時才阻塞等待 | 無論有無數據,都立即返回 |
write | 只有發送緩沖區可以放下整個buffer時才返回,否則阻塞 | 無論發送緩沖區是否能放下整個buffer,都立即返回。返回能夠放下的字節數 |
1. read總是在接收緩沖區有數據時立即返回,而不是等到給定的read buffer填滿時返回。
只有當receive buffer為空時,blocking模式才會等待,而nonblock模式下會立即返回-1(errno = EAGAIN或EWOULDBLOCK)
2. blocking的write只有在緩沖區足以放下整個buffer時才返回(與blocking read並不相同)
nonblock write則是返回能夠放下的字節數,之后調用則返回-1(errno = EAGAIN或EWOULDBLOCK)
對於blocking的write有個特例:當write正阻塞等待時對面關閉了socket,則write則會立即將剩余緩沖區填滿並返回所寫的字節數,再次調用則write失敗(connection reset by peer),這正是下個小節要提到的: