Skip to main content

all about multithreading in c++ and how to use it in a better way (c++11 and c++17)



THREADING IN C++



before going to threading we need to check out some concepts of thread synchronization in modern computers.

  1. thread
  2. deadlock
  3. race condition
  4. spin lock
  5. context switching
  6. parallelism 
  7. concurrency
  8. semaphore
  9. mutex
  10. Scheduling

    Cancellation

    Synchronization




These day we have multi core processor thats mean we can run multiple operation at same time using multi core functionality. so fully support multithreading where you can distribute your complex task to among different threads and after finishing each task they can join main thread. In most of cases graphics manipulation handle by main thread so different child thread can accomplish the operation given to them and than get back to main thread.  

so in c++ we required #include <thread> header file to access threading in c++

so let start with a  very simple program this function takes two parameter and return back sum of both the parameter

int addNumber(int x,int y)

{

    return x+y;

};

int main(int argc, const char * argv[]) {

    //Function can be stored as a variable in c++

     //inside my

    cout<<"the sum is"<<addNumber(10, 10)<<endl;

      return 0;

}

inside my main i calling my function addNumber which is just adding two values and returning me new value. 

but all the things happening here in main thread adding 




adding a breakpoint at addNumber function you can check that addNumber opreation is done in mainthread.

now i changed my program to little bit

#include <iostream>

#include <thread>

#include <iostream>

#include <cmath>

using namespace std;

typedef   int(*NewFunctionType)(int, int);

int addNumber(int x,int y)

{

    return x+y;

};

int minusNumber(int x,int y)

{

    return  abs(x-y);

}


//function pointer in c++

//simple concept to store a function as a variable in c++

int main(int argc, const char * argv[]) {

    //Function can be stored as a variable in c++

     //inside my

    addNumber(10, 20);

    thread thread1(minusNumber, 10,20);

    thread1.join();

      return 0;

}


this time my Main function contain one more function which is minusNumber which is running in a different thread 

 thread thread1(minusNumber, 10,20);


thread1 is new thread name, minusNumber is the function i want to run in different thread and 10, 20 are the first and second parameter add a breakpoint i would get this information and its make sure that my minusNumber function running in a different thread.


as you can see two difference 
1. now i have one more extra thread.

2. my minusNumber operation is happening in thread2 instead of thread1 which is main a main thread  


2. now the above example shows how i can execute a function in a different thread.

you will get a crash if you would not write thread1.join().  

thread.join() basically stops the current thread execution  which is calling the child thread until child thread does not finish its works.
read more about join 


Next now we will try to call a class member function by creating a new thread


//

//  classThread.hpp

//  c++ thread

//

//  Created by Manish on 17/07/20.

//  Copyright © 2020 Manish. All rights reserved.

//


#ifndef classThread_hpp

#define classThread_hpp

#include <iostream>

#include <vector>

using namespace std;

class Student

{

    private:

        string name;

    int age;

public:

    int getAge()

    {

        return age;

    };

    void setAge(int _age)

    {

        age=_age;

    };

    void setName(string _name)

    {

        name=_name;

    };

    string getName()

    {

          return name;

    };

    void displayMe()

    {

        cout<<"My name is =>"<<getName()<<" "<<"My age is=>"<<getAge()<<endl;

    }

    void failPass(const vector<int> &marks)

    {

        int totalMarks=0;

        for(auto m:marks)

        {

            totalMarks+=m;

        }

        if(totalMarks>100)

        {

            cout<<"Pass"<<endl;

        }else

        {

            cout<<"Fail"<<endl;

        }

    }

};


#endif /* classThread_hpp */


create a class Student i have done one mistake class name is Student but file name is classThread but its doesn't matter in c++. in c++ we can have different class name and file name.

//

//  main.cpp

//  c++ thread

//

//  Created by Manish on 06/07/20.

//  Copyright © 2020 Manish. All rights reserved.

//


#include <iostream>

#include <thread>

#include <iostream>

#include <cmath>

#include "classThread.hpp"


using namespace std;

