Search code examples
c++multithreadinganimationstdthread

Multithreading in C++ - Display animation until another thread has completed


Preface: this is my first attempt at writing a program in any language with multi-threading. I have no prior experience with std::thread, or with describing multi-threaded programs at all. Please let me know if you need more information to answer this question, or if I can reword anything to make my question clearer.

Suppose I have a simple animation function animate, which (for now) runs indefinitely; and another function task, which represents some arbitrary code to execute.

#include <string>

// a simple rotating stick animation: | -> / -> - -> \

void animate()
{
  std::string syms = "|/-\\";
  while (true)
  {
    for (unsigned int ii = 0; ii < syms.length(); ++ii)
    {
      std::cout << syms[ii];
      std::cout << std::string(1, '\b');
    }
  }
}

// arbitrary function

void task()
{
  // code to do something else goes here
}

int main (int argc, char* argv[])
{
  // execute task and animate at the same time;
  // return from animate once task has completed
  return 0;
}

How can I use std::thread (or some other header/library) to allow task and animate to communicate with each other, such that task runs in the "background" and animate runs until task has completed?


Solution

  • I've taken your example code and tried to fill in the blanks from your intention with std::thread. I've added some comments inline to explain what's going on. If something's not clear, or I got it wrong, feel free to ask in the comments.

    I want to stress though that this example only uses std::thread to create and join a separate thread in a toy example. It does not synchronize shared data between threads in a correct way. Shared data in MT environments is where things get hairy and the worst thing is that it can create some of the hardest to debug bugs out there. That's why the books and working examples are important for any substantial multithreaded program.

    #include <string>
    #include <thread>
    #include <chrono>
    #include <iostream>
    
    // a simple rotating stick animation: | -> / -> - -> \
    
    //
    void animate(bool * should_animate)
    {
      // Using this namespace lets us use the "100ms" time literal in the sleep_for() function argument.
      using namespace std::chrono_literals;
    
      std::string syms = "|/-\\";
      while (*should_animate)
      {
        for (unsigned int ii = 0; ii < syms.length(); ++ii)
        {
          std::cout << syms[ii];
          std::cout << std::string(1, '\b');
    
          // I had to flush cout so that the animation would show up
          std::cout.flush();
          // I added some delay here so that the animation is more visible
          std::this_thread::sleep_for(100ms);
        }
      }
    }
    
    // arbitrary function
    
    void task()
    {
      using namespace std::chrono_literals;
      // I have this thread waiting for 2 seconds to simulate a lot of work being done
      std::this_thread::sleep_for(2s);
    }
    
    int main (int argc, char* argv[])
    {
    
      // This line creates a thread from the animate() function
      // The std::thread constructor is flexible, in that it can take a function with any number and type of arguments and
      // make a thread out of it
      // Once the thread is created, it gets sent to the operating system for scheduling right away in a separate thread
      // I'm passing in a pointer to `should_animate` to use that value as a basic way to communicate to the animate thread to signal when it should stop running.
      // I'm doing this to keep the example simple, but I stress that this isn't a good idea for larger scale programs
      // Better to use propper signals or event queues
      bool should_animate = true;
      std::thread thread_animate(animate, &should_animate);
    
      // This line creates a thread for the worker task
      // That separate thread can be referenced by tis std::thread object
      // Once the functions finish running, the thread associated with it is "joined"
      std::thread thread_task(task);
    
      // 'join' pauses the main thread to wait for the associated thread to finish running in the background.
      thread_task.join();
    
      // By this point in the program, the `task()` function has finished running, so we can flag
      // the animate task to finish running so its thread can be joined
      should_animate = false;
      // Wait for the animate thread to get the message and finish
      thread_animate.join();
    
    
      std::cout << "Done!" << std::endl;
      return 0;
    }
    

    If you want to take it a little further, here's some links I'd recommend.

    • This was the best tutorial I could find in the first page of google (most results seemed bad). Seems like a good jumping off point https://solarianprogrammer.com/2011/12/16/cpp-11-thread-tutorial/
    • cppreference is the best C++ reference site I know of, and I usually have at least one tab on it all day. Because it is reference, its difficult to dig straight into it. Each section header of this page covers one multithreaded topics. "Threads" and "Mutual Exclusion" are the most common things uses in MT. https://en.cppreference.com/w/cpp/thread