如何避免在 C++11 服务器程序中为多个客户端使用多线程
How to avoid using multiple threads for multiple clients in a C++11 server program
我在一次采访中被问到这个问题,我的回答是避免使用多线程并使用 "cooperative multitasking"(单进程)。我真的很想知道我应该如何在 C++ 跨平台服务器中有效地处理多个客户端,而无需为每个客户端创建一个线程。 C++11 是否提供了一些在该上下文中有用的工具?
我最好的建议是不要自己做,除非你真的想做(即为了你的知识或学习)。
有各种图书馆可以完成这项任务,
boost.asio 是其中之一。
为了真正避免使用多线程,无论是你自己还是通过像 Boost.Asio 这样的库间接使用,我会使用 poll/select/epoll(选择你的颜色)来监视服务器侦听套接字以及所有客户端套接字(s).默认情况下,您的进程可以在没有超时的情况下坐在那里,等待套接字事件发生。
然后这取决于您的服务器进程是否自己完成所有工作,那么您可能需要找到一种方法将 large/long-processing 请求分成较小的块,以便为其他客户端提供 chance/time 插槽。然后你建立这些处理块的队列,检查非等待套接字,然后再次处理一个块,直到块队列再次为空。
如果完成繁重的工作,例如通过数据库,将请求发送到数据库,检查套接字,检查来自数据库服务器的回复等,直到请求被完全处理。
通常情况下,您这样做的方式是异步处理请求,即通过回调函数,然后让这些回调函数排队,这样它们就不会发生冲突。
同步调用与异步调用
同步和异步函数调用有什么区别?
同步调用: 调用一个函数并让它立即执行它的工作。同一个线程执行调用,调用阻塞直到函数 finishes/returns;因此:在它完成之前,什么都做不了。这是您在每个普通程序中通常会做的事情。
异步调用:将函数调用放入队列,然后可以从同一个或另一个线程调用该函数。
通常,对于 client/server 应用程序,您永远不会使用同步调用来处理传输的数据。一旦你消化了这一点,你就会明白多线程在那里是如何工作的,一切都会变得容易!
异步调用如何在 server/client 程序上工作?
例如,在Boost ASIO, you can set a function to be called when the server/client receives data。基本上,您所做的就是告诉 Boost Library:如果我的服务器接收到数据(比如 std::string buffer
),我希望您调用此函数来处理此数据。
(请注意,同步替代方案是您调用并等待服务器接收到某些内容,线程完全阻塞直到接收到某些内容。这一点都不方便!这就是同步调用不可用的原因好主意)。
如何开始异步编程?
Boost ASIO 提供了classio_service
,它基本上是ASIO 异步调用的处理程序。这是一个例子:
void handle_async_receive(...) { ... }
void print() {
std::cout<<"Hello!"<<std::endl;
}
int main()
{
//some stuff
io_service.post(&print);
socket.connect(endpoint); //this is synchronous, so it connects and returns after the connection process is finished
socket.async_receive(buffer, &handle_async_receive);
io_service.post(&print);
io_service.run(); //this will block until the io_service queue is empty
}
当您 post
时,所有打印调用都不会发生。您只需将它们放在队列中,当您执行 run()
和 io_service
.
时它们就会被执行
多线程呢?
对于多线程,Boost ASIO 中有一个名为 strand
的解决方案。在将任何函数发布到 io_service
之前,您所要做的就是用链将其发布到 "wrap"。一个 strand 只是将函数调用排队,即使它们在多个线程上也是如此。因此,它是一种非阻塞解决方案,与互斥量不同。
我在一次采访中被问到这个问题,我的回答是避免使用多线程并使用 "cooperative multitasking"(单进程)。我真的很想知道我应该如何在 C++ 跨平台服务器中有效地处理多个客户端,而无需为每个客户端创建一个线程。 C++11 是否提供了一些在该上下文中有用的工具?
我最好的建议是不要自己做,除非你真的想做(即为了你的知识或学习)。
有各种图书馆可以完成这项任务,
boost.asio 是其中之一。
为了真正避免使用多线程,无论是你自己还是通过像 Boost.Asio 这样的库间接使用,我会使用 poll/select/epoll(选择你的颜色)来监视服务器侦听套接字以及所有客户端套接字(s).默认情况下,您的进程可以在没有超时的情况下坐在那里,等待套接字事件发生。
然后这取决于您的服务器进程是否自己完成所有工作,那么您可能需要找到一种方法将 large/long-processing 请求分成较小的块,以便为其他客户端提供 chance/time 插槽。然后你建立这些处理块的队列,检查非等待套接字,然后再次处理一个块,直到块队列再次为空。
如果完成繁重的工作,例如通过数据库,将请求发送到数据库,检查套接字,检查来自数据库服务器的回复等,直到请求被完全处理。
通常情况下,您这样做的方式是异步处理请求,即通过回调函数,然后让这些回调函数排队,这样它们就不会发生冲突。
同步调用与异步调用
同步和异步函数调用有什么区别?
同步调用: 调用一个函数并让它立即执行它的工作。同一个线程执行调用,调用阻塞直到函数 finishes/returns;因此:在它完成之前,什么都做不了。这是您在每个普通程序中通常会做的事情。
异步调用:将函数调用放入队列,然后可以从同一个或另一个线程调用该函数。
通常,对于 client/server 应用程序,您永远不会使用同步调用来处理传输的数据。一旦你消化了这一点,你就会明白多线程在那里是如何工作的,一切都会变得容易!
异步调用如何在 server/client 程序上工作?
例如,在Boost ASIO, you can set a function to be called when the server/client receives data。基本上,您所做的就是告诉 Boost Library:如果我的服务器接收到数据(比如 std::string buffer
),我希望您调用此函数来处理此数据。
(请注意,同步替代方案是您调用并等待服务器接收到某些内容,线程完全阻塞直到接收到某些内容。这一点都不方便!这就是同步调用不可用的原因好主意)。
如何开始异步编程?
Boost ASIO 提供了classio_service
,它基本上是ASIO 异步调用的处理程序。这是一个例子:
void handle_async_receive(...) { ... }
void print() {
std::cout<<"Hello!"<<std::endl;
}
int main()
{
//some stuff
io_service.post(&print);
socket.connect(endpoint); //this is synchronous, so it connects and returns after the connection process is finished
socket.async_receive(buffer, &handle_async_receive);
io_service.post(&print);
io_service.run(); //this will block until the io_service queue is empty
}
当您 post
时,所有打印调用都不会发生。您只需将它们放在队列中,当您执行 run()
和 io_service
.
多线程呢?
对于多线程,Boost ASIO 中有一个名为 strand
的解决方案。在将任何函数发布到 io_service
之前,您所要做的就是用链将其发布到 "wrap"。一个 strand 只是将函数调用排队,即使它们在多个线程上也是如此。因此,它是一种非阻塞解决方案,与互斥量不同。