set_option:在 lambda 中设置选项 boost::asio::ip::multicast::join_group 时参数无效
set_option: Invalid argument when setting option boost::asio::ip::multicast::join_group inside lambda
此代码旨在使用 Boost.Asio 接收 UDP 多播消息。当在接收者的构造函数中进行第二次 set_option() 调用(加入多播组)时,下面的代码会抛出一个 Boost system_error 异常。投诉是"Invalid argument"。这似乎与构造函数出现在 IO::doIO() 内部定义的 lambda 内部这一事实有关,因为使用具有相同功能的 std::thread 成员 (IO::threadFunc()) 反而会导致预期的行为(没有抛出异常)。
为什么会这样,我该如何解决才能使用 lambda?
//g++ -std=c++11 doesntWork.cc -lboost_system -lpthread
#include <iostream>
#include <thread>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
class IO
{
public:
class receiver
{
public:
receiver(
boost::asio::io_service &io_service,
const boost::asio::ip::address &multicast_address,
const unsigned short portNumber) : _socket(io_service)
{
const boost::asio::ip::udp::endpoint listen_endpoint(
boost::asio::ip::address::from_string("0.0.0.0"), portNumber);
_socket.open(listen_endpoint.protocol());
_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
_socket.bind(listen_endpoint);
std::cerr << " About to set option join_group" << std::endl;
_socket.set_option(boost::asio::ip::multicast::join_group(
multicast_address));
_socket.async_receive_from(
boost::asio::buffer(_data),
_sender_endpoint,
boost::bind(&receiver::handle_receive_from, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
private:
void handle_receive_from(
const boost::system::error_code &error,
const size_t bytes_recvd)
{
if (!error)
{
for(const auto &c : _data)
std::cout << c;
std::cout << std::endl;
}
}
private:
boost::asio::ip::udp::socket _socket;
boost::asio::ip::udp::endpoint _sender_endpoint;
std::vector<unsigned char> _data;
}; // receiver class
void doIO()
{
const boost::asio::ip::address multicast_address =
boost::asio::ip::address::from_string("235.0.0.1");
const unsigned short portNumber = 9999;
// _io_service_thread = std::thread(
// &IO::threadFunc, this, multicast_address, portNumber);
_io_service_thread = std::thread([&, this]{
try {
// Construct an asynchronous receiver
receiver r(_io_service, multicast_address, portNumber);
// Now run the IO service
_io_service.run();
}
catch(const boost::system::system_error &e)
{
std::cerr << e.what() << std::endl;
throw e; //std::terminate()
}
});
}
void threadFunc(
const boost::asio::ip::address &multicast_address,
const unsigned short portNumber)
{
try {
// Construct an asynchronous receiver
receiver r(_io_service, multicast_address, portNumber);
// Now run the IO service
_io_service.run();
}
catch(const boost::system::system_error &e)
{
std::cerr << e.what() << std::endl;
throw e; //std::terminate()
}
}
private:
boost::asio::io_service _io_service;
std::thread _io_service_thread;
}; // IO class
int main()
{
IO io;
io.doIO();
std::cout << "IO Service is running" << std::endl;
sleep(9999);
}
存在一种竞争条件,可能导致访问悬空引用,从而调用未定义的行为。 lambda 捕获列表通过引用捕获自动变量 multicast_address
和 portNumber
。但是,这些对象的生命周期可能会在 _io_service_thread
内使用之前结束:
void doIO()
{
const boost::asio::ip::address multicast_address = /* ... */;
const unsigned short portNumber = /* ... */;
_io_service_thread = std::thread([&, this] {
// multicast_address and portNumber's lifetime may have already ended.
receiver r(_io_service, multicast_address, portNumber);
// ...
});
} // multicast_address and portNumber are destroyed.
要解决此问题,请考虑按值捕获,以便线程对副本进行操作,这些副本的生命周期将在线程结束前保持有效。变化:
std::thread([&, this] { /* ... */ }
至:
std::thread([=] { /* ... */ }
当使用函数及其所有参数构造 std::thread
时,不会出现此问题,因为 std::thread
构造函数会将 copy/move 所有提供的参数放入线程可访问的存储中。
此外,请注意 _io_service_thread
对象的销毁将调用 std::terminate()
如果它仍然可以在 IO
的析构函数中加入。为避免这种行为,请考虑从主线程显式加入 _io_service_thread
。
此代码旨在使用 Boost.Asio 接收 UDP 多播消息。当在接收者的构造函数中进行第二次 set_option() 调用(加入多播组)时,下面的代码会抛出一个 Boost system_error 异常。投诉是"Invalid argument"。这似乎与构造函数出现在 IO::doIO() 内部定义的 lambda 内部这一事实有关,因为使用具有相同功能的 std::thread 成员 (IO::threadFunc()) 反而会导致预期的行为(没有抛出异常)。
为什么会这样,我该如何解决才能使用 lambda?
//g++ -std=c++11 doesntWork.cc -lboost_system -lpthread
#include <iostream>
#include <thread>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
class IO
{
public:
class receiver
{
public:
receiver(
boost::asio::io_service &io_service,
const boost::asio::ip::address &multicast_address,
const unsigned short portNumber) : _socket(io_service)
{
const boost::asio::ip::udp::endpoint listen_endpoint(
boost::asio::ip::address::from_string("0.0.0.0"), portNumber);
_socket.open(listen_endpoint.protocol());
_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
_socket.bind(listen_endpoint);
std::cerr << " About to set option join_group" << std::endl;
_socket.set_option(boost::asio::ip::multicast::join_group(
multicast_address));
_socket.async_receive_from(
boost::asio::buffer(_data),
_sender_endpoint,
boost::bind(&receiver::handle_receive_from, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
private:
void handle_receive_from(
const boost::system::error_code &error,
const size_t bytes_recvd)
{
if (!error)
{
for(const auto &c : _data)
std::cout << c;
std::cout << std::endl;
}
}
private:
boost::asio::ip::udp::socket _socket;
boost::asio::ip::udp::endpoint _sender_endpoint;
std::vector<unsigned char> _data;
}; // receiver class
void doIO()
{
const boost::asio::ip::address multicast_address =
boost::asio::ip::address::from_string("235.0.0.1");
const unsigned short portNumber = 9999;
// _io_service_thread = std::thread(
// &IO::threadFunc, this, multicast_address, portNumber);
_io_service_thread = std::thread([&, this]{
try {
// Construct an asynchronous receiver
receiver r(_io_service, multicast_address, portNumber);
// Now run the IO service
_io_service.run();
}
catch(const boost::system::system_error &e)
{
std::cerr << e.what() << std::endl;
throw e; //std::terminate()
}
});
}
void threadFunc(
const boost::asio::ip::address &multicast_address,
const unsigned short portNumber)
{
try {
// Construct an asynchronous receiver
receiver r(_io_service, multicast_address, portNumber);
// Now run the IO service
_io_service.run();
}
catch(const boost::system::system_error &e)
{
std::cerr << e.what() << std::endl;
throw e; //std::terminate()
}
}
private:
boost::asio::io_service _io_service;
std::thread _io_service_thread;
}; // IO class
int main()
{
IO io;
io.doIO();
std::cout << "IO Service is running" << std::endl;
sleep(9999);
}
存在一种竞争条件,可能导致访问悬空引用,从而调用未定义的行为。 lambda 捕获列表通过引用捕获自动变量 multicast_address
和 portNumber
。但是,这些对象的生命周期可能会在 _io_service_thread
内使用之前结束:
void doIO()
{
const boost::asio::ip::address multicast_address = /* ... */;
const unsigned short portNumber = /* ... */;
_io_service_thread = std::thread([&, this] {
// multicast_address and portNumber's lifetime may have already ended.
receiver r(_io_service, multicast_address, portNumber);
// ...
});
} // multicast_address and portNumber are destroyed.
要解决此问题,请考虑按值捕获,以便线程对副本进行操作,这些副本的生命周期将在线程结束前保持有效。变化:
std::thread([&, this] { /* ... */ }
至:
std::thread([=] { /* ... */ }
当使用函数及其所有参数构造 std::thread
时,不会出现此问题,因为 std::thread
构造函数会将 copy/move 所有提供的参数放入线程可访问的存储中。
此外,请注意 _io_service_thread
对象的销毁将调用 std::terminate()
如果它仍然可以在 IO
的析构函数中加入。为避免这种行为,请考虑从主线程显式加入 _io_service_thread
。