//function pointer in c++

//simple concept to store a function as a variable in c++

int main(int argc, const char * argv[]) {

    

    //create a new instance of s1

    Student s1;

    s1.setAge(22);

    s1.setName("deepak negi");

    cout<<"MY AGE=>"<<s1.getAge()<<endl;

    cout<<"MY NAME=>"<<s1.getName()<<endl;

    

    

    

    //calling member function of Student in a new thread

    thread t1(&Student::displayMe,&s1);

    t1.join();

    

    

    cout<<"second"<<endl;

    

    //calling other member function of student class in other thread

    vector<int> marks={10,10,22,11,11};

    thread t2(&Student::failPass,&s1,marks);

    t2.join();

    

    

    cout<<"all done i am over"<<endl;

    return 0;

}


now this is how my main.cpp file looks like its include classThread header files which include all necessary code  

now in above example i created a s1 instance of Student class where thread t1 and thread t2 handling different operation like thread t1 calling displayname member function of Student class and t2 calling failpass member function of Student class so in above situation  i am handling two different operation in two different threads.

now lets add break points


i added two break point one is line 38

 void displayMe()

    {

        cout<<"My name is =>"<<getName()<<" "<<"My age is=>"<<getAge()<<endl;

    }


and other is line 42

  void failPass(const vector<int> &marks)

    {

        int totalMarks=0;

        for(auto m:marks)

        {

            totalMarks+=m;

        }

        if(totalMarks>100)

        {

            cout<<"Pass"<<endl;

        }else

        {

            cout<<"Fail"<<endl;

        }

    }



and after adding breakout you can checkout that when i move from one breakpoint to other i can see that my displayName and failPass operation are handled by different threads. for reference you can check above video


thread with class pointer 

 //thread with pointer

    Student *s2=new Student();

    s2->setAge(29);

    s2->setName("manish negi");

    

    thread t3(&Student::displayMe,s2);

    t3.join();


    cout<<"all done i am over"<<endl;



3. third and main concept thread safety (

Thread Synchronization Primitives

)

this is one of the main topic which is really important when we are dealing with multithreading. threading can improve your game or application performance by utilizing multicore architecture of latest machine by using thread without knowing them better can be problematic so here we try to create thread safe code to which is useful in many situation.

code of my simple example

//

//  main.cpp

//  c++ thread

//

//  Created by Manish on 06/07/20.

//  Copyright © 2020 Manish. All rights reserved.

//


#include <iostream>

#include <thread>

#include <iostream>

#include <cmath>

#include "classThread.hpp"

#include "vector"

using namespace std;

//displayNames

vector<string> myVec;

void displayNames(const string &prefix,int max)

{


    for(int i=0;i<max;i++)

    {

        const string name=prefix+to_string(i);

        myVec.push_back(name);

    }

    for(auto v:myVec)

    {

        cout<<v<<endl;

    }

   

    cout<<"-----"<<endl;

}

int main(int argc, const char * argv[]) {

    

     thread t1(displayNames,"hello",10);

    thread t2(displayNames,"bye",10);


    

    t1.join();

    t2.join();

    return 0;

}


as you can see i created 2 thread and trying to push element in a global vector and i am getting regular crash the reason you can see by adding breakpoint let check out the video below


as you can see thread5 trying to access the vector myVec even thread2 not completed its operation ( in my case pushing 10 items) i am getting a regular crash so my program is not thread safe how i can get rid of it. the solution is adding a Mutex
      

after adding a mutex my code looks like below

//

//  main.cpp

//  c++ thread

//

//  Created by Manish on 06/07/20.

//  Copyright © 2020 Manish. All rights reserved.

//


#include <iostream>

#include <thread>

#include <iostream>

#include <cmath>

#include "classThread.hpp"

#include "vector"

using namespace std;

//displayNames

mutex Mutex;

vector<string> myVec;

void displayNames(const string &prefix,int max)

{

     Mutex.lock();

    for(int i=0;i<max;i++)

    {

        const string name=prefix+to_string(i);

        myVec.push_back(name);

    }

    for(auto v:myVec)

    {

        cout<<v<<endl;

    }

    Mutex.unlock();

    cout<<"-----"<<endl;

}

int main(int argc, const char * argv[]) {

    

     thread t1(displayNames,"hello",10);

    thread t2(displayNames,"bye",10);


    

    t1.join();

    t2.join();

    return 0;

}


so you can see i declare a mutex Mutex and inside code i am locking the mutex by locking mutex thread5 can't access my function displayNames util thread2 complete its operation after thread2 complete its operation i am unlocking the mutex and now thread5 can access my displayNames function let add breakpoint and check out this scenario 


lets add the video and checkout whats happening behind the scenes 








by checking above video you can see thread5 can't access displayNames function until thread2 completed its operation.

so next question that comes in out mind when to use which data type of for thread safety 

1. passing a vector and appending it using std::ref  is thread safe




//

//  main.cpp

//  c++ thread

//

//  Created by Manish on 06/07/20.

//  Copyright © 2020 Manish. All rights reserved.

//


#include <iostream>

#include <thread>

#include <iostream>

#include <cmath>

#include "classThread.hpp"

#include "vector"

using namespace std;

//displayNames


void displayNames(vector<string> &vec,const string &prefix,int max)

{


    for(int i=0;i<max;i++)

    {

        const string name=prefix+to_string(i);

        vec.push_back(name);

    }

    for(auto v:vec)

    {

        cout<<v<<endl;

    }

    cout<<"-------------------"<<endl;

}

int main(int argc, const char * argv[]) {

    vector<string> myVec;

    displayNames(myVec, "hello",50);

    for(auto v:myVec)

    {

        cout<<v<<endl;

    }

    thread t1(displayNames,std::ref(myVec),"bye",50);


    

    t1.join();

    

    return 0;


}

 


2. Manipulating a global variable specially stl container is not thread safe among multiple threads are not thread safe it can either crash you program and give you unwanted result.


now lets try with Map is passing using std::ref is safe

//

//  main.cpp

//  c++ thread

//

//  Created by Manish on 06/07/20.

//  Copyright © 2020 Manish. All rights reserved.

//


#include <iostream>

#include <thread>

#include <iostream>

#include <cmath>

#include "classThread.hpp"

#include "map"

using namespace std;

//displayNames

//append some students


void MapContainer(map<int,string> &myMap,int from,int total)

{

   

    for(int i=from;i<=total;i++)

    {

        const string student="student"+to_string(i);

        myMap[i]=student;

    }

    for(auto x:myMap)

    {

        cout<<"id: "<<x.first<<" "<<"name: "<<x.second<<endl;

    }

    

}

void displayMap(map<int,string> &myMap)

{

    for(auto x:myMap)

    {

        cout<<"id: "<<x.first<<" "<<"name: "<<x.second<<endl;

    }

}

int main(int argc, const char * argv[]) {

    

    map<int,string> students;

    thread t1=thread(MapContainer, std::ref(students),1,5);

    thread t2=thread(MapContainer, std::ref(students),6,10);

    

    t1.join();

    t2.join();

    

    map<int,string> emps;

    emps[0]="ramesh";

    emps[1]="dinesh";

    emps[3]="seema";

    thread t3=thread(displayMap, std::ref(emps));

    thread t4=thread(displayMap, std::ref(emps));

    t3.join();

    t4.join();

    return 0;

}



so adding or appending items between threads in a Map is not thread safe but accessing item in a map is thread safe.

one more thing member function declared as const are thread safe among multiple threads  
and almost reading all STL containers among multiple threads  are thread safe.

Map /unordered_map =>adding among multiple threads not thread safe
Map/unordered_map =>reading among multiple thread safe
Map/unordered_map =>removed items among multiple thread safe

check out the below code its is thread safe while removing items from a map across multiple thread

//

//  main.cpp

//  c++ thread

//

//  Created by Manish on 06/07/20.

//  Copyright © 2020 Manish. All rights reserved.

//


