C++ 使用fork()和boost::asio的进程池

C++ 使用fork()和boost::asio的进程池,c++,asynchronous,process,fork,boost-asio,C++,Asynchronous,Process,Fork,Boost Asio,我目前正在尝试实现一个可以由父进程与之通信的进程池。在父进程通知子进程之前(可能使用信号),子进程不应退出。现在,我脑海中出现了几个问题,我很高兴能通过我的MWE获得一些信息: #include <iostream> #include <boost/thread.hpp> #include <boost/process/async_pipe.hpp> #include <boost/asio.hpp> #include <boost/arr

我目前正在尝试实现一个可以由父进程与之通信的进程池。在父进程通知子进程之前(可能使用信号),子进程不应退出。现在,我脑海中出现了几个问题,我很高兴能通过我的MWE获得一些信息:

#include <iostream>

#include <boost/thread.hpp>
#include <boost/process/async_pipe.hpp>
#include <boost/asio.hpp>
#include <boost/array.hpp>

static const std::size_t process_count = 3;

static void start_reading(boost::process::async_pipe& ap) 
{
    static boost::array<char, 256> buf;

    ap.async_read_some(boost::asio::buffer(buf.data(), buf.size()), [](const boost::system::error_code& error, std::size_t bytes_transferred)
    {
        if(!error)
        {
            std::cout << "received " << bytes_transferred << " from pid " << getpid() << " " << buf[0] << "...." << std::endl;
            // perform some heavy computations here..
        }
    });
}

static void start_writing(boost::process::async_pipe& ap)
{
    boost::array<char, 256> buf;
    buf.fill('A');

    ap.async_write_some(boost::asio::buffer(buf.data(), buf.size()), [&ap](const boost::system::error_code& error, std::size_t bytes_transferred)
    {
        if(!error)
        {
            std::cout << "parent " << getpid() << " sent " << bytes_transferred << " to [" << ap.native_source() << "," << ap.native_sink() << "]" << std::endl;
        }
    });
}

int main()
{
    try
    {        
        boost::asio::io_service io_context;

        // prevent the associated executor from stopping
        boost::asio::executor_work_guard<boost::asio::io_context::executor_type> guard = boost::asio::make_work_guard(io_context);

        pid_t main_process = getpid();
        std::cout << "before forks " << main_process << std::endl;

        std::vector<boost::process::async_pipe> pipes;
        pipes.reserve(process_count);

        for(std::size_t i = 0; i < process_count; i++)
        {
            pipes.emplace_back(io_context);
         
            io_context.notify_fork(boost::asio::io_service::fork_prepare);
            pid_t pid = fork();

            if(pid == 0)
            {
                io_context.notify_fork(boost::asio::io_service::fork_child);
                
                // perform some costly initialization here...

                boost::process::async_pipe& ap = pipes[i];
                
                std::cout << "child " << getpid() << " listening to [" << ap.native_source() << "," << ap.native_sink() << "]" << std::endl;
         
                start_reading(ap);

                io_context.run();               
            }
            else if(pid > 0)
            {
                io_context.notify_fork(boost::asio::io_service::fork_parent);
            }
            else
            {
                std::cerr << "fork() failed" << std::endl;
            }            
        }

        // only parent gets there
        start_writing(pipes[0]);
        start_writing(pipes[0]);
        start_writing(pipes[1]);
        start_writing(pipes[2]);

        io_context.run();
        
    }
    catch(const std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }
    
    return 1;
}
我当时主要关心的是如何无限地读取工作进程(child)中的数据,只要该进程还不忙。一旦worker从async_read_some进入处理程序,它就会执行注释中所述的一些计算(可能需要几秒钟)。在执行此操作时,进程应该也将被阻止,之后我想通知我的家长再次准备好并通过管道接受新的读取。到目前为止,我还不知道该怎么做。从子进程通知父进程本身并不是必需的,但是父进程需要随时跟踪所有空闲的子进程,以便它可以通过相应的管道发送新的输入

除此之外,还有一件事我还没有得到: 请注意,
boost::array buf
start\u reading
中是
static
。如果我删除了静态修饰符,我就永远不会进入
async\u read\u some
的完成处理程序,这是为什么

编辑: 在完成例程中再次调用
start\u reading
,将继续读取。但是,没有父进程“知道”它

编辑2: 直到现在,我才想出了一个可行的方法(我想有几种方法)。我还没有完成实现,但共享互斥体的工作与预期一样。下面是一些伪代码:

process_pool
{

    worker get_next_worker()
        ScopedLock(memory_mapped_mutex);
        free_worker = *available.rbegin()
        available.pop_back()
        return free_worker;

    memory_mapped_vec<worker> available;

};

server::completion_handler_async_connect()
    get_next_worker().socket().write(request)


worker::completion_handler_async_read()
   // do something very long before locking
   ScopedLock(memory_mapped_mutex);
   process_pool::available.push_back(self);
process\u池
{
工人得到下一个工人()
ScopedLock(内存映射互斥);
free_worker=*可用。rbegin()
可用。pop_back()
免费返回工作人员;
内存映射向量可用;
};
服务器::完成\u处理程序\u异步\u连接()
获取下一个工作者().socket().write(请求)
worker::完成\u处理程序\u异步\u读取()
//在锁定前很久做一些事情
ScopedLock(内存映射互斥);
进程池::可用。推回(自身);
除此之外,还有一件事我还没有得到:注意boost::array buf;在开始读取时是静态的。如果我删除了静态修饰符,我就永远不会进入async\u read\u some的完成处理程序,这是为什么

这是因为
buf
是一个本地变量,它在
start\u reading
退出后不存在。但是,
async\u read
(或任何其他
async\u XXXX
调用)会立即返回,而无需等待操作完成。因此,如果缓冲区没有持续存在,那么您正在将对未指定堆栈空间的悬空引用写入到缓冲区中,从而导致

至于来回通信,这在进程之间是不必要的复杂。有什么原因不能使用多线程吗?这样,所有工作人员都可以简单地监视共享队列


当然,您可以使用进程之间共享的队列来设置相同的队列(在这种情况下,我建议不要使用Asio通过管道执行,而是使用。

感谢您首先指出了buf的问题,直到现在我仍然无法解决它。就线程而言,我确实需要使用进程,因为执行的请求可能会崩溃,服务器在这种情况下不能终止。最终这将导致创建一个tcp/ip服务器,并为每个接受计划一个预分叉()进程。我正在使用“真实”服务器中已经存在的io_服务对象,这会干扰消息队列吗?我到目前为止还没有看过它。我自己也有一个想法,并将更新我的帖子。你觉得这个怎么样?
process_pool
{

    worker get_next_worker()
        ScopedLock(memory_mapped_mutex);
        free_worker = *available.rbegin()
        available.pop_back()
        return free_worker;

    memory_mapped_vec<worker> available;

};

server::completion_handler_async_connect()
    get_next_worker().socket().write(request)


worker::completion_handler_async_read()
   // do something very long before locking
   ScopedLock(memory_mapped_mutex);
   process_pool::available.push_back(self);