高級IO——非阻塞IO


讀某些文件時,如果文件沒有數據的話,往往會導致讀操作阻塞(休眠)。比如

①讀鼠標、鍵盤等字符設備文件

讀鍵盤阻塞

 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 }
View Code

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 }
View Code

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 }
View Code

當制定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 }
View Code

補設

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

 代碼演示

 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 }
View Code

實現同時“讀鼠標”和“讀鍵盤”

由於一般情況下,“讀鼠標”和“讀鍵盤”都是阻塞的,為了不要讓“讀鼠標”和“讀鍵盤”因為阻塞而相互干擾,可以采取如下辦法來讀。

①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 }
View Code

②創建次線程,主線程和次線程兩線任務

主線程:讀鍵盤

次線程:讀鼠標

這才是我們經常實現的方式。代碼演示:

 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 }
View Code

③將鼠標和鍵盤設置為“非阻塞”,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 }
View Code

問題:把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 }
View Code

不會阻塞。為什么?scanf()是調用read(0, ...)來實現的,scanf的阻塞是因為調用read阻塞讀0描述符導致的,既然已經將0改為了非阻塞,read讀0就不再阻塞,自然scanf也就不再阻塞。所以如果你不想scanf阻塞的話,就可以將0描述符設置為非阻塞。當然我這個話只能針對Linux,因為fcntl函數是Linux的系統函數,不是標准C庫函數,這個函數在windows那邊不一定支持,在windows等系統下,如何設置非阻塞,他們的系統API可能不一樣。不過對於c的標准IO函數scanf來說,都是希望阻塞讀的,基本不會遇到需要將scanf改為非阻塞的情況。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM