Search code examples
c++multithreadingc++11vectorvisual-studio-2015

std::function in combination with thread c++11 fails debug assertion in vector


I want to build a helper class that can accept a std::function created via std::bind) so that I can call this class repeated from another thread:

Short example:

void loopme() {
   std::cout << "yay";
}

main () {
   LoopThread loop = { std::bind(&loopme) };
   loop.start();
   //wait 1 second
   loop.stop();
   //be happy about output
}

However, when calling stop() my current implementation will raise the following error: debug assertion Failed

debug assertion Failed , see Image.

Does anyone know why the error is thrown?
I don't even use vectors in this example. When I don't call loopme from within the thread but directly output to std::cout, no error is thrown.

Here is the full implementation of my class:

class LoopThread {
public:
   LoopThread(std::function<void(LoopThread*, uint32_t)> function) : function_{ function }, thread_{ nullptr }, is_running_{ false }, counter_{ 0 } {};
   ~LoopThread();
   void start();
   void stop();
   bool isRunning() { return is_running_; };
private:
   std::function<void(LoopThread*, uint32_t)> function_;
   std::thread* thread_;
   bool is_running_;
   uint32_t counter_;
   void executeLoop();
};

 LoopThread::~LoopThread() {
   if (isRunning()) {
      stop();
   }
}

void LoopThread::start() {
   if (is_running_) {
      throw std::runtime_error("Thread is already running");
   }
   if (thread_ != nullptr) {
      throw std::runtime_error("Thread is not stopped yet");
   }
   is_running_ = true;
   thread_ = new std::thread{ &LoopThread::executeLoop, this };
}

void LoopThread::stop() {
   if (!is_running_) {
      throw std::runtime_error("Thread is already stopped");
   }
   is_running_ = false;
   thread_->detach();
}

void LoopThread::executeLoop() {
   while (is_running_) {
      function_(this, counter_);
      ++counter_;
   }
   if (!is_running_) {
      std::cout << "end";
   }
   //delete thread_;
   //thread_ = nullptr;
}

I used the following Googletest code for testing (however a simple main method containing the code should work):

void testfunction(pft::LoopThread*, uint32_t i) {
  std::cout << i << ' ';
}

TEST(pfFiles, TestLoop)
{
  pft::LoopThread loop{ std::bind(&testfunction, std::placeholders::_1, std::placeholders::_2) };
  loop.start();

  std::this_thread::sleep_for(std::chrono::milliseconds(500));

  loop.stop();
  std::this_thread::sleep_for(std::chrono::milliseconds(2500));
  std::cout << "Why does this fail";
}


Solution

  • Your use of is_running_ is undefined behavior, because you write in one thread and read in another without a synchronization barrier.

    Partly due to this, your stop() doesn't stop anything. Even without this UB (ie, you "fix" it by using an atomic), it just tries to say "oy, stop at some point", by the end it does not even attempt to guarantee the stop happened.

    Your code calls new needlessly. There is no reason to use a std::thread* here.

    Your code violates the rule of 5. You wrote a destructor, then neglected copy/move operations. It is ridiculously fragile.

    As stop() does nothing of consequence to stop a thread, your thread with a pointer to this outlives your LoopThread object. LoopThread goes out of scope, destroying what the pointer your std::thread stores. The still running executeLoop invokes a std::function that has been destroyed, then increments a counter to invalid memory (possibly on the stack where another variable has been created).

    Roughly, there is 1 fundamental error in using std threading in every 3-5 lines of your code (not counting interface declarations).

    Beyond the technical errors, the design is wrong as well; using detach is almost always a horrible idea; unless you have a promise you make ready at thread exit and then wait on the completion of that promise somewhere, doing that and getting anything like a clean and dependable shutdown of your program is next to impossible.

    As a guess, the vector error is because you are stomping all over stack memory and following nearly invalid pointers to find functions to execute. The test system either puts an array index in the spot you are trashing and then the debug vector catches that it is out of bounds, or a function pointer that half-makes sense for your std function execution to run, or somesuch.


    Only communicate through synchronized data between threads. That means atomic data, or mutex guarded, unless you are getting ridiculously fancy. You don't understand threading enough to get fancy. You don't understand threading enough to copy someone who got fancy and properly use it. Don't get fancy.

    Don't use new. Almost never, ever use new. Use make_shared or make_unique if you absolutely have to. But use those rarely.

    Don't detach a thread. Period. Yes this means you might have to wait for it to finish a loop or somesuch. Deal with it, or write a thread manager that does the waiting at shutdown or somesuch.

    Be extremely clear about what data is owned by what thread. Be extremely clear about when a thread is finished with data. Avoid using data shared between threads; communicate by passing values (or pointers to immutable shared data), and get information from std::futures back.


    There are a number of hurdles in learning how to program. If you have gotten this far, you have passed a few. But you probably know people who learned along side of you that fell over at one of the earlier hurdles.

    • Sequence, that things happen one after another.

    • Flow control.

    • Subprocedures and functions.

    • Looping.

    • Recursion.

    • Pointers/references and dynamic vs automatic allocation.

    • Dynamic lifetime management.

    • Objects and Dynamic dispatch.

    • Complexity

    • Coordinate spaces

    • Message

    • Threading and Concurrency

    • Non-uniform address spaces, Serialization and Networking

    • Functional programming, meta functions, currying, partial application, Monads

    This list is not complete.

    The point is, each of these hurdles can cause you to crash and fail as a programmer, and getting each of these hurdles right is hard.

    Threading is hard. Do it the easy way. Dynamic lifetime management is hard. Do it the easy way. In both cases, extremely smart people have mastered the "manual" way to do it, and the result is programs that exhibit random unpredictable/undefined behavior and crash a lot. Muddling through manual resource allocation and deallocation and multithreaded code can be made to work, but the result is usually someone whose small programs work accidentally (they work insofar as you fixed the bugs you noticed). And when you master it, initial mastery comes in the form of holding an entire program's "state" in uour head and understanding how it works; this fails to scale to large many-developer code bases, so younusually graduate to having large programs that work accidentally.

    Both make_unique style and only-immutable-shared-data based threading are composible strategies. This means if small pieces are correct, and you put them together, the resulting program is correct (with regards to resource lifetime and concurrency). That permits local mastery of small-scale threading or resource management to apply to larfe-scale programs in the domain that these strategies work.