IO進程——線程、IO模型

一、線程Thread

1、引入

1.1 概念

相當于是一個輕量級的進程,為了提高系統的性能引入線程,在同一進程中可以創建多個線程,共享進程資源

1.2 進程和線程比較

相同點:都為操作系統提供了并發執行的能力

不同點:

調度和資源:線程是系統調度的最小單位; 進程是資源分配的最小單位。

地址空間方面:一個進程創建的多個線程共享該進程資源;進程的地址空間相互獨立

通信方面:線程通信相對簡單。只需要通過全局變量就可以,但是需要考慮臨界資源問題;進程通信比較復雜,需要借助進程間通信機制(3-4g的內核空間)

安全性方面:線程安全性差一些,當進程結束時會導致其中所有線程退出,進程相對安全

程序什么時候該使用線程?什么時候用進程?

對資源的管理和保護要求高,不限制開銷和效率時,使用多進程。

要求效率高、速度快的高并發環境時,需要頻繁創建、銷毀或切換時,資源的保護管理要求不是很高時,使用多線程。

1.3 線程資源(了解)

共享的資源:可執行的指令、靜態數據、進程中打開的文件描述符、信號處理函數、當前工作目錄、用戶ID、用戶組ID

私有的資源:線程ID (TID)、PC(程序計數器)和相關寄存器、堆棧(局部變量, 返回地址)、錯誤號 (errno)、信號掩碼和優先級、執行狀態和屬性

2、函數接口

2.1 創建線程

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,

void *(*start_routine) (void *), void *arg);

功能:創建線程

參數:

thread ===> 線程標識

attr ===> 線程屬性, NULL:代表設置默認屬性

start_routine ===> 函數名:代表線程函數(自己寫的)

arg ===> 用來給前面函數傳參

返回值:成功:0 失敗:錯誤碼

編譯的時候需要加 -pthread 鏈接動態庫

#include<stdio.h>
#include <pthread.h>
void *handler_thread(void *arg)
{printf("in handler_thread\n");while (1);  // 讓從線程不要退出return NULL;
}
int main(int argc, char const *argv[])  // 主線程
{pthread_t tid;if (pthread_create(&tid, NULL, handler_thread, NULL) != 0){perror("pthread error");return -1;}printf("in main\n");while(1);   // 不要讓進程結束,否則所有線程都結束了return 0;
}

補充:也可以給從進程傳參

#include<stdio.h>
#include <pthread.h>
void *handler_thread(void *arg)
{printf("in handler_thread: %d\n", *(int *)arg);while (1);  // 讓從線程不要退出return NULL;
}
int main(int argc, char const *argv[])  // 主線程
{pthread_t tid;int a = 100;    // 定義新的變量傳輸到從線程if (pthread_create(&tid, NULL, handler_thread, &a) != 0){perror("pthread error");return -1;}printf("in main\n");while(1);   // 不要讓進程結束,否則所有線程都結束了return 0;
}

2.2 退出線程

#include <pthread.h>

void pthread_exit(void *retval);

功能:用于退出線程的執行

參數:value_ptr ===> 線程退出時返回的值

#include<stdio.h>
#include <pthread.h>
void *handler_thread(void *arg)
{printf("in handler_thread\n");pthread_exit(NULL); // 讓線程退出while (1);  // 讓從線程不要退出return NULL;
}
int main(int argc, char const *argv[])  // 主線程
{pthread_t tid;if (pthread_create(&tid, NULL, handler_thread, NULL) != 0){perror("pthread error");return -1;}printf("in main\n");while(1);   // 不要讓進程結束,否則所有線程都結束了return 0;
}

2.3 回收線程資源

2.3.1 回收態

#include <pthread.h>

int pthread_join(pthread_t thread, void **value_ptr);

功能:用于等待一個指定的線程結束,阻塞函數(回收態)

參數:

????????thread ===> 創建的線程對象,線程ID

????????value_ptr ===> 指針*value_ptr 一般為NULL

返回值:成功:0 ????????失敗:errno

#include<stdio.h>
#include <pthread.h>
#include<unistd.h>
void *handler_thread(void *arg)
{printf("in handler_thread: %d\n", *(int *)arg);sleep(2);pthread_exit(NULL); // 讓線程退出while (1);  // 讓從線程不要退出return NULL;
}
int main(int argc, char const *argv[])  // 主線程
{pthread_t tid;int a = 100;    // 定義新的變量傳輸到從線程if (pthread_create(&tid, NULL, handler_thread, &a) != 0){perror("pthread error");return -1;}pthread_join(tid, NULL);    // 阻塞等待指定線程退出回收其資源printf("in main\n");while(1);   // 不要讓進程結束,否則所有線程都結束了return 0;
}

2.3.2 分離態

#include <pthread.h>

int pthread_detach(pthread_t thread);

功能:讓線程結束時自動回收線程資源,讓線程和主線程分離,非阻塞函數(分離態)

參數:thread ===> 線程ID

非阻塞式的,例如主線程分離(detach)了線程T2,那么主線程不會阻塞在pthread_detach(),pthread_detach()會直接返回,線程T2終止后會被操作系統自動回收資源

#include<stdio.h>
#include <pthread.h>
#include<unistd.h>
void *handler_thread(void *arg)
{printf("in handler_thread: %d\n", *(int *)arg);sleep(2);pthread_exit(NULL); // 讓線程退出while (1);  // 讓從線程不要退出return NULL;
}
int main(int argc, char const *argv[])  // 主線程
{pthread_t tid;int a = 100;    // 定義新的變量傳輸到從線程if (pthread_create(&tid, NULL, handler_thread, &a) != 0){perror("pthread error");return -1;}pthread_detach(tid);    // 不阻塞,讓指定線程退出時主動把資源還給系統printf("in main\n");while(1);   // 不要讓進程結束,否則所有線程都結束了return 0;
}

2.4 獲取程號

pthread_t pthread_self(void);

功能:獲取線程號

返回值:成功:調用此函數線程的ID

