使用线程池和多个事件的 Linux 轮询

Linux poll with a thread pool and multiple events

提问人:nick2225 提问时间:10/3/2023 最后编辑:nick2225 更新时间:10/3/2023 访问量:128

问:

我正在用 C 语言开发一个简单的客户端服务器程序,其中多个客户端将连接到单个服务器。

客户端将向服务器提交操作/操作,服务器将处理这些请求。这些操作可能很昂贵和/或运行时间很长,所以理想情况下,我希望在服务器上有一个可以并发处理请求的线程池,而不是阻塞主线程。

此外,我还认为使用(不能使用,因为我需要符合 POSIX 标准)可能比为每个套接字连接创建一个新线程(源于 C10K 问题:https://en.wikipedia.org/wiki/C10k_problem)可能更好。pollepoll

因此,从理论上讲,服务器可能看起来像以下伪代码

int main()
{
  // Pretend these are initialized in some manner
  ThreadPool thread_pool;
  Socket server_listening_socket;
  PolledFileDescriptors list_of_polled_fds;

  // The first pollfd will be the listening socket which looks for read events on it
  list_of_polled_fds[0].fd = server_listening_socket;
  list_of_polled_fds[0].events = POLLIN;

  while (true)
  {
    // Call poll on our list of file descriptors with unlimited timeout (-1)
    poll(&list_of_polled_fds, number_of_fds, -1);
    for (int i = 0; i < number_of_fds; i++)
    {
      // We received a read event on this file descriptor
      if (list_of_polled_fds[i].revents & POLLIN)
      {
 
        // The listening socket has an event (meaning a new connection was created) 
        if (i == 0)
        {
          Socket client_socket = accept();
          AddClientConnectionToListOfPollFds(&list_of_polled_fds, client_socket);
        }

        // A connected client has an event (data was sent over the socket)
        else
        {  
          ThreadPoolTask task = {
            .argument = list_of_polled_fds[i].fd // client connected file descriptor
            .function = SomeFunctionToReadDataFromSocketAndProcessIt
          };
          AddTaskToThreadPool(&thread_pool, &task);
        }
      }
    }
  }

  return 0;
}

现在有了这个高级设计,我有一些担忧。

单个消息导致多个事件

  • 假设客户端尝试向服务器发送一条 10 字节的消息,但由于某种原因,字节被拆分为 2 个 TCP 数据包。
  • 第一个数据包将进入客户端套接字,这将导致检测到事件。poll
  • 然后,它将此套接字放入线程池上的任务中,该任务将读取和处理数据。
  • 然后,第二个数据包进入并导致轮询执行相同的操作。
  • 现在我的线程池中有 2 个任务,它们对应于同一个套接字,并且应该是相同的“消息”。

我应该如何管理?我是否应该只跟踪线程池中当前正在处理哪些套接字,而如果存在任务,则不添加相同的套接字?

如果我保护线程池不两次添加相同的套接字,那么这意味着如果单个客户端发送 2 个独立的请求,我将无法并行处理它们。我将不得不等待第一条消息完成,然后处理下一条消息。

什么是好的机制来检测多个事件是否属于单个客户端消息,这样我既不能将冗余任务添加到线程池,又可以同时处理来自客户端的多个请求?poll

C Linux 多线程 套接字 轮询

评论

1赞 dimich 10/3/2023
为每个事件源 (fd) 分配一个线程,而不是为每个事件分配一个线程。
0赞 nick2225 10/3/2023
@dimich 在有 10,000 个或更多连接的大型情况下怎么办?听起来我需要同时打开 10,000 个线程,这可能会产生一些性能问题。
1赞 dimich 10/3/2023
它与每个事件获取一个线程有何不同?线程数受池容量限制。您应该使用非阻塞套接字,并且仅在返回或 .对于这种情况,epoll 是更方便的机制。recv()EAGAINEWOULDBLOCK
2赞 teapot418 10/3/2023
stackoverflow.com/questions/17593699/......<- 10 年前报告的 Linux 上每个连接 1 个线程的 C10k @ 10 个线程
0赞 nick2225 10/3/2023
@dimich 哦,这是一个有趣的方法。因此,我的数据框架的一部分是我发送 4 个字节,以字节为单位表示即将到来的消息大小。然后我只收到 4 个字节来获得该大小。您是否建议在主线程中将套接字设置为非阻塞,并尝试接收单个“消息”的所有数据,并且只有当我可以收到完整消息时,我才将其放入我的线程池中?如果我无法收到完整的消息(即 将错误设置为 或 ,然后我将文件描述符放入轮询列表中并再次尝试接收完整消息?recvEWOULDBLOCKEAGAIN

答: 暂无答案