I'm passing a std::sub_match
as an argument to a std::thread
(see my example code below). The thread function expects a const string reference. A sub_match can be converted to a string. So everything compiles fine.
But sometimes the function receives the wrong string. When I convert the sub_match to a string before passing it to the thread it works as expected. What is the difference?
I think this is a race condition as the original sub_match may not exist anymore when the thread executes. But I thought arguments to threads would be copied anyway. How can I find out which arguments are safe to pass to a thread and which not?
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <regex>
#include <unistd.h>
class test_t {
public:
test_t(void) {}
~test_t(void) {}
void start(void){
//-------------------------------------------------
// Do some memory allocation.
// The error seems to appear faster with that.
std::vector<std::string> vec;
for(unsigned int i = 0; i < 1000; ++i) {
vec.push_back("test_test_test");
}
//-------------------------------------------------
std::string event = "operating";
std::smatch match;
std::regex expr("\\(operating\\)",
std::regex_constants::icase |
std::regex_constants::basic);
if(std::regex_match(event, match, expr)) {
std::cout << "start thread" << std::endl;
m_thread = std::thread(&test_t::thread_func, this, match[1]); //NOK
// m_thread = std::thread(&test_t::thread_func, this, match[1].str()); // OK
// m_thread = std::thread(&test_t::thread_func, this, (std::string)match[1]); // OK
m_thread.detach();
std::cout << "thread started" << std::endl;
}
}
private:
std::thread m_thread;
void thread_func(const std::string& string) {
if(string != "operating") {
std::cout << "ERROR: string: \"" << string << "\"" << std::endl;
exit(EXIT_FAILURE);
} else {
std::cout << "string: \"" << string << "\"" << std::endl;
}
}
};
int main(int argc, char** argv) {
test_t test;
while(1) {
test.start();
usleep(100);
}
return 0;
}
Compilation Message:
Compiled with: g++ --std=c++11 -pthread -o test main.cpp
g++ --version: g++ (SUSE Linux) 4.8.5
Expected output:
start thread
thread started
string: "operating"
(repeat)
Actual output:
start thread
thread started
string: "operating"
ERROR: string: "test_test"
operator[]
for std::smatch
returns sub_match
which can be treated as pair of iterators to matched characters.
After regex_match
was called, you can use operator[]
to access sub-matches as long as event
exists. When event
is deleted (you are not joining your thread, so start
returns immediately and event
is destroyed), sub-matches have dangling pointers and should not be accessed.
m_thread = std::thread(&test_t::thread_func, this, match[1]);
this doesn't work because when function goes out of scope, event is deleted and sub-match has dangling pointers.
m_thread = std::thread(&test_t::thread_func, this, match[1].str());
this works because str()
returns copy of matched string.
m_thread = std::thread(&test_t::thread_func, this, (std::string)match[1]);
this also works because temporary string is created based on sub-match match[1]
, and temp is passed into thread.
From some docs:
Because
std::match_results
holdsstd::sub_matches
, each of which is a pair of iterators into the original character sequence that was matched, it's undefined behavior to examinestd::match_results
if the original character sequence was destroyed or iterators to it were invalidated for other reasons.
… and the same page teaches us that std::smatch
is an alias for std::match_results<std::string::const_iterator>
.
You'll need to take a copy of the character range referred to by these iterators, and pass that to std::thread
.
It is true that thread_func
will already be doing this copy during argument conversion (since the function takes a const std::string&
, not a std::sub_match
), but this occurs on the thread by which point it's too late, as your pointers are already [potentially] dangling.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With