#include<stdio.h>
#include <pthread.h>
#include<unistd.h>
void *handler_thread(void *arg)
{printf("in handler_thread: %ld\n", pthread_self()); // 獲取進程號pthread_exit(NULL); // 讓線程退出while (1);  // 讓從線程不要退出return NULL;
}
int main(int argc, char const *argv[])  // 主線程
{pthread_t tid;if (pthread_create(&tid, NULL, handler_thread, NULL) != 0){perror("pthread error");return -1;}printf("in main\n");while(1);   // 不要讓進程結束,否則所有線程都結束了return 0;
}

3、練習

????????通過線程實現數據的交互,主線程循環從終端輸入,線程函數將數據循環輸出,當輸入quit結束程序。

#include<stdio.h>
#include <pthread.h>
#include<string.h>
char buf[32];
int flag = 0;   // 設置標志位判斷是否輸入輸出完成,0代表可以輸入,1代表可以輸出
void *handler_thread(void *arg)
{while (1)   // 從線程不斷輸出{if (flag == 1)  // 1才可以輸出{        if (!strcmp(buf, "quit"))break;printf("%s\n", buf);flag = 0;   // 輸入完置0代表可以輸入}}pthread_exit(NULL); // 讓從線程退出return NULL;
}
int main(int argc, char const *argv[])  // 主線程
{pthread_t tid;if (pthread_create(&tid, NULL, handler_thread, NULL) != 0){perror("pthread error");return -1;}while (1)   // 主線程不斷輸入{if (flag == 0)  // 0才可以輸入{scanf("%s", buf);flag = 1;   // 輸入完置1代表可以輸出if (!strcmp(buf, "quit"))break;   }     }pthread_detach(tid);    // 不阻塞,讓指定線程退出時主動把資源還給系統return 0;
}

4、同步

4.1概念

????????同步(synchronization)指的是多個任務(線程)按照約定的順序相互配合完成一件事情 (異步:異步則反之,并非要按照順序完成事件)

4.2 同步機制

通過信號量實現線程間同步

信號量:通過信號量實現同步操作;由信號量來決定線程是繼續運行還是阻塞等待

信號量代表某一類資源,其值表示系統中該資源的數量

信號量的值>0表示有資源可以用, 可以申請到資源

信號量的值<=0表示沒有資源可以用, 無法申請到資源, 阻塞

信號量還是一個受保護的變量,只能通過三種操作來訪問:初始化、P操作(申請資源)、V操作(釋放資源)

sem_init: 信號量初始化

sem_wait: 申請資源P操作, 如果沒有資源可以用阻塞-1

sem_post: 釋放資源V操作, 非阻塞 +1

4.3 函數接口(信號量)

4.3.1 初始化信號量

#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);

功能:初始化信號量

參數:sem:初始化的信號量對象

pshared:信號量共享的范圍(0: 線程間使用 非01進程間使用)

value:信號量初值

返回值:成功 0 ????????失敗 -1

4.3.2 申請資源

#include <semaphore.h>

int sem_wait(sem_t *sem);

功能:申請資源 P操作

參數:sem:信號量對象

返回值:成功 0 失敗 -1

注:此函數執行過程,當信號量的值大于0時,表示有資源可以用,則繼續執行,同時對信號量減1;當信號量的值等于0時,表示沒有資源可以使用,函數阻塞

4.3.3 釋放資源

#include <semaphore.h>

int sem_post(sem_t *sem);

功能:釋放資源 V操作

參數:sem:信號量對象

返回值:成功 0 失敗 -1

注:釋放一次信號量的值加1,函數不阻塞

4.4 練習

????????通過線程實現數據的交互,主線程循環從終端輸入,線程函數將數據循環輸出,當輸入quit結束程序。

雙信號量:

#include<stdio.h>
#include <semaphore.h>
#include <pthread.h>
#include<string.h>
char buf[32];
sem_t sem1, sem2;
void *handler_thread(void *arg)
{while (1)   // 從線程不斷輸出{sem_wait(&sem1); // 申請資源if (!strcmp(buf, "quit"))break;printf("%s\n", buf);sem_post(&sem2); // 釋放資源}pthread_exit(NULL); // 讓從線程退出return NULL;
}
int main(int argc, char const *argv[])  // 主線程
{pthread_t tid;  // 創線程// 初始化信號量if (sem_init(&sem1, 0, 0) != 0){perror("sem init error");return -1;}if (sem_init(&sem2, 0, 1) != 0){perror("sem init error");return -1;}   if (pthread_create(&tid, NULL, handler_thread, NULL) != 0){perror("pthread error");return -1;}while (1)   // 主進程不斷輸入{sem_wait(&sem2); // 申請資源scanf("%s", buf);if (!strcmp(buf, "quit"))break;sem_post(&sem1); // 釋放資源}pthread_detach(tid);    // 不阻塞,讓指定線程退出時主動把資源還給系統return 0;
}

單信號量:

#include<stdio.h>
#include <semaphore.h>
#include <pthread.h>
#include<string.h>
char buf[32];
sem_t sem;
void *handler_thread(void *arg)
{while (1)   // 從線程不斷輸出{     sem_wait(&sem); // 申請資源if (!strcmp(buf, "quit"))break;printf("%s\n", buf);}pthread_exit(NULL); // 讓從線程退出return NULL;
}
int main(int argc, char const *argv[])  // 主線程
{pthread_t tid;  // 創線程// 初始化信號量if (sem_init(&sem, 0, 0) != 0){perror("sem init error");return -1;}   if (pthread_create(&tid, NULL, handler_thread, NULL) != 0){perror("pthread error");return -1;}while (1)   // 主進程不斷輸入{scanf("%s", buf);if (!strcmp(buf, "quit"))break;sem_post(&sem); // 釋放資源}pthread_detach(tid);    // 不阻塞,讓指定線程退出時主動把資源還給系統return 0;
}

5、互斥

5.1 概念

多個線程在訪問臨界資源時,同一時間只能一個線程訪問。

