Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/70.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/linux/26.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C中带有工作线程的epoll IO_C_Linux_Multithreading_Posix_Epoll - Fatal编程技术网

C中带有工作线程的epoll IO

C中带有工作线程的epoll IO,c,linux,multithreading,posix,epoll,C,Linux,Multithreading,Posix,Epoll,我正在编写一个小型服务器,它将接收来自多个来源的数据并处理这些数据。接收到的数据源和数据非常重要,但只有epoll能够很好地处理。但是,所有接收到的数据都必须经过分析,并通过大量测试运行,这非常耗时,而且即使epoll多路复用,也会阻塞单个线程。基本上,模式应该如下所示:IO循环接收数据并将其捆绑到作业中,发送到池中可用的第一个线程,该捆绑由作业处理,结果传递给IO循环以写入文件 我决定使用一个IO线程和N个工作线程。使用以下提供的示例,接受tcp连接和读取数据的IO线程很容易实现: 线程通常

我正在编写一个小型服务器,它将接收来自多个来源的数据并处理这些数据。接收到的数据源和数据非常重要,但只有epoll能够很好地处理。但是,所有接收到的数据都必须经过分析,并通过大量测试运行,这非常耗时,而且即使epoll多路复用,也会阻塞单个线程。基本上,模式应该如下所示:IO循环接收数据并将其捆绑到作业中,发送到池中可用的第一个线程,该捆绑由作业处理,结果传递给IO循环以写入文件

我决定使用一个IO线程和N个工作线程。使用以下提供的示例,接受tcp连接和读取数据的IO线程很容易实现:

线程通常也很容易处理,但我正在努力以优雅的方式将epoll IO循环与线程池结合起来。我在网上也找不到任何将epoll与工作池一起使用的“最佳实践”,但关于同一主题的问题很多

因此,我有一些问题,希望有人能帮我回答:

  • eventfd是否可以(也应该)用作IO线程和所有工作线程之间的双向同步机制?例如,每个工作线程都有自己的epoll例程等待共享的eventfd(带有一个结构指针,包含关于作业的数据/信息),即以某种方式使用eventfd作为作业队列,这是一个好主意吗?还可能有另一个eventfd将结果从多个工作线程传递回IO线程
  • 在IO线程收到关于套接字上更多数据的信号后,实际的recv应该发生在IO线程上,还是工作线程应该自己recv数据,以便在解析数据帧等时不阻塞IO线程。?在这种情况下,如何确保安全性,例如,如果recv读取工作线程中的1,5帧数据,而另一个工作线程从同一连接接收到最后0,5帧数据
  • 如果工作线程池是通过互斥体等实现的,那么如果N+1个线程试图使用相同的锁,等待锁是否会阻止IO线程
  • 对于如何使用双向通信(即从IO到工作线程和从IO到工作线程)围绕epoll构建工作线程池,是否有良好的实践模式
  • 编辑:一种可能的解决方案是从IO循环更新一个环形缓冲区,在更新之后通过所有工作区的共享管道将环形缓冲区索引发送给工作区(从而将该索引的控制权让给从管道读取索引的第一个工作区),让工作线程拥有该索引直到处理结束,然后再次通过管道将索引号发送回IO线程,从而返回控制权


    我的应用程序是Linux专用的,因此我可以使用Linux专用的功能,以便以最优雅的方式实现这一点。不需要跨平台支持,但性能和线程安全是必需的。

    在我的测试中,每个线程一个epoll实例的性能远远优于复杂的线程模型。如果将侦听器套接字添加到所有epoll实例中,那么工作人员只需
    接受(2)
    ,获胜者将获得连接并在其生命周期内对其进行处理

    您的员工可能看起来像这样:

    for (;;) {
        nfds = epoll_wait(worker->efd, &evs, 1024, -1);
    
        for (i = 0; i < nfds; i++)
            ((struct socket_context*)evs[i].data.ptr)->handler(
                evs[i].data.ptr,
                evs[i].events);
    }
    
    epoll_add(serv_sock);
    while(1){
        ret = epoll_wait();
        foreach(ret as fd){
            req = fd.read();
            resp = proc(req);
            fd.send(resp);
        }
    }
    
    我喜欢这个策略,因为:

    • 设计非常简单
    • 所有螺纹都相同
    • 工作人员和连接是隔离的——没有踩到对方的脚趾或在错误的工作人员中调用
      read(2)
    • 不需要锁(内核开始担心
      accept(2)
      上的同步问题)
    • 有些自然地负载平衡,因为没有忙碌的工作人员会在
      accept(2)
      上积极竞争
    关于epoll的一些注意事项:

    • 使用边缘触发模式,非阻塞插槽,始终读取,直到
      EAGAIN
    • 避免使用
      dup(2)
      family调用来避免一些意外情况(epoll注册文件描述符,但实际上监视文件描述)
    • 您可以安全地
      epoll\u ctl(2)
      其他线程的epoll实例
    • epoll\u wait(2)
      使用大型
      struct epoll\u事件
      缓冲区以避免饥饿
    其他一些注意事项:

    • 使用
      accept4(2)
      保存系统调用
    • 每个内核使用一个线程(如果CPU绑定,则每个物理线程1个;如果I/O绑定,则每个逻辑线程1个)
    • poll(2)
      /
      select(2)
      在连接计数较低时可能更快

    我希望这会有所帮助。

    在执行此模型时,因为只有在完全接收到数据包后,我们才知道数据包的大小,不幸的是,我们无法将接收本身卸载到工作线程。相反,我们仍然可以做的最好的事情是使用一个线程来接收数据,该线程必须传递指向完全接收的数据包的指针

    数据本身最好保存在一个循环缓冲区中,但是我们希望每个输入源都有一个单独的缓冲区(如果我们得到一个部分数据包,我们可以继续从其他来源接收数据,而不必分割数据。剩下的问题是如何通知工作人员新数据包何时准备好,并给他们一个指向所述数据包中数据的指针。因为这里的数据很少,所以只需要一些指针。最优雅的方法是使用posi消息队列。这些队列为多个发送者和多个接收者提供了写和读消息的能力,始终确保每一条消息都被接收,并且只由一个线程接收

    对于每个数据源,您都需要一个类似于下面的结构,我现在将遍历这些字段

    struct DataSource
    {
        int SourceFD;
        char DataBuffer[MAX_PACKET_SIZE * (THREAD_COUNT + 1)];
        char *LatestPacket;
        char *CurrentLocation
        int SizeLeft;
    };
    
    SourceFD显然是相关数据流的文件描述符,DataBuffer是处理数据包内容的地方,它是一个循环缓冲区
    epoll_add(serv_sock);
    while(1){
        ret = epoll_wait();
        foreach(ret as fd){
            req = fd.read();
            resp = proc(req);
            fd.send(resp);
        }
    }
    
    epoll_add(serv_sock);
    epoll_add(queue->fd());
    while(1){
        ret = epoll_wait();
        foreach(ret as fd){
            if(fd is worker_thread){
                sock, resp = worker->pop_result();
                sock.send(resp);
            }
            if(fd is client_socket){
                req = fd.read();
                worker->add_task(fd, req);
            }
        }
    }
    
    fd, req = queue->pop_task();
    resp = proc(req);
    queue->add_result(fd, resp);