#include <iostream>

#include <thread>

#include <iostream>

#include <cmath>

#include "classThread.hpp"

#include "map"

using namespace std;


void removeItem(map<int,string> &myMap,vector<int> &keys)

{

    for(auto x:keys)

    {

        std::map<int,string>::iterator it=myMap.find(x);

        myMap.erase(it);

        

    }

    

    

}

int main(int argc, const char * argv[]) {


    map<int,string> emps;

    for(int i=1;i<100;i++)

    {

        emps[i]="manish"+to_string(i);

    }

    vector<int> vec1={1,2,3,4,5,6,7,8,9,10};

    vector<int> vec2={98,97,96};

    thread t3=thread(removeItem, std::ref(emps),std::ref(vec1));

    thread t4=thread(removeItem, std::ref(emps),std::ref(vec2));

    t3.join();

    t4.join();

    

    for(auto x:emps)

    {

        cout<<"id: "<<x.first<<" "<<"name: "<<x.second<<endl;

    }

    return 0;

}



you can also try same program with unordered_map map

1. writing STL container is not thread safe.
2. access a value or reading STL container among thread safe.
3. deleting value across multiple thread safe.













4.std::async threading in a better way using in build std::async

std::async is a better way to deal with multithreading in c++ c++ 11 and c++ 14 and upwards version support std:::async 


#include <future>  std:async return a future and using future.get() you can get required result let's dive in the code 


#include <thread>

#include <iostream>

#include <cmath>

//need to include #include <future> for std::async

#include <future>

#include "unordered_map"

using namespace std;

//a simple add function which accept 2 input and return the result

int addNumber(int x,int y)

{

    int z=x+y;

    return z;

}

//bestEmp function just accept a map reference and search out the best emp based on the empid

string bestEmp(unordered_map<int,string> &empMap,int empId)

{

    string name="no good emp";

    //if emp name is in unordered_map then return true either false;

    unordered_map<int, string>::iterator it=empMap.find(empId);

    if(it!=empMap.end())

    {

        name=it->second;

    }

    return name;

};

int main(int argc, const char * argv[]) {

    

    //more about std::async http://www.cplusplus.com/reference/future/async/

    //std::async always return a future

    

    //std::async returm a future 

    future<int> fut=async(addNumber,  10,10);

    //fut.get() you can get excepted result in sometime in future

    cout<<fut.get()<<endl;

  

    //launch::async runs a function always in new thread (new child thread) -------------------------------------

    unordered_map<int, string> empMap;

    empMap[0]="manish";

    empMap[1]="sachin";

    empMap[2]="deepak";

    empMap[4]="seema";

    empMap[5]="deepak";

    future<string> fut1=async(launch::async,bestEmp,std::ref(empMap),2);

    cout<<"the best emp is "<<fut1.get()<<endl;

    

    

    

    //aunch::defer does not create a new thread and tried to run passed function in same thread (main thread in my case)

    future<string> fut2=async(launch::deferred,bestEmp,std::ref(empMap),6);

    cout<<"the best emp is "<<fut2.get()<<endl;

    return 0;

}


A bitmask value of type launch indicating the launching policy:


launch::async-> always launch a new thread
launch::deferred->never create a new thread try to execute operation in same thread

launch::async|launch::deferred->default parameter if you don't provide any value to std::async  so totally depends on system  thread can be created or not if your program using too much cpu i think it would try to execute programme in same thread 


Thread safety with std::async 

so below i created  a little programme to check out thread safety with std::async 

1. so adding records or inserted records across multiple thread is not thread safe so you have to lock the mutex and unlock it to get the expected result.
2. deleting records across multiple threads are safe ( i think with all STL container is safe) 

#include <thread>

#include <iostream>

#include <cmath>

#include <vector>

//need to include #include <future> for std::async

#include <future>

#include "unordered_map"

using namespace std;

mutex mu;

//adding data not thread safe

unordered_map<int, string>  addSomeStudents(unordered_map<int, string> &students,int start,int end)

