簡體   English   中英

重載epoll線程的文件描述符是否安全?

[英]Is rearming file descriptors for epoll thread safe?

這個問題中我知道可以在另一個線程阻塞epoll_wait(2)同時調用epoll_ctl(2) epoll_wait(2) 我仍然有一個問題。

當將epollEPOLLONESHOT標志一起使用時,只會觸發一個事件,並且必須使用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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM