UDP socket能否被多線程同時調用sendto來發送數據?

程序里有多個線程調用同一個UDP socket的sendto操作來發送數據,目前沒有做互斥,不知道這個做法是否合適。

如果是TCP,那麼肯定是需要互斥加鎖的,因為TCP存在一個發送緩存,多線程一起send會有問題。但對UDP我就不太確定了,網上找了下也沒有相關的討論。


我認為不用加鎖。
這是 TCP 網路編程和 UDP 網路編程的重大(本質)區別,由於有 short read/short write,一個 TCP socket 只能固定由一個線程訪問,否則很容易出錯(詳情見書第 4.6 節)。而 UDP 則不受此限制,多個線程可以安全地讀寫同一個 UDP socket。


其實你表述的不太對,對於TCP,多個線程同時使用send和receive,對於操作系統來說也是沒有問題的,只是對於你的程序來說是個大問題,一個流的數據被不同線程獲取到了,這個程序就很難寫對。操作系統對於每個文件號的操作,無論是read、write、send還是receive都是多線程安全的。
那麼UDP自然是可以多個線程使用sendto的。


系統調用一級當然是線程安全的,這個意義上講無需加鎖。加鎖是保證業務正確的。完全可以單線程發送或者讀取請求,在發送前或者讀取後使用多線程提高系統吞吐量。

如果一定要在syscall一級考慮,UDP無需加鎖的前提感覺在於數據包的大小不能大於系統限制,系統保證返回整個包,這樣就應該可以保證原子性。而TCP沒有這樣的保證,所以需要加鎖。關於這個行為,僅僅是直覺猜測,未測試。


TCP是面向位元組流的協議,對上層應用層send的數據,為有效的發到對端,使用了優化方法(Nagle演算法),將多次間隔較小且數據量小的數據,合併成一個大的數據塊發送,所以,對於你這種有N多線程發送、數據傳輸頻繁的場景,tcp有很大概率發生粘包,會導致接收端數據協議解析發生錯誤,所以,一定需要互斥機制;
Udp是面向數據報的協議,不會使用合併優化演算法,UDP嚴格保護消息邊界,一次發送就是一個完整的用戶數據報,接收端接收數據也好做協議解析,所以,可以不加鎖。


這裡應該強調的是原子性而不是線程安全。Linux 下套接字的寫操作本身是線程安全的,這意味著多個線程同時去寫某個套接字不會造成其內核數據結構的毀壞。內核只對某些類型的套接字或文件描述符做出了原子性保證。

UDP 傳輸的數據是以數據包為單位的,從語義上來說單個數據包應該作為一個整體被發送出去。最終還得以文檔為準。

If the message is too long to pass atomically through the underlying protocol, the error EMSGSIZE is returned, and the message is not transmitted. --sendto(2)

By default, Linux UDP does path MTU (Maximum Transmission Unit) discovery. This means the kernel will keep track of the MTU to a specific target IP address and return EMSGSIZE when a UDP packet write exceeds it. When this happens, the application should decrease the packet size. Path MTU discovery can be also turned off using the IP_MTU_DISCOVER socket option or the /proc/sys/net/ipv4/ip_no_pmtu_disc file; see ip(7) for details. When turned off, UDP will fragment outgoing UDP packets that exceed the interface MTU. However, disabling it is not recommended for performance and reliability reasons. --udp(7)

有些只走內核的流套接字也可以保證一定程度的原子性的。例如 pipe(2):

POSIX.1-2001 says that write(2)s of less than PIPE_BUF bytes must be atomic: the output data is written to the pipe as a contiguous sequence. Writes of more than PIPE_BUF bytes may be nonatomic: the kernel may interleave the data with data written by other processes. POSIX.1-2001 requires PIPE_BUF to be at least 512 bytes. (On Linux, PIPE_BUF is 4096 bytes.) The precise semantics depend on whether the file descriptor is nonblocking (O_NONBLOCK), whether there are multiple writers to the pipe, and on n, the number of bytes to be written:
O_NONBLOCK disabled, n &<= PIPE_BUF
All n bytes are written atomically; write(2) may block if there is not room for n bytes to be written immediately
O_NONBLOCK enabled, n &<= PIPE_BUF
If there is room to write n bytes to the pipe, then write(2) succeeds immediately, writing all n bytes; otherwise write(2)fails, with errno set to EAGAIN.
O_NONBLOCK disabled, n &> PIPE_BUF
The write is nonatomic: the data given to write(2) may be interleaved with write(2)s by other process; the write(2)blocks until n bytes have been written.
O_NONBLOCK enabled, n &> PIPE_BUF
If the pipe is full, then write(2) fails, with errno set to EAGAIN. Otherwise, from 1 to n bytes may be written (i.e., a "partial write" may occur; the caller should check the return value from write(2) to see how many bytes were actually written), and these bytes may be interleaved with writes by other processes.

--pipe(7)


不用加鎖。sendto作為一個系統調用,原子性還是有保障的。


用個隊列吧,所有需要往UDP發送數據的,都推入隊列,然後用一個線程監測隊列,隊列中有數據就發。
推入隊列時要加鎖,包括stl中的隊列都不是線程安全的


謝邀。udp的sendto同樣有發送緩存,在這一點上tcp和udp是沒有區別的。所以最好還是加鎖,以防止多個線程同時對緩存區域的寫操作。


推薦閱讀:

Linux 開發,使用多線程還是用 IO 復用 select/epoll?
如何修改shared_ptr智能指針,讓他支持多線程?
開發多線程的程序應該注意哪些問題?

TAG:UDP | Socket | 多線程 |