讀某些文件時,如果文件沒有數據的話,往往會導致讀操作阻塞(休眠)。比如
①讀鼠標、鍵盤等字符設備文件
讀鍵盤阻塞

1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 5 int main(void) 6 { 7 char buf[100]={0}; 8 int ret=0; 9 while(1) 10 { 11 printf("~~~~~~~~~~~~~\n"); 12 ret=read(0,buf,sizeof(buf)); 13 if(ret > 0) printf("%s\n",buf); 14 printf("~~~~~~~~~~~~~\n"); 15 } 16 return 0; 17 }
read第一次調用時會等待stdin輸入,沒有輸入的話會一直阻塞。取地輸入后便繼續向下執行,不會一直卡在read調用處
讀鼠標阻塞

1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<errno.h> 5 #include<string.h> 6 #include <sys/types.h> 7 #include <sys/stat.h> 8 #include <fcntl.h> 9 10 11 void print_err(char *str,int line,int err_no) 12 { 13 printf("%d,%s: %s\n",line,str,strerror(err_no)); 14 exit(-1); 15 } 16 int main(void) 17 { 18 int cord=0; //鼠標坐標 19 int ret=0; 20 int mousefd = -1; 21 22 mousefd=open("/dev/input/mouse0",O_RDONLY); 23 if(-1 == mousefd) print_err("open mouse0 fail",__LINE_,errno); 24 25 while(1) 26 { 27 printf("~~~~~~~~~~~~~\n"); 28 ret=read(mousefd,&cord,sizeof(cord)); 29 if(ret > 0) printf("%d\n",cord); 30 printf("~~~~~~~~~~~~~\n"); 31 } 32 return 0; 33 }
read這個函數,不管是成功返回 還是 出錯返回,只要返回就接着往下執行
②讀管道文件(有名無名)
讀普通文件會阻塞嗎?
讀普通文件時,如果讀到了數據就成功返回,如果沒有讀到數據返回0(注意是返回0,不是出錯返回),總之不會阻塞。
總之,不管有沒有從普通文件讀到數據,讀普通文件時都不會阻塞。
寫文件時會阻塞嗎?
在寫某些文件時,當文件不能立即接收寫入的數據時,也可能會導致寫操作阻塞,一直阻塞到寫成功為止。一把來說,寫文件不會阻塞,因此我們不考慮寫文件阻塞的情況,我們這里只講讀文件阻塞的情況。
阻塞是好還是壞
實際上讀文件因為沒有數據而阻塞,其實是好事,因為這樣子就進入休眠狀態,休眠時就不會占用CPU,節省了cpu的資源。
我能不能將阻塞的讀修改為非阻塞的讀呢?
答:可以,非阻塞讀的意思就是說,如果有數據就成功讀到,如果沒有讀到數據就出錯返回,而不是阻塞。
疑問:既然阻塞讀很好,為什么提供非阻塞讀呢?
答:盡管我們很少非阻塞的讀,但是有些時候還真需要非阻塞的讀,因此OS還是為我們提供了非阻塞操作方式。
如何實現非阻塞讀
打開文件時指定O_NONBLOCK狀態標志
以讀鼠標為例

1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<errno.h> 5 #include<string.h> 6 #include <sys/types.h> 7 #include <sys/stat.h> 8 #include <fcntl.h> 9 10 11 void print_err(char *str,int line,int err_no) 12 { 13 printf("%d,%s: %s\n",line,str,strerror(err_no)); 14 exit(-1); 15 } 16 int main(void) 17 { 18 int cord=0; //鼠標坐標 19 int ret=0; 20 int mousefd = -1; 21 22 mousefd=open("/dev/input/mouse0",O_RDONLY|O_NONBLOCK); 23 if(-1 == mousefd && errno!=EAGAIN) print_err("open mouse0 fail",__LINE_,errno); 24 25 while(1) 26 { 27 ret=read(mousefd,&cord,sizeof(cord)); 28 if(ret > 0) printf("%d\n",cord); 29 } 30 return 0; 31 }
當制定O_NONBLOCK讀鼠標的時候,沒有數據的話,ret回返回-1,並且errno被設置為EAGAIN
在講IPC有名管道時,如果不希望阻塞的話,就可以在open打開“有名管道”時,指定O_NONBLOCK,然后讀有名管道無數據時就不會阻塞。
通過fcntl函數指定O_NONBLOCK來實現
什么情況下會使用fcntl來實現,
情況1:當文件已經被open打開了,但是open是並沒有指定你要的文件狀態標志,而你又無法修改open的參數,此時就是可以使用fcntl來重設或者補設。
情況2:沒辦法在open指定,你手里只有一個文件描述符fd,此時就使用fcntl來重設或者補設。比如無名管道,無名管道連名字都沒有,沒辦法使用open函數,無名管道是使用pipe函數來返回文件描述符的,如果過你想非阻塞的讀無名管道的話,是沒有辦法通過open來指定O_NONBLOCK的,此時就需要使用fcntl來重設或者補設。
當然我們使用fcntl不僅僅只能重設或者補設O_NONBLOCK,也可以重設或者補設O_TRUNC/O_APPEND等任何你需要的“文件狀態”標志。
例子:將0設置為O_NONBLOCK。設置有兩種方式:
重設
fcntl(0, F_SETFL, O_RDONLY|O_NONBLOCK);
代碼演示

