2017-08-11 156 views
8

我想知道套接字在阻塞和非阻塞動作上的行爲。當套接字阻塞模式更改時,線程在套接字上阻塞會發生什麼?這是場景; 線程1(T1)創建UDP套接字和C套接字阻塞調用

fd = socket(AF_INET , SOCK_DGRAM, 0); 

T1等待(睡眠),用於接收

recv(fd, buf , sizeof(buf) , 0); 

和線程2(T2)改變插座模式到非阻塞插座接收任何數據之前

fcntl(fd, F_SETFL, O_NONBLOCK); 

T1會發生什麼情況?是否因爲套接字不再被阻塞而發出信號/喚醒信號?

+0

我無法在文檔中找到它,因此我會說「行爲未定義」;) –

+0

我期望它不被支持,因爲套接字api是在多線程存在之前很久才建立的。 – alain

+2

從多線程訪問套接字是一個壞主意,因爲它會引起競爭條件 - 例如,考慮T1的recv()調用返回錯誤(-1)的情況,並且T1響應我在套接字上調用close() ,就在T2調用fcntl()之前。在最好的情況下,T2的fcntl()調用將失敗(使用EBADF),因爲套接字句柄現在無效;在最壞的情況下,一些其他(不相關的)線程在過渡期間調用了socket(),並且被賦予了相同的fd值,現在T2已經做出了一個非阻塞的隨機套接字。 (這是一個非常難以追蹤的錯誤!) –

回答

8

該行爲字面上未指定:fcntl不需要解鎖任何線程。

Linux just sets the flag in the file descriptionstruct file and returns without unblocking any blocked threads.

線程已阻塞在recv可以被調度運行,僅當:

  • 讀取數據變得可用;
  • 或檢測到文件描述符的錯誤條件(FIN/RST,套接字讀取超時,TCP保持活動失敗,文件描述符爲close d由另一個線程);
  • 或收到信號且信號處理不包括SA_RESTART;
  • 或者它是pthread_cancel

您試圖更改另一個線程的文件描述符標誌的事實表明您的設計需要審查。理想情況下,線程不能共享任何數據,也不能在對方的狀態下戳,而應該使用消息傳遞來相互通信。

+0

是的 - 我希望開發人員在停止了可以理解的解決方法後,停止嘗試奇怪的事情:( –

+0

@MartinJames只要告訴他們就行了吧 –

+0

Ehm。'SA_RESTART'你說?我的答案是有趣的轉折 – Art

0

暢通無阻線程(在阻塞模式下沒有因爲讀取而阻塞)將表現得好像它始終是非阻塞模式一樣。

recv(2) manual page

如果可在插座中沒有消息,並且如果該插座是非阻塞,該值返回-1和外部 變量errno設置爲EAGAIN或EWOULDBLOCK。


阻止線程變爲無阻塞之前(在讀取在阻塞模式下時阻止)。 正如@Maxim所指出的那樣,共享未喚醒線程的函數的代碼,被阻塞的線程只會在寫入完成後(數據可用)喚醒。

1

我覺得POSIX specification是這個相當清楚的:

如果沒有消息可在插座和O_NONBLOCK上沒有套接字的文件描述符設置,recv()應阻塞,直到消息到達。如果在套接字上沒有可用的消息並且在套接字的文件描述符上設置了O_NONBLOCK,則recv()將失敗並將errno設置爲[EAGAIN][EWOULDBLOCK]

你叫recvO_NONBLOCK尚未設置,它應該阻塞,直到消息到達(直到模式改變)。

3

你讓我好奇。首先,顯而易見的是,由於沒有標準指定套接字應該喚醒,所以它不會被喚醒,因爲實現它會是一件非常痛苦的事情(因爲非阻塞標誌位於與套接字阻塞)。所以我們可以很自信地說,套接字在我們收到一個包之前不會醒來。還是會呢?

#include <pthread.h> 
#include <signal.h> 
#include <stdio.h> 
#include <err.h> 
#include <sys/types.h> 
#include <netinet/in.h> 
#include <string.h> 
#include <time.h> 
#include <fcntl.h> 

int sock; 

static void 
sighand(int s) 
{ 
     write(1, "sig\n", 4); 
} 

static void * 
rcv(void *v) 
{ 
     struct sigaction sa; 

     memset(&sa, 0, sizeof(sa)); 
     sa.sa_handler = sighand; 
     sa.sa_flags = SA_RESTART; 
     if (sigaction(SIGUSR1, &sa, NULL) == -1) 
       err(1, "sigaction"); 

     char buf[64]; 
     ssize_t sz = recv(sock, buf, sizeof(buf), 0); 
     printf("recv %d\n", (int)sz); 
     return NULL; 
} 

pthread_t t1, t2; 

static void * 
beeper(void *v) 
{ 
     for (;;) { 
       nanosleep(&((struct timespec){.tv_sec = 1}), NULL); 
       if (pthread_kill(t1, SIGUSR1)) 
         errx(1, "pthread_kill"); 
       printf("beep\n"); 
     } 
} 

int 
main(int argc, char **argv) 
{ 
     if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) == -1) 
       err(1, "socket"); 

     struct sockaddr_in sin; 
     memset(&sin, 0, sizeof(sin)); 
     sin.sin_family = AF_INET; 
     sin.sin_port = htons(4711); 
     sin.sin_addr.s_addr = htonl(INADDR_ANY); 
     if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) == -1) 
       err(1, "bind"); 

     if (pthread_create(&t1, NULL, rcv, NULL)) 
       errx(1, "pthread_create"); 

     if (pthread_create(&t2, NULL, beeper, NULL)) 
       errx(1, "pthread_create"); 

     /* pretend that this is correct synchronization. */ 
     nanosleep(&((struct timespec){.tv_sec = 3}), NULL); 

     printf("setting non-block\n"); 
     if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) 
       err(1, "fcntl"); 
     printf("set\n"); 

     nanosleep(&((struct timespec){.tv_sec = 3}), NULL); 

     return 0; 
} 

上面的代碼(對不起,不能縮短)。在recv中阻塞線程,等待一會兒,然後在文件描述符上設置非阻塞。如預期的那樣,沒有任何反應但後來我加了一個轉折。接收線程有一個信號處理程序,每秒喚醒一次,並使用SA_RESTART。當然,因爲我們有SA_RESTARTrecv不應該醒來。

在OSX上,如果我們認爲任何行爲都是正確的,這將會「行爲不端」。我很確定這在所有的BSD上表現都是一樣的。在Linux上也是如此。事實上,通常我會執行SA_RESTART的方式,我非常確定這將會在任何地方「不正常」。

好笑。這當然不意味着上面的代碼對任何事情都有用,但這是一個有趣的好奇心。要回答你的問題,這是不明確的,但在大多數情況下,它不會醒來,除非它會。請不要做這樣的怪事。

+0

在Linux上,'recv'不會在信號上返回並被正確重新啓動。如果它的行爲不像'man signal 7'中所宣傳的那樣,那將是一個嚴重的問題。 –

+0

有趣的觀察+1。 –

+0

@感謝您的詳細解釋 – Qxtrml