{

    mu.lock();

    for(int i=start;i<=end;i++)

    {

        students[i]="student"+to_string(i);

    }

    mu.unlock();

    return students;

}

//removing data

unordered_map<int, string>   removeSomeStudents(unordered_map<int, string> &students,vector<int> &IDS)

{

    

    unordered_map<int, string>::iterator it;

    for(auto id:IDS)

    {

         it=students.find(id);

         if(it!=students.end())

         {

             students.erase(it);

         }

   

    }

    return students;

}


int main(int argc, const char * argv[]) {

    unordered_map<int, string> studentMap;

    future<unordered_map<int, string>> fu1=async(addSomeStudents, std::ref(studentMap),1,10);

    future<unordered_map<int, string>> fu2=async(addSomeStudents, std::ref(studentMap),11,20);

    unordered_map<int, string> finalStudents=fu2.get();

    for(auto s:finalStudents)

    {

        cout<<"id: "<<s.first<<"name: "<<s.second<<endl;

    }

    

    //removing some students

    cout<<"removeing some students"<<endl;

    vector<int> IDS1={1,2,3,4,5,6,7,8,9,10,11,12};

    vector<int> IDS2={1,2,3,4,5,6,7,8,9,10,19,20};

    future<unordered_map<int, string>> fu3=async(removeSomeStudents, std::ref(studentMap),std::ref(IDS1));

    future<unordered_map<int, string>> fu4=async(removeSomeStudents, std::ref(studentMap),std::ref(IDS2));

    for(auto s:fu4.get())

    {

        cout<<"id: "<<s.first<<"name: "<<s.second<<endl;

    }

    

    return 0;

}


just try to run above code 


5. c++ std Libraries contain multiple inbuilt member function to handle the threading a better way






6. Thread with sleeping Time

















so few questions comes in mind where to use threads and where to not


1. in gaming you can preload assets using std::async because assets are heavy and they would always take time so better to load them in a different thread. 


2. making http call like getting data from server and sending data back to server can be done using std::async 


3. in multiplayer games it performance can be down where your game using web sockets so better push all web sockets related code in different thread so it would not disturb  main thread which is mainly responsible for rendering heavy graphics.

4. working with too many threads at the same time can easily increase the cpu usage.


5. expensive calculation can be done in separate threads like complex rescusive 
programme.



   









Comments

Popular posts from this blog

Better Memory management with PixiJS or How to manage cpu and cpu memory in PixiJS.

PixiJS is my favorite framework when i am looking for a web games specially for mobile or desktop  PixiJS is fast blazing fast and you can get a decent FPS even on older device.   so here is my optimization techniques for PixiJs 1. manage your sprites in a better way use spritesheet to reduce the draw calls create big sprite sheet which contain multiple sprites can be draw in gpu with a single draw call. use TexturePacker  https://www.codeandweb.com/texturepacker  best tool when its comes to spritesheet 2. for floating point calculation round off calculation for example let  speed = 0.75 ; let  position = 100 ; console . log ( Math . round ( speed * position )) 3. don't create very big canvas when u need a big canvas size game just try to create a small canvas and translate it. 4. its very important one managing TextureCache in memory you can get all TextureCache list by using  Object.entries(PIXI.utils.TextureCache); so even you use ap...

adding particles Effect in pixijs using https://pixijs.io/pixi-particles-editor/

adding particle in pixijs is very easy using the below tool more information can be found below https://github.com/pixijs/pixi-particles https://pixijs.io/pixi-particles-editor/ required packages  /// < reference path = "node_modules/pixi-particles/ambient.d.ts" /> import 'pixi-particles' code of particle delcare a     global variable   private emitter ?: Emitter ; const img = PIXI . Texture . from ( "./assets/images/particle.png" ); this . emitter = new Emitter ( this ,[ img ],{ "alpha" : { "start" : 0.62 , "end" : 0.39 }, "scale" : { "start" : 0.1 , "end" : 0.9 , "minimumScaleMultiplier" : 1.25 }, "color" : { "start" : "#ffff8f" , "end" : ...