1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<errno.h> 5 #include<string.h> 6 #include <sys/types.h> 7 #include <sys/stat.h> 8 #include <fcntl.h> 9 10 void print_err(char *str,int line,int err_no) 11 { 12 printf("%d,%s: %s\n",line,str,strerror(err_no)); 13 exit(-1); 14 } 15 16 int main(void) 17 { 18 char buf[100]={0}; 19 int ret=0; 20 fcntl(0, F_SETFL, O_RDONLY|O_NONBLOCK); 21 while(1) 22 { 23 ret=read(0,buf,sizeof(buf)); 24 if(ret > 0) printf("%s\n",buf); 25 } 26 return 0; 27 }
補設

1 flag = fcntl(0, F_GETFL); //獲取原有文件狀態標志 2 flag = flag | O_NONBLOCK; //通過|操作,在已有的標志上增設O_NONBLOCK 3 fcntl(0, F_SETFL, flag); //將修改后的“文件狀態標志”設置回去
代碼演示

1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<errno.h> 5 #include<string.h> 6 #include <sys/types.h> 7 #include <sys/stat.h> 8 #include <fcntl.h> 9 10 void print_err(char *str,int line,int err_no) 11 { 12 printf("%d,%s: %s\n",line,str,strerror(err_no)); 13 exit(-1); 14 } 15 16 int main(void) 17 { 18 char buf[100]={0}; 19 int ret=0; 20 int flag=0; 21 flag = fcntl(0, F_GETFL); 22 flag = flag | O_NONBLOCK; 23 fcntl(0, F_SETFL, flag); 24 while(1) 25 { 26 ret=read(0,buf,sizeof(buf)); 27 if(ret > 0) printf("%s\n",buf); 28 } 29 return 0; 30 }
實現同時“讀鼠標”和“讀鍵盤”
由於一般情況下,“讀鼠標”和“讀鍵盤”都是阻塞的,為了不要讓“讀鼠標”和“讀鍵盤”因為阻塞而相互干擾,可以采取如下辦法來讀。
①fork子進程,然后父子進程兩線任務
父進程:讀鍵盤
子進程:讀鼠標
這樣可以,但是並不主張這么做。多線任務使用進程來做開銷太大
代碼演示

1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<errno.h> 5 #include<string.h> 6 #include <sys/types.h> 7 #include <sys/stat.h> 8 #include <fcntl.h> 9 10 void print_err(char *str,int line,int err_no) 11 { 12 printf("%d,%s: %s\n",line,str,strerror(err_no)); 13 exit(-1); 14 } 15 16 int main(void) 17 { 18 int cord=0; //鼠標坐標 19 int mousefd = -1; 20 char buf[100]={0};//讀鍵盤 21 int ret=0; 22 23 mousefd=open("/dev/input/mouse0",O_RDONLY); 24 if(-1 == mousefd) print_err("open mouse0 fail",__LINE__,errno); 25 26 fcntl(0, F_SETFL, O_RDONLY|O_NONBLOCK); 27 fcntl(mousefd, F_SETFL, O_RDONLY|O_NONBLOCK); 28 29 ret = fork(); 30 if(ret > 0)//父進程 31 { 32 while(1) 33 { 34 ret = 0; 35 ret=read(0,buf,sizeof(buf)); 36 if(ret > 0) printf("%s\n",buf); 37 } 38 } 39 else if(ret == 0)//子進程 40 { 41 while(1) 42 { 43 ret=read(mousefd,&cord,sizeof(cord)); 44 if(ret > 0) printf("%d\n",cord); 45 } 46 } 47 return 0; 48 }
②創建次線程,主線程和次線程兩線任務
主線程:讀鍵盤
次線程:讀鼠標
這才是我們經常實現的方式。代碼演示:

