[英]Is rearming file descriptors for epoll thread safe?
從這個問題中我知道可以在另一個線程阻塞epoll_wait(2)
同時調用epoll_ctl(2)
epoll_wait(2)
。 我仍然有一個問題。
當將epoll
與EPOLLONESHOT
標志一起使用時,只會觸發一個事件,並且必須使用epoll_ctl(2)
重新設置fd。 這是必要的,因此只有一個線程將從fd中讀取並適當地處理結果。
以下是一個時間表,可以從某種程度上可視化我的假設問題:
Thread1: Thread2: Kernel:
-----------------------------------------------------------------------
epoll_wait();
Receives chunk
dispatch chunk to thread 2
epoll_wait(); Handle chunk
Still handle chunk Receives chunk
Rearm fd for epoll
?
收到大塊后重新設置fd時,問號會發生什么? epoll
會引發EPOLLIN
事件,還是會在套接字可讀的情況下無限期阻止? 我的建築完全明智嗎?
您的架構是明智的,並且可以使用: epoll
將文件描述符標記為可讀,並觸發EPOLLIN
事件。
有關此文件的文獻很少而又微妙。 關於man 7 epoll
的“ Q / A”部分簡要提到了這一點:
Q8對文件描述符的操作是否會影響已收集但尚未報告的事件?
A8您可以對現有文件描述符執行兩項操作。 在這種情況下,刪除將毫無意義。 修改將重新讀取可用的I / O。
刪除和修改您可以對現有文件描述符執行的兩個操作(現有文件描述符是過去已添加到epoll集中的文件描述符-其中包括等待重新存儲的文件描述符)。 正如聯機幫助頁所述,刪除在這里沒有意義,而修改將重新評估文件描述符中的條件。
但是,沒有什么能比真實世界的實驗更好。 以下程序測試這種極端情況:
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
#include <assert.h>
#include <semaphore.h>
#include <sys/epoll.h>
#include <unistd.h>
static pthread_t tids[2];
static int epoll_fd;
static char input_buff[512];
static sem_t chunks_sem;
void *dispatcher(void *arg) {
struct epoll_event epevent;
while (1) {
printf("Dispatcher waiting for more chunks\n");
if (epoll_wait(epoll_fd, &epevent, 1, -1) < 0) {
perror("epoll_wait(2) error");
exit(EXIT_FAILURE);
}
ssize_t n;
if ((n = read(STDIN_FILENO, input_buff, sizeof(input_buff)-1)) <= 0) {
if (n < 0)
perror("read(2) error");
else
fprintf(stderr, "stdin closed prematurely\n");
exit(EXIT_FAILURE);
}
input_buff[n] = '\0';
sem_post(&chunks_sem);
}
return NULL;
}
void *consumer(void *arg) {
sigset_t smask;
sigemptyset(&smask);
sigaddset(&smask, SIGUSR1);
while (1) {
sem_wait(&chunks_sem);
printf("Consumer received chunk: %s", input_buff);
/* Simulate some processing... */
sleep(2);
printf("Consumer finished processing chunk.\n");
printf("Please send SIGUSR1 after sending more data to stdin\n");
int signo;
if (sigwait(&smask, &signo) < 0) {
perror("sigwait(3) error");
exit(EXIT_FAILURE);
}
assert(signo == SIGUSR1);
struct epoll_event epevent;
epevent.events = EPOLLIN | EPOLLONESHOT;
epevent.data.fd = STDIN_FILENO;
if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, STDIN_FILENO, &epevent) < 0) {
perror("epoll_ctl(2) error when attempting to readd stdin");
exit(EXIT_FAILURE);
}
printf("Readded stdin to epoll fd\n");
}
}
int main(void) {
sigset_t sigmask;
sigfillset(&sigmask);
if (pthread_sigmask(SIG_SETMASK, &sigmask, NULL) < 0) {
perror("pthread_sigmask(3) error");
exit(EXIT_FAILURE);
}
if ((epoll_fd = epoll_create(1)) < 0) {
perror("epoll_create(2) error");
exit(EXIT_FAILURE);
}
struct epoll_event epevent;
epevent.events = EPOLLIN | EPOLLONESHOT;
epevent.data.fd = STDIN_FILENO;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &epevent) < 0) {
perror("epoll_ctl(2) error");
exit(EXIT_FAILURE);
}
if (sem_init(&chunks_sem, 0, 0) < 0) {
perror("sem_init(3) error");
exit(EXIT_FAILURE);
}
if (pthread_create(&tids[0], NULL, dispatcher, NULL) < 0) {
perror("pthread_create(3) error on dispatcher");
exit(EXIT_FAILURE);
}
if (pthread_create(&tids[1], NULL, consumer, NULL) < 0) {
perror("pthread_create(3) error on consumer");
exit(EXIT_FAILURE);
}
size_t i;
for (i = 0; i < sizeof(tids)/sizeof(tids[0]); i++) {
if (pthread_join(tids[i], NULL) < 0) {
perror("pthread_join(3) error");
exit(EXIT_FAILURE);
}
}
return 0;
}
它的工作方式如下:調度程序線程將stdin
添加到epoll集合中,然后使用epoll_wait(2)
從stdin
獲取可讀的輸入。 輸入到達時,調度程序喚醒工作線程,該工作線程打印輸入並通過休眠2秒來模擬一些處理時間。 同時,調度程序返回主循環並再次在epoll_wait(2)
中epoll_wait(2)
。
除非您通過發送SIGUSR1
來告知工作線程,否則工作線程不會重新啟動stdin
。 因此,我們只需要向stdin
寫入更多內容,然后將SIGUSR1
發送到該進程即可。 工作線程接收該信號,才把它會重置stdin
-這已經是可讀由時間,以及調度器已經等待epoll_wait(2)
您可以從輸出中看到調度程序已正確喚醒,並且一切工作都像一個超級按鈕:
Dispatcher waiting for more chunks
testing 1 2 3 // Input
Dispatcher waiting for more chunks // Dispatcher notified worker and is waiting again
Consumer received chunk: testing 1 2 3
Consumer finished processing chunk.
Please send SIGUSR1 after sending more data to stdin
hello world // Input
Readded stdin to epoll fd // Rearm stdin; dispatcher is already waiting
Dispatcher waiting for more chunks // Dispatcher saw new input and is now waiting again
Consumer received chunk: hello world
Consumer finished processing chunk.
Please send SIGUSR1 after sending more data to stdin
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.