C++ Multi Threading

4 minute read

Multi threading & Multiprocessing

Multithreading is an ability of a platform(Operating System, Virtual Machine etc.) or application to create a process that consists of multiple threads of execution (threads). A thread of execution is the smallest sequence of programming instructions that can be managed independently by a scheduler.

In Multicore and Multiprocessor systems Multithreading means that diferent threads are executed at the same time on different cores or processors.

For single core systems multithreading divides the time between the treads. The operating system in turns sends a certain number of instructions from each thread to the processors. Threads are not executed simultaneously. Operating System only simulates their simultaneous execution. This feature of the operating system is called multithreading.

How to create a thread

  1. Include thread header c++ #include <thread>
  2. Create object of thread class c++ thread aThred;
  3. Initialize the thread c++ thread aThread(threadFunc); // pass function to thread
  4. Join threads

Threads joining is done by using join()) member function of thread class This function returns only after all the threads are terminated. It means that the main thread will wait until child thread does not finish its execution. c++ aThread.join(); ## Joinable and not joinable threads

After join() returns, thread becomes not joinable.

A joinable thread is a thread that represents a thread of execution which has not yet been joined.

Thread can be checked if is joinable with joinable() c++ bool joinable() Returns true if thread is joinable and false otherwise.

    //pass function to thread
    thread aThred(thredFunc);

    //check if thread is joinable
    if (threadFunc.joinable())
    {
        //main is blocked until aThread is not finished
        aThread.join();
    }

Detach thread

Threads becomes not joinable after detach() member function is called

    void detach();

This function detaches thread from the parent thread. It allows parent and child threads to be executed independently from each other. After the call to detach() the threads are not synchronized in any way

    aThread.detach();
    if(aThread.joinable())
    {
      //main is blocked until aThread() is not finished
      aThread.join();
    }
    else
    {
        std::cout << "aThread is detached";
    }

Initializing thread with an object

For initializing thread can be used function object (functor) or a member function of a class.

Using functor

A functor is an object of a class that overloads () operator.

If we want to initialize a thread with an object of a class, this class should overload () operator. It can be done like this:

    class myObject
    {
      public:
        void operator()()
        {
          cout << "This is my functor";
        }
    };

Now we could initialize a thread by passing an object of the class myObject to the constructor of the thread

    myObject aObjectFunc;
    thread aThread(aObjectFunc);
    if(aThread.joinable());
      aThread.join();

Using public member function

In order to use public member function of a class, we need to specify the indentifier of this function and pass an object of the class, which defines this member function

    class myObjectClass
    {
    public:
      void operator()()
      {
        cout << "This is need to make a functor";
      }
      void publicFunction()
      {
        cout << "public function of myFunctor class is called"; << endl;
      }
    };

    int main()
    {
      myObjectClass aObject;
      thread functorTest(&myObjectClass::publicFunction, aObject);
      if (functorTest.joinable())
        functorTest.join();
    }

Passing arguments to thread

Initialize with a function to which we can pass arguments


    void printValues( int val, char* std, double dval)
    {
      cout << val << " " << str << " " dval << endl;
    }

    int main()
    {
      char* str ="Hello thread!";

      thread paramThread(printValues, 5, str, 3.2);
      if(paramThread.joinable())
        paramThread.join();
    }

When you want to initialize a thread with an object with parameteres, we have to add corresponding parameter list to the oveloading version of operator ()

  class myObjectClass
  {
    public:
      void operator()(int* arr, int length)
      {
        cout << "An array of length " << length << "is passed to thread" << endl;
        for(int i = 0; i != length; ++i)
          cout << arr[i] << " " endl;
        cout << endl;
      }
  };

  int main()
  {
    int arr[5] = { 1, 2, 3, 4, 5 };
     myObjectClass aObject;
     thread aThred(aObject, arr, 5);
     if(aThread.joinable())
      aTread.join();

    return 0;
  }

Thread ID

Every thread has it’s unique identifier. Class thread has public member function that returns the ID of the thread.

    id get_id();

the returned type is of type id

this_thread Namespace

_ this_thred namespace from thread header offer possibilities to work with current thread. _

  1. id get_id() - return the id of current thread.

  2. template void sleep_until(const chrono::time_point <Clock, Duration>& abs_time) - blocks the current thread until abs_time is not reached.

  3. template void sleep_for(const chrono::duration<Rep,Period>& rel_time); - thread is blocked during time span specified

  4. void yield() - current thread allows implementation to reschedule the execution of thread. It used to avoid blocking.

Deadlock conditions

  • Mutual exclusion
    • Only one thread may use a resource(variable) at a time.
  • Hold and wait
    • A thread may hold allocated resources while awaiting assignment of others
  • No pre-emption
    • No resource an be forcibly removed from a thread holding it.

Dealing with a deadlock

  • Prevent deadlock
    • Design a system in such a way that the possibilities of deadlock is excluded
      • Indirect - prevent all three of the necessary conditions occurring at once
      • Direct - prevent circular wait
  • Avoid deadlock
    • Make a decision and check if deadlock will occur.
  • Detect deadlock
    • limit access to resource and impose restriction on process

Concurrent access to resources

Mutex

  • Only one process at a time is allowed to a critical section when there is no other process using it

  #include<mutex>

  std::mutex mtx;

  mtx.lock();
  /* ciritical resource*/
  mtx.unlock();

Starvation

This is the process when a process/thread is needing a resource which is not available

Spinlock

When multiple processes are waiting to enter the critical resource

Leave a Comment