2015-07-11 27 views
7

Có an toàn để chia sẻ cùng một Epoll fd (không phải socket fd) trong một số chủ đề không? Và nếu có, mỗi luồng có phải vượt qua mảng sự kiện riêng của mình để epoll_wait(2) hoặc chúng có thể chia sẻ nó không?Có OK để chia sẻ cùng một bộ mô tả tệp epoll giữa các luồng không?

Ví dụ

void *thread_func(void *thread_args) { 
     // extract socket_fd, epoll_fd, &event, &events_array from 
     //  thread_args 
     // epoll_wait() using epoll_fd and events_array received from main 
     // now all threads would be using same epoll_fd and events array 
    } 

    void main(void) { 
     // create and bind to socket 
     // create events_fd 
     // allocate memory for events array 
     // subscribe to events EPOLLIN and EPOLLET 
     // pack the socket_fd, epoll_fd, &events, &events_array into 
     // thread_args struct. 

     // create multiple threads and pass thread_func and 
     // same thread_args to all threads 
    } 

Hoặc là nó tốt hơn để làm điều đó như thế này:

void *thread_func(void *socket_fd) { 
     // create events_fd 
     // allocate memory for events array 
     // subscribe to events EPOLLIN and EPOLLET 
     // epoll_wait using own epoll_fd and events_array 
     // now all threads would have a separate epoll_fd with 
     // events populated on its own array 
    } 

    void main(void) { 
    // create and bind to socket 

    //create multiple threads and pass thread_func and socket_fd to 
    // all threads 
    } 

Có một ví dụ tốt về làm thế nào để làm điều này trong C? Các ví dụ tôi thấy chạy vòng lặp sự kiện trong main() và sinh ra một luồng mới để xử lý yêu cầu bất cứ khi nào một sự kiện được phát hiện. Những gì tôi muốn làm là tạo ra một số lượng cụ thể của các chủ đề ở đầu của chương trình và có mỗi thread chạy vòng lặp sự kiện và yêu cầu xử lý.

Trả lời

13

Có an toàn để chia sẻ cùng một Epoll fd (không phải socket fd) trong một số chủ đề .

Vâng, nó là an toàn - giao diện epoll(7) là thread-safe - nhưng bạn nên cẩn thận khi làm như vậy, ít nhất bạn nên sử dụng EPOLLET (chế độ edge-kích hoạt, như trái ngược với mặc định cấp kích hoạt) để tránh đánh thức giả mạo trong các chủ đề khác. Điều này là do chế độ kích hoạt cấp sẽ đánh thức mọi chuỗi khi có sự kiện mới để xử lý. Vì chỉ có một luồng sẽ xử lý nó, điều này sẽ đánh thức hầu hết các luồng không cần thiết.

Nếu epfd chia sẻ được sử dụng mỗi thread sẽ phải vượt qua các sự kiện riêng mảng hoặc một mảng sự kiện chia sẻ để epoll_wait()

Có, bạn cần một mảng sự kiện riêng biệt trên mỗi thread, hoặc nếu không bạn sẽ có điều kiện chủng tộc và những điều khó chịu có thể xảy ra. Ví dụ, bạn có thể có một chuỗi vẫn đang lặp qua các sự kiện được trả về bởi epoll_wait(2) và xử lý các yêu cầu khi đột nhiên một chuỗi khác gọi epoll_wait(2) với cùng một mảng và sau đó các sự kiện bị ghi đè cùng lúc với chuỗi khác đang đọc chúng. Không tốt! Bạn hoàn toàn cần một mảng riêng biệt cho mỗi chuỗi.

Giả sử bạn có một mảng riêng biệt cho mỗi chuỗi, hoặc khả năng - chờ đợi trên cùng một fd epoll hoặc có foll epoll riêng biệt cho mỗi chuỗi - sẽ hoạt động tốt như nhau, nhưng lưu ý rằng ngữ nghĩa khác nhau. Với foll được chia sẻ chung trên toàn cầu, mỗi chuỗi chờ yêu cầu từ bất kỳ ứng dụng nào, vì tất cả khách hàng đều được thêm vào cùng một fd epoll. Với một foll epoll riêng biệt cho mỗi thread, sau đó mỗi thread về cơ bản chịu trách nhiệm cho một tập hợp con của các máy khách (những máy khách đã được chấp nhận bởi chủ đề đó).

