I'm in the process of trying to figure out multithreading - I'm pretty new to it. I'm using a thread_pool type that I found here. For sufficiently large N
, the following code segfaults. Could you guys help me understand why and how to fix?
#include "thread_pool.hpp"
#include <thread>
#include <iostream>
static std::mutex mtx;
void printString(const std::string &s) {
std::lock_guard lock(mtx);
std::hash<std::thread::id> tid{};
auto id = tid(std::this_thread::get_id()) % 16;
std::cout << "thread: " << id << " " << s << std::endl;
}
TEST(test, t) {
thread_pool pool(16);
int N = 1000000;
std::vector<std::string> v(N);
for (int i = 0; i < N; i ) {
v[i] = std::to_string(i);
}
for (auto &s: v) {
pool.push_task([&s]() {
printString(s);
});
}
}
Here's the thread sanitizer output (note the ===> comments where I direct you to appropriate line"):
SEGV on unknown address 0x000117fbdee8 (pc 0x000102fa35b6 bp 0x7e8000186b50 sp 0x7e8000186b30 T257195)
0x102fa35b6 std::basic_string::__get_short_size const string:1514
0x102fa3321 std::basic_string::size const string:970
0x102f939e6 std::operator<<<…> ostream:1056
0x102f9380b printString RoadRunnerMapTests.cpp:37 // ==> this line: void printString(const std::string &s) {
0x102fabbd5 $_0::operator() const RoadRunnerMapTests.cpp:49 // ===> this line: v[i] = std::to_string(i);
0x102fabb3d (test_cxx_api_RoadRunnerMapTests:x86_64 0x10001eb3d) type_traits:3694
0x102fabaad std::__invoke_void_return_wrapper::__call<…> __functional_base:348
0x102faba5d std::__function::__alloc_func::operator() functional:1558
0x102fa9669 std::__function::__func::operator() functional:1732
0x102f9d383 std::__function::__value_func::operator() const functional:1885
0x102f9c055 std::function::operator() const functional:2560
0x102f9bc29 thread_pool::worker thread_pool.hpp:389 // ==> [this](https://github.com/bshoshany/thread-pool/blob/master/thread_pool.hpp#L389) line
0x102fa00bc (test_cxx_api_RoadRunnerMapTests:x86_64 0x1000130bc) type_traits:3635
0x102f9ff1e std::__thread_execute<…> thread:286
0x102f9f005 std::__thread_proxy<…> thread:297
0x1033e9a2c __tsan_thread_start_func
0x7fff204828fb _pthread_start
0x7fff2047e442 thread_start
CodePudding user response:
Destructors are called in the order opposite to variable declaration order. i.e. v
will be destructed earlier than pool
, therefore at the moment when some threads from pool will call to printString()
, the argument string will not be a valid object, because v
and its content are already destroyed. To resolve this, I'd recommend to declare v
before pool
.
CodePudding user response:
If you debug this, you will see that the main thread is in thread_pool::~thread_pool() the destructor.
#0 0x00007ffff7a7a3bf in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0, req=0x7fffffffde48, rem=0x7fffffffde48) at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:78
#1 0x00007ffff7a80047 in __GI___nanosleep (requested_time=<optimized out>, remaining=<optimized out>) at nanosleep.c:27
#2 0x000000000040e4ed in std::this_thread::sleep_for<long, std::ratio<1l, 1000000l> > (__rtime=...)
at /ssd/depbuilder-1.1.3/bin/../lib/gcc/x86_64-pc-linux-gnu/11.2.0/../../../../include/c /11.2.0/bits/this_thread_sleep.h:82
#3 0x000000000040de09 in thread_pool::sleep_or_yield (this=0x7fffffffdfb0) at /home/hbucher/git/tests/threadpool/thread_pool.hpp:371
#4 0x000000000040faef in thread_pool::wait_for_tasks (this=0x7fffffffdfb0) at /home/hbucher/git/tests/threadpool/thread_pool.hpp:300
#5 0x000000000040c789 in thread_pool::~thread_pool (this=0x7fffffffdfb0) at /home/hbucher/git/tests/threadpool/thread_pool.hpp:62
#6 0x000000000040b9b9 in test_t_Test::TestBody (this=0x42bfb0) at /home/hbucher/git/tests/threadpool/threadpool.cpp:31
#7 0x00007ffff7fac03d in void testing::internal::HandleExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) ()
from /ssd/depbuilder-1.1.3/lib/libgtest.so
#8 0x00007ffff7fa0b85 in testing::Test::Run() () from /ssd/depbuilder-1.1.3/lib/libgtest.so
#9 0x00007ffff7fa0cc8 in testing::TestInfo::Run() () from /ssd/depbuilder-1.1.3/lib/libgtest.so
#10 0x00007ffff7fa0da5 in testing::TestCase::Run() () from /ssd/depbuilder-1.1.3/lib/libgtest.so
#11 0x00007ffff7fa131c in testing::internal::UnitTestImpl::RunAllTests() () from /ssd/depbuilder-1.1.3/lib/libgtest.so
#12 0x00007ffff7fa1408 in testing::UnitTest::Run() () from /ssd/depbuilder-1.1.3/lib/libgtest.so
#13 0x00007ffff7fc30db in main () from /ssd/depbuilder-1.1.3/lib/libgtest_main.so
#14 0x00007ffff79c10b3 in __libc_start_main (main=0x7ffff7fc30a0 <main>, argc=1, argv=0x7fffffffe368, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe358)
at ../csu/libc-start.c:308
#15 0x000000000040b61e in _start ()
This means that the thread_pool object is being destroyed and the string vector was already destroyed while the threads are still attempting to use it.
If you exchange the position of the variables you will se that the test runs successfully to completion.
TEST(test, t)
{
int N = 1000000;
std::vector<std::string> v(N);
thread_pool pool(16);
for (int i = 0; i < N; i )
{
v[i] = std::to_string(i);
}
for (auto &s : v)
{
pool.push_task([&s]()
{ printString(s); });
}
}
thread: 3 999996
thread: 15 999997
thread: 9 999998
thread: 13 999983
thread: 3 999964
[ OK ] test.t (27346 ms)
[----------] 1 test from test (27346 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (27347 ms total)
[ PASSED ] 1 test.
CodePudding user response:
Tasks passed to thread pool contain references to content of vector v
, however this vector goes out of scope prior to pool leaving tasks with dangling references. In order to fix this you need to reorder scopes of variables:
int N = 1000000;
std::vector<std::string> v(N);
thread_pool pool(16);