臨界資源:一次僅允許一個線程所使用的資源

臨界區:一個訪問共享資源的程序片段

互斥鎖(mutex): 通過互斥鎖可以實現互斥機制,主要用來保護臨界資源,每個臨界資源都由一個互斥鎖來保護,線程必須先獲得互斥鎖才能訪問臨界資源,訪問完資源后釋放該鎖。如果無法獲得鎖,線程會阻塞直到獲得鎖為止。

5.2 函數接口

5.2.1 初始化互斥鎖

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr)

功能:初始化互斥鎖

參數:mutex:互斥鎖

attr: 互斥鎖屬性 // NULL表示缺省屬性

返回值:成功 0 失敗 -1

5.2.2 申請互斥鎖

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex)

功能:申請互斥鎖

參數:mutex:互斥鎖

返回值:成功 0

失敗 -1

注:和pthread_mutex_trylock區別:pthread_mutex_lock是阻塞的;pthread_mutex_trylock不阻塞,如果申請不到鎖會立刻返回

5.2.3 釋放互斥鎖

#include <pthread.h>

int pthread_mutex_unlock(pthread_mutex_t *mutex)

功能:釋放互斥鎖

參數:mutex:互斥鎖

返回值:成功 0

失敗 -1

5.2.4 銷毀互斥鎖

#include <pthread.h>

int pthread_mutex_destroy(pthread_mutex_t *mutex)

功能:銷毀互斥鎖

參數:mutex:互斥鎖

5.3 練習

通過互斥鎖實現打印倒置數組功能

#include <pthread.h>
#include<stdio.h>
#include <unistd.h>     /*sleep頭文件*/
#define N 10
int a[N] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};  // 定義數組
pthread_mutex_t lock;   // 定義一把鎖
/*==========從線程:倒置函數==========*/
void *swap(void *arg)
{int temp = 0;   /*定義中間變量用于交換*/while (1){pthread_mutex_lock(&lock);  /*上鎖*/for (int i = 0; i < N / 2; i++){temp = a[i];a[i] = a[N - 1 - i];a[N - 1 - i] = temp;}pthread_mutex_unlock(&lock);    /*解鎖*/}return NULL;
}
/*==========從線程:打印函數==========*/
void *print(void *arg)
{while (1){pthread_mutex_lock(&lock);  /*上鎖*/for (int i = 0; i < N; i++)printf("%d ", a[i]);putchar(10);pthread_mutex_unlock(&lock);  /*解鎖*/ sleep(1);   /*鎖里面減少耗時大的操作*/       }return NULL;
}
/*==========主線程==========*/
int main(int argc, char const *argv[])
{pthread_t tid1, tid2;// 1.初始化互斥鎖if(pthread_mutex_init(&lock, NULL) != 0){perror("pthread_mutex_init error");return -1;}// 2.創建線程/*==1>創建從線程 1 用于倒置數組==*/if (pthread_create(&tid1, NULL, swap, NULL) != 0){perror("pthread_create swap error");return -1;}/*==2>創建從線程 2 用于打印數組==*/if (pthread_create(&tid2, NULL, print, NULL) != 0){perror("pthread_create print error");return -1;}// 3.防止主線程結束,進行阻塞回收從線程資源pthread_join(tid1, NULL);pthread_join(tid2, NULL);   return 0;
}

5.4 死鎖

是指兩個或兩個以上的進程或線程在執行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。

●死鎖產生的四個必要條件

1、互斥使用,即當資源被一個線程使用(占有)時,別的線程不能使用

2、不可搶占,資源請求者不能強制從資源占有者手中奪取資源,資源只能由資源占有者主動釋放。

3、請求和保持,即當資源請求者在請求其他的資源的同時保持對原有資源的占有。

4、循環等待,即存在一個等待隊列:P1占有P2的資源,P2占有P3的資源,P3占有P1的資源。這樣就形成了一個等待環路。

注意:當上述四個條件都成立的時候,便形成死鎖。當然,死鎖的情況下如果打破上述任何一個條件,便可讓死鎖消失。

5.5 條件變量

5.5.1 概念

條件變量用于在線程之間傳遞信號,以便某些線程可以等待某些條件發生。當某些條件發生時,條件變量會發出信號,使等待該條件的線程可以恢復執行。

假設想先運行線程A,再運行線程B:

因為想要先運行A線程,所以需要先將B進程阻塞,故進程開始時先讓A線程睡一會,先去調度B線程,

5.5.2 函數接口

一般和互斥鎖搭配使用,實現同步機制:

pthread_cond_init(&cond,NULL); //初始化條件變量

使用前需要上鎖:

pthread_mutex_lock(&lock); //上鎖

一些邏輯:

ptread_cond_wait(&cond, &lock); //阻塞等待條件產生,沒有條件產生時阻塞,同時解鎖,當條件產生時結束阻塞,再次上鎖。

執行任務:

pthread_mutex_unlock(&lock); //解鎖

pthread_cond_signal(&cond); //產生條件,不阻塞

pthread_cond_destroy(&cond); //銷毀條件變量

注意: 必須保證讓pthread_cond_wait先執行,然后再pthread_cond_signal產生條件。