Điều này có thể không liên quan đến hệ thống của bạn hoặc có thể tạo ra sự khác biệt lớn. Ví dụ, nó có thể xảy ra rằng một thread là không may, đủ để có được một nhóm người sử dụng năng lượng mà làm cho yêu cầu nặng và thường xuyên, để lại thread overworked, trong khi các chủ đề khác với khách hàng ít tích cực gần như nhàn rỗi. Nó sẽ không công bằng sao? Mặt khác, có thể bạn muốn chỉ có một số chủ đề giao dịch với một lớp người dùng cụ thể và trong trường hợp đó, có thể có ý nghĩa khi có các fds epoll khác nhau trên mỗi chuỗi. Như thường lệ, bạn cần phải xem xét cả hai khả năng, đánh giá thương mại, suy nghĩ về vấn đề cụ thể của bạn, và đưa ra quyết định.

Dưới đây là ví dụ sử dụng fd được chia sẻ chung trên toàn cầu.Ban đầu tôi không có ý định làm tất cả những điều này, nhưng một điều đã dẫn đến việc khác, và, tốt, thật vui và tôi nghĩ nó có thể giúp bạn bắt đầu. Đó là một máy chủ echo lắng nghe trên cổng 3000 và có một nhóm gồm 20 chủ đề sử dụng epoll để đồng thời chấp nhận các máy khách mới và phục vụ các yêu cầu.

#include <stdio.h> 
#include <stdlib.h> 
#include <inttypes.h> 
#include <errno.h> 
#include <string.h> 
#include <pthread.h> 
#include <assert.h> 
#include <unistd.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <arpa/inet.h> 
#include <sys/epoll.h> 

#define SERVERPORT 3000 
#define SERVERBACKLOG 10 
#define THREADSNO 20 
#define EVENTS_BUFF_SZ 256 

static int serversock; 
static int epoll_fd; 
static pthread_t threads[THREADSNO]; 

int accept_new_client(void) { 

    int clientsock; 
    struct sockaddr_in addr; 
    socklen_t addrlen = sizeof(addr); 
    if ((clientsock = accept(serversock, (struct sockaddr *) &addr, &addrlen)) < 0) { 
     return -1; 
    } 

    char ip_buff[INET_ADDRSTRLEN+1]; 
    if (inet_ntop(AF_INET, &addr.sin_addr, ip_buff, sizeof(ip_buff)) == NULL) { 
     close(clientsock); 
     return -1; 
    } 

    printf("*** [%p] Client connected from %s:%" PRIu16 "\n", (void *) pthread_self(), 
      ip_buff, ntohs(addr.sin_port)); 

    struct epoll_event epevent; 
    epevent.events = EPOLLIN | EPOLLET; 
    epevent.data.fd = clientsock; 

    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, clientsock, &epevent) < 0) { 
     perror("epoll_ctl(2) failed attempting to add new client"); 
     close(clientsock); 
     return -1; 
    } 

    return 0; 
} 

int handle_request(int clientfd) { 
    char readbuff[512]; 
    struct sockaddr_in addr; 
    socklen_t addrlen = sizeof(addr); 
    ssize_t n; 

    if ((n = recv(clientfd, readbuff, sizeof(readbuff)-1, 0)) < 0) { 
     return -1; 
    } 

    if (n == 0) { 
     return 0; 
    } 

    readbuff[n] = '\0'; 

    if (getpeername(clientfd, (struct sockaddr *) &addr, &addrlen) < 0) { 
     return -1; 
    } 

    char ip_buff[INET_ADDRSTRLEN+1]; 
    if (inet_ntop(AF_INET, &addr.sin_addr, ip_buff, sizeof(ip_buff)) == NULL) { 
     return -1; 
    } 

    printf("*** [%p] [%s:%" PRIu16 "] -> server: %s", (void *) pthread_self(), 
      ip_buff, ntohs(addr.sin_port), readbuff); 

    ssize_t sent; 
    if ((sent = send(clientfd, readbuff, n, 0)) < 0) { 
     return -1; 
    } 

    readbuff[sent] = '\0'; 

    printf("*** [%p] server -> [%s:%" PRIu16 "]: %s", (void *) pthread_self(), 
      ip_buff, ntohs(addr.sin_port), readbuff); 

    return 0; 
} 