1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <strings.h> 5 #include <signal.h> 6 #include <errno.h> 7 #include <sys/types.h> 8 #include <sys/stat.h> 9 #include <fcntl.h> 10 #include <pthread.h> 11 12 void print_err(char *str,int line,int err_no) 13 { 14 printf("%d,%s: %s\n",line,str,strerror(err_no)); 15 exit(-1); 16 } 17 18 void *pth_fun(void *pth_arg) 19 { 20 int ret=0; 21 int cord=0; //鼠標坐標 22 int mousefd = *(int *)pth_arg; 23 while(1) 24 { 25 ret=read(mousefd,&cord,sizeof(cord)); 26 if(ret > 0) printf("%d\n",cord); 27 } 28 29 return NULL; 30 } 31 32 int main(void) 33 { 34 int ret = 0; 35 pthread_t tid; 36 char buf[100]={0};//讀鍵盤 37 int mousefd = -1; 38 39 mousefd=open("/dev/input/mouse0",O_RDONLY); 40 if(-1 == mousefd) print_err("open mouse0 fail",__LINE__,errno); 41 42 ret = pthread_create(&tid, NULL, pth_fun, (void *)&mousefd); 43 if(0 != ret) print_err("pthread_create fail",__LINE__,ret); 44 45 while(1) 46 { 47 ret = 0; 48 ret=read(0,buf,sizeof(buf)); 49 if(ret > 0) printf("%s\n",buf); 50 } 51 52 return 0; 53 }
③將鼠標和鍵盤設置為“非阻塞”,while輪詢的讀。
代碼演示

1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<errno.h> 5 #include<string.h> 6 #include <sys/types.h> 7 #include <sys/stat.h> 8 #include <fcntl.h> 9 10 void print_err(char *str,int line,int err_no) 11 { 12 printf("%d,%s: %s\n",line,str,strerror(err_no)); 13 exit(-1); 14 } 15 16 int main(void) 17 { 18 char buf[100]={0}; 19 int ret=0; 20 int cord=0; //鼠標坐標 21 int mousefd = -1; 22 23 mousefd=open("/dev/input/mouse0",O_RDONLY); 24 if(-1 == mousefd) print_err("open mouse0 fail",__LINE__,errno); 25 26 fcntl(0, F_SETFL, O_RDONLY|O_NONBLOCK); 27 fcntl(mousefd, F_SETFL, O_RDONLY|O_NONBLOCK); 28 while(1) 29 { 30 ret=read(0,buf,sizeof(buf)); 31 if(ret > 0) printf("%s\n",buf); 32 ret=0; 33 ret=read(mousefd,&cord,sizeof(cord)); 34 if(ret > 0) printf("%d\n",cord); 35 } 36 return 0; 37 }
問題:把0設置為非阻塞,請問使用scanf從鍵盤讀數據時阻塞的嗎?
代碼演示:

1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<errno.h> 5 #include<string.h> 6 #include <sys/types.h> 7 #include <sys/stat.h> 8 #include <fcntl.h> 9 10 void print_err(char *str,int line,int err_no) 11 { 12 printf("%d,%s: %s\n",line,str,strerror(err_no)); 13 exit(-1); 14 } 15 16 int main(void) 17 { 18 char buf[100]={0}; 19 int ret=0; 20 int cord=0; //鼠標坐標 21 int mousefd = -1; 22 23 mousefd=open("/dev/input/mouse0",O_RDONLY); 24 if(-1 == mousefd) print_err("open mouse0 fail",__LINE__,errno); 25 26 fcntl(0, F_SETFL, O_RDONLY|O_NONBLOCK); 27 fcntl(mousefd, F_SETFL, O_RDONLY|O_NONBLOCK); 28 while(1) 29 { 30 ret=scanf("%s",buf); 31 if(ret > 0) printf("%s\n",buf); 32 printf("~~~~~~~~~~~~~~\n"); 33 } 34 return 0; 35 }
不會阻塞。為什么?scanf()是調用read(0, ...)來實現的,scanf的阻塞是因為調用read阻塞讀0描述符導致的,既然已經將0改為了非阻塞,read讀0就不再阻塞,自然scanf也就不再阻塞。所以如果你不想scanf阻塞的話,就可以將0描述符設置為非阻塞。當然我這個話只能針對Linux,因為fcntl函數是Linux的系統函數,不是標准C庫函數,這個函數在windows那邊不一定支持,在windows等系統下,如何設置非阻塞,他們的系統API可能不一樣。不過對於c的標准IO函數scanf來說,都是希望阻塞讀的,基本不會遇到需要將scanf改為非阻塞的情況。