在多线程程序中,
io_service::notify_fork()在子级中调用是不安全的。但是,Boost.Asio希望基于
fork()support来调用它,因为这是孩子关闭父级以前的内部文件描述符并创建新的文件描述符时。尽管Boost.Asio明确列出了调用的前提条件
io_service::notify_fork(),并在期间保证了其内部组件的状态,但
fork()对实现的简短了解表明
std::vector::push_back()可以从空闲存储区分配内存,并且不能保证分配是异步信号-
安全。
话虽如此,一种可能值得考虑的解决方案
fork()是单线程时的进程。子进程将保持单线程并执行,
fork()并且
exec()在父进程通过进程间通信告知执行该操作时。这种分离通过消除在执行
fork()和时管理多个线程的状态的需要,简化了问题
exec()。
这是一个演示此方法的完整示例,其中多线程服务器将通过UDP接收文件名,并且子进程将执行
fork()并在文件名
exec()上运行
/usr/bin/touch。为了使该示例更具可读性,我选择使用堆栈式协程。
#include <unistd.h> // execl, fork#include <iostream>#include <string>#include <boost/bind.hpp>#include <boost/asio.hpp>#include <boost/asio/spawn.hpp>#include <boost/make_shared.hpp>#include <boost/shared_ptr.hpp>#include <boost/thread.hpp>/// @brief launcher receives a command from inter-process communication,/// and will then fork, allowing the child process to return to/// the caller.class launcher{public: launcher(boost::asio::io_service& io_service,boost::asio::local::datagram_protocol::socket& socket,std::string& command) : io_service_(io_service), socket_(socket), command_(command) {} void operator()(boost::asio::yield_context yield) { std::vector<char> buffer; while (command_.empty()) { // Wait for server to write data. std::cout << "launcher is waiting for data" << std::endl; socket_.async_receive(boost::asio::null_buffers(), yield); // Resize buffer and read all data. buffer.resize(socket_.available()); socket_.receive(boost::asio::buffer(buffer)); io_service_.notify_fork(boost::asio::io_service::fork_prepare); if (fork() == 0) // child { io_service_.notify_fork(boost::asio::io_service::fork_child); command_.assign(buffer.begin(), buffer.end()); } else // parent { io_service_.notify_fork(boost::asio::io_service::fork_parent); } } }private: boost::asio::io_service& io_service_; boost::asio::local::datagram_protocol::socket& socket_; std::string& command_;};using boost::asio::ip::udp;/// @brief server reads filenames from UDP and then uses/// inter-process communication to delegate forking and exec/// to the child launcher process.class server{public: server(boost::asio::io_service& io_service, boost::asio::local::datagram_protocol::socket& socket, short port) : io_service_(io_service), launcher_socket_(socket), socket_(boost::make_shared<udp::socket>( boost::ref(io_service), udp::endpoint(udp::v4(), port))) {} void operator()(boost::asio::yield_context yield) { udp::endpoint sender_endpoint; std::vector<char> buffer; for (;;) { std::cout << "server is waiting for data" << std::endl; // Wait for data to become available. socket_->async_receive_from(boost::asio::null_buffers(), sender_endpoint, yield); // Resize buffer and read all data. buffer.resize(socket_->available()); socket_->receive_from(boost::asio::buffer(buffer), sender_endpoint); std::cout << "server got data: "; std::cout.write(&buffer[0], buffer.size()); std::cout << std::endl; // Write filename to launcher. launcher_socket_.async_send(boost::asio::buffer(buffer), yield); } }private: boost::asio::io_service& io_service_; boost::asio::local::datagram_protocol::socket& launcher_socket_; // To be used as a coroutine, server must be copyable, so make socket_ // copyable. boost::shared_ptr<udp::socket> socket_;};int main(int argc, char* argv[]){ std::string filename; // Try/catch provides exception handling, but also allows for the lifetime // of the io_service and its IO objects to be controlled. try { if (argc != 2) { std::cerr << "Usage: <port>n"; return 1; } boost::thread_group threads; boost::asio::io_service io_service; // Create two connected sockets for inter-process communication. boost::asio::local::datagram_protocol::socket parent_socket(io_service); boost::asio::local::datagram_protocol::socket child_socket(io_service); boost::asio::local::connect_pair(parent_socket, child_socket); io_service.notify_fork(boost::asio::io_service::fork_prepare); if (fork() == 0) // child { io_service.notify_fork(boost::asio::io_service::fork_child); parent_socket.close(); boost::asio::spawn(io_service, launcher(io_service, child_socket, filename)); } else // parent { io_service.notify_fork(boost::asio::io_service::fork_parent); child_socket.close(); boost::asio::spawn(io_service,server(io_service, parent_socket, std::atoi(argv[1]))); // Spawn additional threads. for (std::size_t i = 0; i < 3; ++i) { threads.create_thread( boost::bind(&boost::asio::io_service::run, &io_service)); } } io_service.run(); threads.join_all(); } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << "n"; } // Now that the io_service and IO objects have been destroyed, all internal // Boost.Asio file descriptors have been closed, so the execl should be // in a clean state. If the filename has been set, then exec touch. if (!filename.empty()) { std::cout << "creating file: " << filename << std::endl; execl("/usr/bin/touch", "touch", filename.c_str(), static_cast<char*>(0)); }}1号航站楼:
$ lsa.out example.cpp$ ./a.out 12345服务器正在等待数据启动器正在等待数据服务器获取数据:服务器正在等待数据启动器正在等待数据创建文件:服务器获取数据:b服务器正在等待数据启动器正在等待数据创建文件:b服务器获取数据:c服务器正在等待数据启动器正在等待数据创建文件:cctrl + c$ lsa.out bc example.cpp
2号航站楼:
$ nc -u 127.0.0.1 12345a ctrl + db ctrl + dcctrl + d