void *worker_thr(void *args) { 
    struct epoll_event *events = malloc(sizeof(*events)*EVENTS_BUFF_SZ); 
    if (events == NULL) { 
     perror("malloc(3) failed when attempting to allocate events buffer"); 
     pthread_exit(NULL); 
    } 

    int events_cnt; 
    while ((events_cnt = epoll_wait(epoll_fd, events, EVENTS_BUFF_SZ, -1)) > 0) { 
     int i; 
     for (i = 0; i < events_cnt; i++) { 
      assert(events[i].events & EPOLLIN); 

      if (events[i].data.fd == serversock) { 
       if (accept_new_client() == -1) { 
        fprintf(stderr, "Error accepting new client: %s\n", 
         strerror(errno)); 
       } 
      } else { 
       if (handle_request(events[i].data.fd) == -1) { 
        fprintf(stderr, "Error handling request: %s\n", 
         strerror(errno)); 
       } 
      } 
     } 
    } 

    if (events_cnt == 0) { 
     fprintf(stderr, "epoll_wait(2) returned 0, but timeout was not specified...?"); 
    } else { 
     perror("epoll_wait(2) error"); 
    } 

    free(events); 

    return NULL; 
} 

int main(void) { 
    if ((serversock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { 
     perror("socket(2) failed"); 
     exit(EXIT_FAILURE); 
    } 

    struct sockaddr_in serveraddr; 
    serveraddr.sin_family = AF_INET; 
    serveraddr.sin_port = htons(SERVERPORT); 
    serveraddr.sin_addr.s_addr = INADDR_ANY; 

    if (bind(serversock, (const struct sockaddr *) &serveraddr, sizeof(serveraddr)) < 0) { 
     perror("bind(2) failed"); 
     exit(EXIT_FAILURE); 
    } 

    if (listen(serversock, SERVERBACKLOG) < 0) { 
     perror("listen(2) failed"); 
     exit(EXIT_FAILURE); 
    } 

    if ((epoll_fd = epoll_create(1)) < 0) { 
     perror("epoll_create(2) failed"); 
     exit(EXIT_FAILURE); 
    } 

    struct epoll_event epevent; 
    epevent.events = EPOLLIN | EPOLLET; 
    epevent.data.fd = serversock; 

    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, serversock, &epevent) < 0) { 
     perror("epoll_ctl(2) failed on main server socket"); 
     exit(EXIT_FAILURE); 
    } 

    int i; 
    for (i = 0; i < THREADSNO; i++) { 
     if (pthread_create(&threads[i], NULL, worker_thr, NULL) < 0) { 
      perror("pthread_create(3) failed"); 
      exit(EXIT_FAILURE); 
     } 
    } 

    /* main thread also contributes as worker thread */ 
    worker_thr(NULL); 

    return 0; 
} 

Một vài lưu ý:

  • main() nên trở int, không void (như bạn thấy trong ví dụ của bạn)
  • Luôn luôn đối phó với mã lỗi lại. Nó rất phổ biến để bỏ qua chúng và khi mọi thứ phá vỡ thật khó để biết những gì đã xảy ra.
  • Mã giả định rằng không có yêu cầu nào lớn hơn 511 byte (được xem bởi kích thước bộ đệm trong handle_request()). Nếu một yêu cầu lớn hơn, có thể một số dữ liệu còn lại trong socket trong một thời gian rất dài, vì epoll_wait(2) sẽ không báo cáo cho đến khi một sự kiện mới xảy ra trên bộ mô tả tệp đó (vì chúng tôi đang sử dụng EPOLLET). Trong trường hợp xấu nhất, khách hàng có thể không bao giờ thực sự gửi bất kỳ dữ liệu mới nào và chờ một câu trả lời mãi mãi.
  • Mã in số nhận dạng chuỗi cho mỗi yêu cầu giả định rằng pthread_t là loại con trỏ mờ. Thật vậy, pthread_t là một loại con trỏ trong Linux, nhưng nó có thể là một loại số nguyên trong các nền tảng khác, vì vậy đây không phải là di động. Tuy nhiên, đó có lẽ không phải là một vấn đề, vì epoll là Linux cụ thể, do đó, mã không phải là di động anyway.
  • Giả định rằng không có yêu cầu nào khác từ cùng một khách hàng đến khi chuỗi vẫn đang phục vụ yêu cầu từ khách hàng đó. Nếu yêu cầu mới đến trong thời gian chờ đợi và một chuỗi khác bắt đầu phân phối, chúng tôi có điều kiện chủng tộc và khách hàng sẽ không nhất thiết nhận được thông báo echo theo cùng thứ tự mà anh gửi cho họ (tuy nhiên, write(2) là nguyên tử, vì vậy trong khi trả lời có thể ra khỏi trật tự, họ sẽ không xen kẽ).
+0

Cảm ơn câu trả lời toàn diện. Đó là sự giúp đỡ to lớn. – MiJo

+0

@MiJo Vui vì tôi có thể giúp. Đó là một câu hỏi tuyệt vời :) –

Các vấn đề liên quan