#include <pthread.h>
#include<stdio.h>
#include <unistd.h>     /*sleep頭文件*/
#define N 10
int a[N] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};  // 定義數組
pthread_mutex_t lock;   // 定義一把鎖
pthread_cond_t cond;    // 條件變量
/*==========從線程:倒置函數==========*/
void *swap(void *arg)
{int temp = 0;   /*定義中間變量用于交換*/while (1){pthread_mutex_lock(&lock);  /*上鎖*/// 等待條件產生pthread_cond_wait(&cond, &lock);for (int i = 0; i < N / 2; i++)     /*倒置數組*/{temp = a[i];a[i] = a[N - 1 - i];a[N - 1 - i] = temp;}pthread_mutex_unlock(&lock);    /*解鎖*/}return NULL;
}
/*==========從線程:打印函數==========*/
void *print(void *arg)
{while (1){sleep(1);   /*鎖里面減少耗時大的操作*/ pthread_mutex_lock(&lock);  /*上鎖*/for (int i = 0; i < N; i++) /*循環打印數組*/printf("%d ", a[i]);putchar(10);pthread_cond_signal(&cond); /*產生條件,不阻塞*/pthread_mutex_unlock(&lock);  /*解鎖*/       }return NULL;
}
/*==========主線程==========*/
int main(int argc, char const *argv[])
{pthread_t tid1, tid2;// 1.初始化互斥鎖if(pthread_mutex_init(&lock, NULL) != 0){perror("pthread_mutex_init error");return -1;}// 2.初始化條件變量if (pthread_cond_init(&cond, NULL) != 0){perror("cond init err");return - 1;}// 3.創建線程/*==1>創建從線程 1 用于倒置數組==*/if (pthread_create(&tid1, NULL, swap, NULL) != 0){perror("pthread_create swap error");return -1;}/*==2>創建從線程 2 用于打印數組==*/if (pthread_create(&tid2, NULL, print, NULL) != 0){perror("pthread_create print error");return -1;}// 4.防止主線程結束,進行阻塞回收從線程資源pthread_join(tid1, NULL);pthread_join(tid2, NULL);pthread_mutex_destroy(&lock);pthread_cond_destroy(&cond);return 0;
}

6、同步&互斥總結

互斥:兩個線程之間不可以同時運行,他們會相互排斥,必須等待一個線程運行完畢,另一個才能運行。

同步:兩個線程之間也不可以同時運行,但他是必須要按照某種次序來運行相應的線程(也可以說是一種互斥)!

所以說:同步是一種更為復雜的互斥,而互斥是一種特殊的同步。

二、Linux IO 模型

  • 場景假設1

假設媽媽有一個孩子,孩子在房間里睡覺,媽媽需要及時獲知孩子是否醒了,如何做?

  1. 媽媽在房間呆著,和孩子一起睡:媽媽不累,但是不能干其他事情。
  2. 時不時看一下孩子,其他事件可以干一點其他事情:累,但是可以干其他事情。
  3. 媽媽在客廳玩,聽孩子是否哭了:二者互不耽誤

1、阻塞式IO:最常見、效率低、不浪費CPU

阻塞I/O 模式是最普遍使用的I/O 模式,大部分程序使用的都是阻塞模式的I/O 。

學習的讀寫函數在調用過程中會發生阻塞,相關函數如下:

?讀操作中的read

讀阻塞--> 需要讀緩沖區中有數據可讀,讀阻塞解除

?寫操作中的write

寫阻塞--> 阻塞情況比較少,主要發生在寫入的緩沖區的大小小于要寫入的數據量的情況下,寫操作不進行任何拷貝工作,將發生阻塞,一旦緩沖區有足夠的空間,內核將喚醒進程,將數據從用戶緩沖區拷貝到相應的發送數據緩沖區。

2、非阻塞式IO:輪詢、耗費CPU、可以同時處理多路IO

?當我們設置為非阻塞模式,我們相當于告訴了系統內核:“當我請求的I/O 操作不能夠馬上完成,你想讓我的進程進行休眠等待的時候,不要這么做,請馬上返回一個錯誤給我。”

?當一個應用程序使用了非阻塞模式的套接字,它需要使用一個循環來不停地測試是否一個文件描述符有數據可讀(稱做polling)。

?應用程序不停的polling 內核來檢查是否I/O操作已經就緒。這將是一個極浪費CPU 資源的操作。

?這種模式使用中不普遍。

2.1 通過函數自帶參數設置

IPC_NOWAIT:非阻塞,不管有沒有消息都立刻返回,所以有可能會讀不到消息需要輪詢

2.2 通過設置文件描述符的屬性設置非阻塞

#include <unistd.h>

#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ );?

功能:設置文件描述符屬性

參數:

fd:文件描述符

cmd:設置方式 - 功能選擇

F_GETFL 獲取文件描述符的狀態信息 第三個參數化忽略

F_SETFL 設置文件描述符的狀態信息 通過第三個參數設置

O_NONBLOCK 非阻塞

O_ASYNC 異步

O_SYNC 同步

arg:設置的值 in

返回值:

特殊選擇返回特殊值 - F_GETFL 返回的狀態值(int)

其他:成功0 失敗-1,更新errno

使用:0為例子 0原本:阻塞、讀權限-->修改或添加為非阻塞 int flags=fcntl(0,F_GETFL); //1.獲取文件描述符的原有的屬性 flags=flags | O_NONBLOCK; //2.修改添加模式為非阻塞 fcntl(0,F_SETFL,flags); //3.設置修改后的模式

#include<stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include<string.h>
int main(int argc, char const *argv[])
{// 1.獲取文件描述符的屬性int flags = fcntl(0, F_GETFL); // 2.修改添加描述符屬性為阻塞flags |= O_NONBLOCK;// 3.設置文件描述符的屬性fcntl(0, F_SETFL, flags);// 4.實驗非阻塞模式char buf[32] = "";while (1){sleep(2);       fgets(buf, sizeof(buf), stdin);printf("buf: %s\n", buf);memset(buf, 0, sizeof(buf));printf("===========================\n");}return 0;
}

會發現不等待用戶輸入直接打印,但是也不影響輸入

注意:恢復阻塞模式需要關閉終端,換個終端才生效

或者設置回去:

flag &= ~O_NONBLOCK;

fcntl(0, F_SETFL, flag);

#include<stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include<string.h>
int main(int argc, char const *argv[])
{// 1.獲取文件描述符的屬性int flags = fcntl(0, F_GETFL); // 2.修改添加描述符屬性為阻塞flags |= O_NONBLOCK;// 3.設置文件描述符的屬性fcntl(0, F_SETFL, flags);// 4.恢復阻塞模式flags &= ~O_NONBLOCK;fcntl(0, F_SETFL, flags);// 5.實驗非阻塞模式char buf[32] = "";while (1){sleep(2);       fgets(buf, sizeof(buf), stdin);printf("buf: %s\n", buf);memset(buf, 0, sizeof(buf));printf("===========================\n");}return 0;
}

3、信號驅動IO:異步通知方式,底層驅動的支持

查看鼠標是哪個文件:

信號驅動I/O是一種異步I/O模型,通過操作系統向應用程序發送信號來通知數據可讀或可寫,從而避免輪詢或阻塞等待。

異步通知:異步通知是一種非阻塞的通知機制,發送方發送通知后不需要等待接收方的響應或確認。通知發送后,發送方可以繼續執行其他操作,而無需等待接收方處理通知。

1.通過信號方式,當內核檢測到設備數據后,會主動給應用發送信號SIGIO。

2.應用程序收到信號后做異步處理即可。

3.應用程序需要把自己的進程號告訴內核,并打開異步通知機制。

//1.將設置文件描述符和進程號遞交給內核驅動
//一旦fd有事件響應,則內核驅動會給進程發送一個SIGIO信號
fcntl(fd,F_SETOWN,getpid());//2.設置異步通知
int flags=fcntl(fd,F_GETFL);//獲取原來描述符屬性
flags|=O_ASYNC;//將屬性設置為異步
fcntl(fd,F_SETFL,flags); //將修改的屬性設置進去//3.signal捕捉SIGIO信號--SIGIO信號是內核通知進程的信號
signal(SIGIO,handler);#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
int fd;
void handler(int sig)
{char buf[32] = "";read(fd, buf, sizeof(buf));printf("%s\n", buf);
}
int main(int argc, char const *argv[])
{fd = open("/dev/input/mouse0", O_RDONLY);if (fd < 0){perror("open err");return -1;}printf("fd:%d\n", fd);//1.將設置文件描述符和進程號遞交給內核驅動//一旦fd有事件響應,則內核驅動會給進程發送一個SIGIO信號fcntl(fd, F_SETOWN, getpid());//2.設置異步通知int flags = fcntl(fd, F_GETFL); //獲取原來描述符屬性flags |= O_ASYNC;               //將屬性設置為異步fcntl(fd, F_SETFL, flags);      //將修改的屬性設置進去//3.signal捕捉SIGIO信號--SIGIO信號是內核通知進程的信號signal(SIGIO, handler);while (1){printf("玩一玩\n");sleep(1);}return 0;
}

阻塞IO(Blocking IO)

非阻塞IO

(Non-blocking IO)

信號驅動IO(Signal-driven IO)

同步性

同步

同步

異步

描述

調用IO操作的線程會被阻塞,直到操作完成

調用IO操作時,如果不能立即完成操作,會立即返回,線程可以繼續執行其他操作

當IO操作可以進行時,內核會發送信號通知進程

特點

最常見、效率低、不耗費cpu,

輪詢、耗費CPU,可以處理多路IO,效率高

異步通知方式,需要底層驅動的支持

適應場景

小規模IO操作,對性能要求不高

高并發網絡服務器,減少線程阻塞時間

實時性要求高的應用,避免輪詢開銷

  • 場景假設2

假設媽媽有三個孩子,分別不同的房間里睡覺,需要及時獲知每個孩子是否醒了,如何做?

阻塞IO?在一個房間,不行

非阻塞IO?不停的每個房間查看,可以

信號驅動IO?不行,因為只有一個信號,不知道哪個孩子醒了

方案:

1不停的每個房間查看:超級無敵累,但是也可以干點別的事

2媽媽在客廳睡覺,雇保姆孩子醒了讓保姆抱著找媽媽:即可以休息,也可以及時獲取狀態。

4、IO多路復用:select/poll/epoll

(1)應用程序中同時處理多路輸入輸出流,若采用阻塞模式,得不到預期的目的;

(2)若采用非阻塞模式,對多個輸入進行輪詢,但又太浪費CPU時間;

(3)若設置多個進程/線程,分別處理一條數據通路,將新產生進程/線程間的同步與通信問題,使程序變得更加復雜;

(4)比較好的方法是使用I/O多路復用技術。其基本思想是:

○ 先構造一張有關描述符的表(最大1024),然后調用一個函數。

○ 當這些文件描述符中的一個或多個已準備好進行I/O時函數才返回。

○ 函數返回時告訴進程那個描述符已就緒,可以進行I/O操作。

4.1 select

4.1.1 特點

特點:

1.一個進程最多只能監聽1024個文件描述符

2.select被喚醒之后要重新輪詢,效率相對低

3.select每次都會清空未發生響應的文件描述符,每次拷貝都需要從用戶空間到內核空間,效率低,開銷大

4.1.2 步驟

第一步:構造一張關于文件描述符的表

第二步:清空表FD_ZERO

第三步:將關心的文件描述符添加到表中FD_SET

第四步:調用select函數

第五步:判斷哪個或哪些文件描述符產生了事件FD_ISSET

第六步:做對應的邏輯處理

4.1.3 函數接口

int select(int nfds, fd_set *readfds, fd_set *writefds,

fd_set *exceptfds, struct timeval *timeout);

功能:

實現IO的多路復用

參數:

nfds:關注的最大的文件描述符+1

readfds:關注的讀表

writefds:關注的寫表

exceptfds:關注的異常表

timeout:超時的設置

NULL:一直阻塞,直到有文件描述符就緒或出錯

時間值為0:僅僅檢測文件描述符集的狀態,然后立即返回

時間值不為0:在指定時間內,如果沒有事件發生,則超時返回0,并清空設置的時間值

struct timeval

{

long tv_sec; /* 秒 */

long tv_usec; /* 微秒 = 10^-6秒 */

};

返回值:

成功時返回準備好的文件描述符的個數

0:超時檢測時間到并且沒有文件描述符準備好

-1 :失敗

注意:select返回后,關注列表中只存在準備好的文件描述符

操作表:

void FD_CLR(int fd, fd_set *set); //清除集合中的fd位

void FD_SET(int fd, fd_set *set); //將fd放入關注列表中

int FD_ISSET(int fd, fd_set *set); //判斷fd是否產生操作 是:1 不是:0

void FD_ZERO(fd_set *set); //清空關注列表

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <string.h>
int main(int argc, char const *argv[])
{// 1.打開鼠標文件描述符int fd_mouse = open("/dev/input/mouse0", O_RDONLY);if (fd_mouse < 0){perror("fd_mouse error");return -1;}printf("fd_mouse: %d\n", fd_mouse);// 2.創建文件描述符的表fd_set readfds;while (1)   /*select返回后表被內核修改了,所以每次循環都要清空并重新添加描述符到表中*/{// 3.清空表FD_ZERO(&readfds);// 4.將關心的文件描述符添加到表中/*= 1>鼠標 =*/FD_SET(fd_mouse, &readfds);/*= 2>鍵盤 =*/FD_SET(0, &readfds);// 5.監聽是否有描述符發生操作if (select(fd_mouse + 1, &readfds, NULL, NULL, NULL) < 0){perror("select error");return -1;}printf("something happend!\n");// 6.判斷是哪個文件描述符發生了操作/*= 1>鼠標 =*/char buf[32] = "";if (FD_ISSET(fd_mouse, &readfds)){ssize_t n = read(fd_mouse, buf, sizeof(buf) - 1);   /*預留一個空間給'\0'*/buf[n] = '\0';printf("mouse: %s\n", buf);  /*手動在末尾添加'\0',因為read不會補'\0'*/}/*= 2>鍵盤 =*/if (FD_ISSET(0, &readfds)){scanf("%s", buf);printf("keybord: %s\n", buf);}}   close(fd_mouse);return 0;
}

4.1.4 超時檢測

概念

什么是網絡超時檢測呢,比如某些設備的規定,發送請求數據后,如果多長時間后沒有收到來自設備的回復,那么需要做出一些特殊的處理

比如: 鏈接wifi的時候,等了好長時間也沒有連接上,此時系統會發送一個消息: 網絡連接失敗;

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <string.h>
int main(int argc, char const *argv[])
{// 1.打開鼠標文件描述符int fd_mouse = open("/dev/input/mouse0", O_RDONLY);if (fd_mouse < 0){perror("fd_mouse error");return -1;}printf("fd_mouse: %d\n", fd_mouse);// 2.創建文件描述符的表fd_set readfds;while (1)   /*select返回后表被內核修改了,所以每次循環都要清空并重新添加描述符到表中*/{// 3.清空表FD_ZERO(&readfds);// 4.將關心的文件描述符添加到表中/*= 1>鼠標 =*/FD_SET(fd_mouse, &readfds);/*= 2>鍵盤 =*/FD_SET(0, &readfds);// 超時檢測struct timeval tm = {2, 0}; /*定時2秒*/// 5.監聽是否有描述符發生操作if (select(fd_mouse + 1, &readfds, NULL, NULL, &tm) < 0){perror("select error");return -1;}else if (select(fd_mouse + 1, &readfds, NULL, NULL, &tm) == 0)    // 到時間IO還沒有準備就緒{perror("Time's up");continue;}   printf("something happend!\n");// 6.判斷是哪個文件描述符發生了操作/*= 1>鼠標 =*/char buf[32] = "";if (FD_ISSET(fd_mouse, &readfds)){ssize_t n = read(fd_mouse, buf, sizeof(buf) - 1);   /*預留一個空間給'\0'*/buf[n] = '\0';printf("mouse: %s\n", buf);  /*手動在末尾添加'\0',因為read不會補'\0'*/}/*= 2>鍵盤 =*/if (FD_ISSET(0, &readfds)){scanf("%s", buf);printf("keybord: %s\n", buf);}}   close(fd_mouse);return 0;
}
必要性
  1. 避免進程在沒有數據時無限制的阻塞;
  2. 規定時間未完成語句應有的功能,則會執行相關功能

4.2 poll

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

功能:同select相同實現IO的多路復用

參數:

fds:指向一個結構體數組的指針,用于指定測試某個給定的文件描述符的條件

nfds:指定的第一個參數數組的元素個數。

timeout:超時設置

-1:永遠等待

0:立即返回

>0:等待指定的毫秒數

struct pollfd

{

int fd; // 文件描述符

short events; // 等待的事件

short revents; // 實際發生的事件

};

返回值:

成功時返回結構體中 revents 域不為 0 的文件描述符個數

0: 超時前沒有任何事件發生時,返回 0

-1:失敗并設置 errno

特點:

(1)優化文件描述符的限制,文件描述符的限制取決于系統

(2)poll被喚醒之后要重新輪詢一遍,效率相對低

(3)poll不需要重新構造表,采用結構體數組,每次都需要從用戶空間拷貝到內核空間

4.2.1 實現過程

(1)創建一張表,也就是一個結構體數組struct pollfd fds[1000];

(2)添加關心的描述符到表中

(3)循環poll監聽更新表

(4)邏輯判斷

#include <stdio.h>
#include <sys/poll.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>int main(int argc, char const *argv[])
{int fd = open("/dev/input/mouse0", O_RDONLY);if (fd < 0){perror("open err");return -1;}//1.創建表也就是結構體數組struct pollfd fds[2];//2. 將關心的文件描述符添加到表中,并賦予事件fds[0].fd = 0;          //鍵盤fds[0].events = POLLIN; //想要發生的事件是讀事件fds[1].fd = fd;         //鼠標fds[1].events = POLLIN;//3.保存數組內最后一個有效元素下標int last = 1;//4.循環調用poll監聽while (1){int ret = poll(fds, last + 1, 2000);if (ret < 0){perror("poll err");return -1;}else if (ret == 0){printf("time out\n");continue;}//5.判斷結構體內文件描述符實際發生的事件char buf[32] = "";//鍵盤if (fds[0].revents == POLLIN){//6.根據不同的文件描述符發生的不同事件做對應的邏輯處理fgets(buf, sizeof(buf), stdin);printf("keyboard: %s\n", buf);}//鼠標if (fds[1].revents == POLLIN){ssize_t n = read(fd, buf, sizeof(buf) - 1);buf[n] = '\0';printf("mouse: %s\n", buf);}}close(fd);return 0;
}

4.3 epoll

特點:

  1. 監聽的最大的文件描述符沒有個數限制
  2. 異步IO,epoll當有事件產生被喚醒之后,文件描述符主動調用callback函數(回調函數)直接拿到喚醒的文件描述符,不需要輪詢,效率高
  3. epoll不需要重新構造文件描述符表,只需要從用戶空間拷貝到內核空間一次。

4.4 總結

select

poll

epoll

監聽個數

一個進程最多監聽1024個文件描述符

由程序員自己決定

百萬級

方式

每次都會被喚醒,都需要重新輪詢

每次都會被喚醒,都需要重新輪詢

紅黑樹內callback自動回調,不需要輪詢

效率

文件描述符數目越多,輪詢越多,效率越低

文件描述符數目越多,輪詢越多,效率越低

不輪詢,效率高

原理

每次使用select后,都會清空表

每次調用select,都需要拷貝用戶空間的表到內核空間

內核空間負責輪詢監視表內的文件描述符,將發生事件的文件描述符拷貝到用戶空間,再次調用select,如此循環

不會清空結構體數組

每次調用poll,都需要拷貝用戶空間的結構體到內核空間

內核空間負責輪詢監視結構體數組內的文件描述符,將發生事件的文件描述符拷貝到用戶空間,再次調用poll,如此循環

不會清空表

epoll中每個fd只會從用戶空間到內核空間只拷貝一次(上樹時)

通過epoll_ctl將文件描述符交給內核監管,一旦fd就緒,內核就會采用callback的回調機制來激活該fd,epoll_wait便可以收到通知(內核空間到用戶空間的拷貝

特點

一個進程最多能監聽1024個文件描述符

select每次被喚醒,都要重新輪詢表,效率低

select每次都清空未發生相應的文件描述符,每次都要拷貝用戶空間的表到內核空間

優化文件描述符的個數限制

poll每次被喚醒,都要重新輪詢,效率比較低(耗費cpu)

poll不需要構造文件描述符表(也不需要清空表),采用結構體數組,每次也需要從用戶空間拷貝到內核空間

監聽的文件描述符沒有個數限制(取決于自己的系統)

異步IO,epoll當有事件產生被喚醒,文件描述符會主動調用callback函數拿到喚醒的文件描述符,不需要輪詢,效率高

epoll不需要構造文件描述符的表,只需要從用戶空間拷貝到內核空間一次。

結構

數組

數組

紅黑樹+就緒鏈表

開發復雜度

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/100462.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/100462.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/100462.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

人工智能概念:NLP任務的評估指標(BLEU、ROUGE、PPL、BERTScore、RAGAS)

文章目錄一、評估指標基礎1. 準確率&#xff08;Accuracy&#xff09;2. 精確率&#xff08;Precision&#xff09;3. 召回率&#xff08;Recall&#xff09;4. F1-Score5. 示例二、文本生成專用指標1. BLEU&#xff1a;機器翻譯與標準化文案的“質量標尺”1.1 計算流程&#x…

團隊對 DevOps 理解不統一會帶來哪些問題

團隊對DevOps理念與實踐的理解不統一、片面甚至扭曲&#xff0c;是導致眾多企業DevOps轉型失敗的根本原因&#xff0c;它將直接引發一系列深層次的、相互關聯的嚴重問題。核心體現在&#xff1a;轉型極易淪為“為了工具而工具”的盲目自動化&#xff0c;導致最核心的文化變革被…

企業級實戰:構建基于Qt、C++與YOLOv8的模塊化工業視覺檢測系統(基于QWidget)

目錄一、概述二、項目目標與技術架構2.1 核心目標2.2 技術選型2.3 軟件架構三、AI推理DLL的開發 (Visual Studio 2019)3.1 定義DLL接口 (DetectorAPI.h)3.2 實現核心功能 (DetectorAPI.cpp)四、Qt Widget GUI應用程序的開發4.1 項目配置 (.pro 文件)4.2 UI設計 (mainwindow.ui)…

SVN自動化部署工具 腳本

SVN自動化部署工具 功能概述 這是一個自動化部署SVN倉庫的bash腳本&#xff0c;主要功能包括&#xff1a; 自動安裝SVN服務&#xff08;如未安裝&#xff09; 創建SVN項目倉庫 配置多用戶權限 設置自動同步到網站目錄 提供初始檢出功能 下載地址 https://url07.ctfile…

Facebook主頁變現功能被封?跨境玩家該如何申訴和預防

不少跨境玩家在運營Facebook公共主頁時&#xff0c;最期待的就是通過變現工具獲得穩定收入。但現實中&#xff0c;經常會遇到一個扎心的問題&#xff1a;主頁好不容易做起來&#xff0c;卻突然收到提示——“你的變現功能已被停用”。這意味著收入中斷&#xff0c;甚至可能導致…

安裝es、kibana、logstash

下載 elk 下載地址 elasticsearch地址: https://www.elastic.co/cn/downloads/elasticsearch kibana地址: https://www.elastic.co/cn/downloads/kibana logstash地址: https://www.elastic.co/cn/downloads/logstash 解壓elk 創建es全家桶文件夾 cd /usr/local mkdir elk …

Django admin 后臺開發案例【字段/圖片】

這是一個簡單的django admin 管理后臺,這個應用案例主要是給運營人員進行填寫數據 主要功能包括: 上傳圖片功能【選擇上傳時可以預覽】【替換已有數據中的圖片時可以預覽新舊圖片】 每條數據都將會記錄操作歷史。記錄操作人是誰?修改內容是什么?并且定位責任到某一員。 …

【C++】const和static的用法

目錄&#x1f680;前言&#x1f4bb;const&#xff1a;“只讀”的守護者&#x1f4af;修飾普通變量&#x1f4af;修飾指針&#x1f4af;修飾函數&#x1f4af;修飾類成員&#x1f4af;修飾對象&#x1f31f;static&#xff1a;“靜態存儲”與“作用域控制”&#x1f4af;修飾全…

F019 vue+flask海外購商品推薦可視化分析系統一帶一路【三種推薦算法】

文章結尾部分有CSDN官方提供的學長 聯系方式名片 B站up&#xff1a; 麥麥大數據 關注B站&#xff0c;有好處&#xff01; 編號: F019 關鍵詞&#xff1a;海外購 推薦系統 一帶一路 python 視頻 VueFlask 海外購電商大數據推薦系統源碼 &#xff08;三種推薦算法 全新界面布局…

【大數據專欄】流式處理框架-Apache Fink

Apache Fink 1 前言 1.1 功能 1.2 用戶 國際 國內 1.3 特點 ◆ 結合Java、Scala兩種語言 ◆ 從基礎到實戰 ◆ 系統學習Flink的核心知識 ◆ 快速完成從入門到上手企業開發的能力提升 1.4 安排 ◆ 初識Flink ◆ 編程模型及核心概念 ◆ DataSet API編程 ◆ Data…

向內核社區提交補丁

一、背景 內核的版本一直以來一直在持續迭代&#xff0c;離不開眾多開發者的貢獻。有時候我們會根據項目要求基于現有的內核版本開發一些新的功能或者修復掉一些特定場下的問題&#xff0c;我們是可以將其提交給社區的。 一般提交社區有兩個基本原則&#xff0c;一是提交的補…

TENGJUN-USB TYPE-C 24PIN測插雙貼連接器(H14.3,4腳插板帶柱):USB4.0高速傳輸時代的精密連接方案解析

在高速數據傳輸與多設備互聯需求日益增長的當下&#xff0c;USB TYPE-C接口憑借其可逆插拔、高兼容性的優勢成為主流&#xff0c;而TENGJUN推出的USB TYPE-C 24PIN測插雙貼連接器&#xff08;規格&#xff1a;H14.3&#xff0c;4腳插板帶柱&#xff09; &#xff0c;以對USB4.0…

企業級 Docker 應用:部署、倉庫與安全加固

1 Docker簡介及部署方法 1.1 Docker簡介 Docker之父Solomon Hykes&#xff1a;Docker就好比傳統的貨運集裝箱 Note 2008 年LXC(LinuX Contiainer)發布&#xff0c;但是沒有行業標準&#xff0c;兼容性非常差 docker2013年首次發布&#xff0c;由Docker, Inc開發1.1.1 什么是do…

rust語言 (1.88) 學習筆記:客戶端和服務器端同在一個項目中

同一項目下多個可執行文件&#xff0c;多個子項目參照以下&#xff1a; 一、項目目錄 項目/|-- client/|-- main.rs|-- Cargo.toml|-- server/|-- main.rs|-- Cargo.toml|-- Cargo.toml二、項目公共 Cargo.toml [workspace] # 定義Rust工作區配置 members …

mac本地安裝mysql

本人環境 macOs 14.5 1.下載安裝mysql https://dev.mysql.com/downloads/mysql/ 配置環境變量&#xff0c;打開terminal vim ~/.bash_profile 添加MYSQL_HOME/usr/local/mysql 在PATH中添加 通過mysql --version命令查看版本 2.開啟mysql 打開終端teminal,輸入命令 sudo…

面試前端遇到的問題

面試官讓我寫一個delay函數然后這是我寫的代碼async function delay(){setTimeout(function() {}, 3000); }面試官就和我說不是這個&#xff0c;用promise當時就蒙了&#xff0c;什么東西&#xff0c;為什么要用promise然后問豆包說Promise 是 JavaScript 中用于處理異步操作的…

Ubuntu Desktop 22.04.5 LTS 使用默認的 VNC 遠程桌面

1. 打開 VNC 打開設置 - 分享 - 遠程桌面2. 配置 VNC 打開遠程桌面 啟用vnc 選擇vnc密碼訪問 配置密碼3. 固定密碼 遠程桌面的訪問密碼在每次開機后會刷新一次&#xff0c;可以通過以下方式固定 打開【應用程序】&#xff0d;【附件】&#xff0d;密碼和加密密鑰&#xff08;或…

【無線安全實驗4】基于偽隨機數的WPS PIN碼逆向(精靈塵埃/仙塵攻擊)

文章目錄1 原理分析1.1 WPS連接過程1.1.1 初始階段1.1.2 注冊階段1.2 WPS攻擊原理1.2.1 在線攻擊1.2.2 離線攻擊1.2.2.1 Ralink模式1.2.2.2 eCos模式2 實驗過程3 參考資料在2011年 Stefan Viehbck 演示過WPS的在線暴力攻擊&#xff0c;由于PIN碼猜測最多只需11000種組合&#x…

IDEA開發過程中經常使用到的快捷鍵

IntelliJ IDEA 開發 Java 時常用的快捷鍵列表 代碼編輯與行操作快捷鍵功能描述Ctrl Y刪除當前行。Ctrl D復制當前行到下一行。Shift Alt ↑將當前行&#xff08;或選中塊&#xff09;向上移動。Shift Alt ↓將當前行&#xff08;或選中塊&#xff09;向下移動。Ctrl /注…

ubuntu使用webrtc庫開發一個webrtc推拉流程序

目錄 一. 前言 二. 整體交互流程 三. 類實現說明 1. WebRtcClient 2. SignalPeerClient 3. WebRTCStream 4. 視頻源類 5. 拉流渲染 四. 使用示例 1. 推流代碼示例 2. 拉流代碼示例 一. 前言 在 《ubuntu編譯webrtc庫》我們介紹了如何在 ubuntu 上使用 webrtc 源代碼…