I am developing a serial port program using boost::asio.
In synchronous mode I create a thread every time read_sync function is called. All reading related operation are carried in this thread (implementation is in read_sync_impl function).
On close_port
or stop_read
function reading operation is stopped.
This stopped reading operation can be restarted by calling the read_sync
function again.
read_sync
function will never be called successively without calling close_port
or stop_read
function in between.
I wish to know how to implement a class wide std::jthread
along with proper destructor when I call my read_sync function. In languages like Kotlin or Dart the garbage-collector takes care of this. What is C implementation of this.
bool SerialPort::read_sync(std::uint32_t read_length, std::int32_t read_timeout)
{
this->thread_sync_read = std::jthread(&SerialPort::read_sync_impl, this);
return true;
}
bool SerialPort::read_sync_impl(const std::stop_token& st)
{
while(true)
{
...
if (st.stop_requested())
{
PLOG_INFO << "Stop Requested. Exiting thread.";
break;
}
}
}
bool SerialPort::close_port(void)
{
this->thread_sync_read->request_stop();
this->thread_sync_read->join();
this->port.close();
return this->port.is_open();
}
class SerialPort
{
public :
std::jthread *thread_sync_read = nullptr;
...
}
Actual Code
bool SerialPort::read_sync(std::uint32_t read_length, std::int32_t read_timeout)
{
try
{
if (read_timeout not_eq ignore_read_timeout)
this->read_timeout = read_timeout;//If read_timeout is not set to ignore_read_timeout, update the read_timeout else use old read_timeout
if (this->thread_sync_read.joinable())
return false; // Thread is already running
thread_sync_read = std::jthread(&SerialPort::read_sync_impl, this);
return true;
}
catch (const std::exception& ex)
{
PLOG_ERROR << ex.what();
return false;
}
}
void SerialPort::read_sync_impl(const std::stop_token& st)
{
try
{
while (true)
{
if (st.stop_requested())
{
PLOG_INFO << "Stop Requested in SerialPort::read_sync_impl. Exiting thread.";
break;
}
}
}
catch (const std::exception& ex)
{
PLOG_ERROR << ex.what();
}
}
class SerialPort
{
std::jthread thread_sync_read;
SerialPort() : io(), port(io), thread_sync_read()
{
read_buffer.fill(std::byte(0));
write_buffer.fill(std::byte(0));
}
}
CodePudding user response:
You don't need to deal with the jthread
's destructor. A thread object constructed without constructor arguments (default constructor), or one that has been joined, is in an empty state. This can act as a stand-in for your nullptr
.
class SerialPort
{
public :
std::jthread thread_sync_read;
...
SerialPort(...)
: thread_sync_read() // no explicit constructor call needed, just for show
{}
SerialPort(SerialPort&&) = delete; // see side notes below
SerialPort& operator=(SerialPort&&) = delete;
~SerialPort()
{
if(thread_sync_read.joinable())
close_port();
}
bool read_sync(std::uint32_t read_length, std::int32_t read_timeout)
{
if(thread_sync_read.joinable())
return false; // already reading
/* start via lambda to work around parameter resolution
* issues when using member function pointer
*/
thread_sync_read = std::jthread(
[this](const std::stop_token& st) mutable {
return read_sync_impl(st);
}
);
return true;
}
bool close_port()
{
thread_sync_read.request_stop();
thread_sync_read.join(); // after this will be back in empty state
port.close();
return port.is_open();
}
};
Side notes
- Starting and stopping threads is rather expensive. Normally you would want to keep a single worker thread alive and feed it new read/write requests via a work queue or something like that. But there is nothing wrong with using a simpler design like yours, especially when starting and stopping are rare operations
- In the code above I delete the move constructor and assignment operator. The reason is that the thread captures the
this
pointer. Moving theSerialPort
while the thread runs would lead to it accessing a dangling pointer
CodePudding user response:
You're already reinitialize (move new one into) thread_sync_read
in SerialPort::read_sync
, everything should works.
at destructor, you need to remember delete read_sync
SerialPort::~SerialPort(){
close_port(); // if necessary to close port
delete thread_sync_read;
}
or if you declare thread_sync_read
not as (raw) pointer
class SerialPort{
public:
std::jthread thread_sync_read;
}
then you don't need to delete it.
SerialPort::~SerialPort(){
close_port(); // if necessary
}
note that the destructor of std::jthread
would perform necessary request_stop()
and join()
by itself.