Monday, 31 October 2016

Step by step multithreading : mutex


I am hoping that, you have understood previous topics mentioned in my web page, if not please go through them to understand this topic. Till now we have understood how to create thread. So simple and easy, isn't it? You made your program run faster, didn't you? of course you did, if you are running your program into multi-core processor and executing it through threads you are going to achieve the performance. 

Now lets move to the real issues, what if more then one threads are using same resource, simultaneously? This will result a messy output, this behavior will result into a disaster. Though your program become faster but it will not behave as per your expectations at run-time.

Lets understand it step by step, and learn how we can resolve it in C++. First look at below mentioned program and see what output it will produces -

#include<iostream>
#include<thread>
#include<string>
#include<vector>
using namespace std;

// student list
vector<string> studentList;
//teacher list
vector<string> teacherList;
void addStudentNames()
{
    studentList.push_back("studentA");
    studentList.push_back("studentB");
    studentList.push_back("studentC");
    studentList.push_back("studentD");
    studentList.push_back("studentE");
    studentList.push_back("studentF");
    studentList.push_back("studentG");
    studentList.push_back("studentH");
    studentList.push_back("studentI");
}

void addTeacherNames()
{
    teacherList.push_back("teacherListA");
    teacherList.push_back("teacherListB");
    teacherList.push_back("teacherListC");
    teacherList.push_back("teacherListD");
    teacherList.push_back("teacherListE");
    teacherList.push_back("teacherListF");
    teacherList.push_back("teacherListG");
    teacherList.push_back("teacherListH");
    teacherList.push_back("teacherListI");
}
void printStudentsName()
{
    for(auto const &student : studentList)
    {
        cout<< "Printing student names from thread - " << student << endl;
    }
}
void printTeachersName()
{
    for(auto const &teacher : teacherList)
    {
        cout<< "Printing teacher names from main thread - " << teacher << endl;
    }
}

int main(int argc, char **argv)
{
    addStudentNames();
    addTeacherNames();
    thread t1(printStudentsName);
    printTeachersName();
    t1.join();
    return 0;
}
/*
output -
$ ./a.exe
Printing teacher names from main thread - Printing student names from thread - teacherListAstudentA

Printing teacher names from main thread - Printing student names from thread - teacherListBstudentB

Printing teacher names from main thread - Printing student names from thread - teacherListCstudentC

Printing teacher names from main thread - Printing student names from thread - teacherListDstudentD

Printing teacher names from main thread - Printing student names from thread - teacherListEstudentE

Printing teacher names from main thread - Printing student names from thread - teacherListFstudentF

Printing teacher names from main thread - Printing student names from thread - teacherListGstudentG

Printing teacher names from main thread - Printing student names from thread - teacherListHstudentH

Printing teacher names from main thread - Printing student names from thread - teacherListIstudentI

*/

Look the output, is that what you was expecting? what is happening here? 

Actually child thread and main thread, both are sharing same resource (in this case cout), and both are running parallel. So both are printing their messages on screen and you get mixed result and all things just messed up, without caring what user/programmer expected it to be. How to solve that?

Well C++ standard library will give you mutex. which will help you solve above mentioned problem. See below mentioned code, followed by detailed discussion.

#include<iostream>
#include<thread>
#include<string>
#include<vector>
#include<mutex>
using namespace std;

// student list
vector<string> studentList;
//teacher list
vector<string> teacherList;
// declare mutex
std::mutex locker;

void addStudentNames()
{
    studentList.push_back("studentA");
    studentList.push_back("studentB");
    studentList.push_back("studentC");
    studentList.push_back("studentD");
    studentList.push_back("studentE");
    studentList.push_back("studentF");
    studentList.push_back("studentG");
    studentList.push_back("studentH");
    studentList.push_back("studentI");
}

void addTeacherNames()
{
    teacherList.push_back("teacherListA");
    teacherList.push_back("teacherListB");
    teacherList.push_back("teacherListC");
    teacherList.push_back("teacherListD");
    teacherList.push_back("teacherListE");
    teacherList.push_back("teacherListF");
    teacherList.push_back("teacherListG");
    teacherList.push_back("teacherListH");
    teacherList.push_back("teacherListI");
}
void printStudentsName()
{
    for(auto const &student : studentList)
    {
        locker.lock();
        cout<< "Printing student names from thread - " << student << endl;
        locker.unlock();
    }
}
void printTeachersName()
{
    for(auto const &teacher : teacherList)
    {
        locker.lock();
        cout<< "Printing teacher names from main thread - " << teacher << endl;
        locker.unlock();
    }
}

int main(int argc, char **argv)
{
    addStudentNames();
    addTeacherNames();
    thread t1(printStudentsName);
    printTeachersName();
    t1.join();
    return 0;
}
/*
output -

$ ./a.exe
Printing teacher names from main thread - teacherListA
Printing student names from thread - studentA
Printing teacher names from main thread - teacherListB
Printing student names from thread - studentB
Printing teacher names from main thread - teacherListC
Printing student names from thread - studentC
Printing teacher names from main thread - teacherListD
Printing student names from thread - studentD
Printing teacher names from main thread - teacherListE
Printing student names from thread - studentE
Printing teacher names from main thread - teacherListF
Printing student names from thread - studentF
Printing teacher names from main thread - teacherListG
Printing student names from thread - studentG
Printing teacher names from main thread - teacherListH
Printing student names from thread - studentH
Printing teacher names from main thread - teacherListI
Printing student names from thread - studentI
*/

Now we are locking shared resource, cout (which is shared between main and child thread). Mutex will allow you to do that. Until current thread unlock mutex, other thread will not get access to resource, cout.

So we lock the resource through mutex, which is shared between two or more threads. Mutex will protect your resource, it will avoid execution of resource by more then one thread simultaneously.

Congratulations, we have solved the first issue, now lets talk about second issue, (step by step we will solve all the issues, with code example). 

In any function, what if we lock resource and before control reaches to unlock(), we get exception? Well we have seen it while implementing join(), as what if we get exception before control reaches to join()

We have to use RAII, to solve above mentioned problem. look at the below mentioned code, and follow its description -

#include<iostream>
#include<thread>
#include<string>
#include<vector>
#include<mutex>
using namespace std;

// student list
vector<string> studentList;
//teacher list
vector<string> teacherList;
// declare mutex
std::mutex locker;

void addStudentNames()
{
    studentList.push_back("studentA");
    studentList.push_back("studentB");
    studentList.push_back("studentC");
    studentList.push_back("studentD");
    studentList.push_back("studentE");
    studentList.push_back("studentF");
    studentList.push_back("studentG");
    studentList.push_back("studentH");
    studentList.push_back("studentI");
}

void addTeacherNames()
{
    teacherList.push_back("teacherListA");
    teacherList.push_back("teacherListB");
    teacherList.push_back("teacherListC");
    teacherList.push_back("teacherListD");
    teacherList.push_back("teacherListE");
    teacherList.push_back("teacherListF");
    teacherList.push_back("teacherListG");
    teacherList.push_back("teacherListH");
    teacherList.push_back("teacherListI");
}
void printStudentsName()
{
    for(auto const &student : studentList)
    {
        lock_guard<mutex> nowLockWithGuard(locker);
        cout<< "Printing student names from thread - " << student << endl;
    }
}
void printTeachersName()
{
    for(auto const &teacher : teacherList)
    {
        // this will act as RAII.
        lock_guard<mutex> nowLockWithGuard(locker);
        cout<< "Printing teacher names from main thread - " << teacher << endl;
    }
}
// the best part of using lock_guard, is - now you don't have to unlock(). as soon as lock_guard goes
// out of control, it will get destroyed and that will invoke unlock().
int main(int argc, char **argv)
{
    addStudentNames();
    addTeacherNames();
    thread t1(printStudentsName);
    printTeachersName();
    t1.join();
    return 0;
}
/*
output -
$ ./a.exe
Printing teacher names from main thread - teacherListA
Printing student names from thread - studentA
Printing teacher names from main thread - teacherListB
Printing student names from thread - studentB
Printing teacher names from main thread - teacherListC
Printing student names from thread - studentC
Printing teacher names from main thread - teacherListD
Printing student names from thread - studentD
Printing teacher names from main thread - teacherListE
Printing student names from thread - studentE
Printing teacher names from main thread - teacherListF
Printing student names from thread - studentF
Printing teacher names from main thread - teacherListG
Printing student names from thread - studentG
Printing teacher names from main thread - teacherListH
Printing student names from thread - studentH
Printing teacher names from main thread - teacherListI
Printing student names from thread - studentI

*/


The best part of using RAII, you don't have to think about deleting or calling unlock(). As it will automatically get called, once RAII object goes out of scope.

Okay so we have discussed and solve two problems so far, related to mutex, now lets talk about the third problem

We are sharing global resource (cout), but what if some other thread use cout from somewhere else? as it is a global resource, any function call use it, from some corner of your project, isn't it?

In real scenario, please make sure your resource is limited to some scope (scope could be big or small, as per requirement, but it has to be in some scope, so that you can use mutex carefully). see below mentioned program for more detail (A simple class to get BD connection)-

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;

class DB   // class responsible for all DB operation.
{
    public:
        execute(const string& str)
        {
            //execute the sql in respective DB
        }
};
class DBConnection
{
    public:
        DBConnection()
        {
            m_connection = new DB;
        }
        //execute your query 
        void executeYourQuery(const string& str)
        {
            lock_guard<mutex> lockedWithGuard(m_lock);
            m_connection->execute(str);
        }
    private:
        auto_ptr<DB> m_connection;
        std::mutex m_lock;
};

int main(int argc, char **argv)
{
    DBConnection connection;
    string input = "select * from emp;";
    connection.executeYourQuery(input);
    return 0;
}

Now your resource is secure, No one can get it without going through mutex (lock). As there is only one entry point, which is executeYourQuery() function, ideally cout is not a resource to put lock on, as it is global in true sense, you have to create resource and set few defined entry points, so that you have control on locking and unlocking it (acquiring DB connection class would be a very good example of resource, of may be a logfile class who log the runtime error message into a logfile, whatever you can think as a resource, fuel up your imagination and think about resource what you can relate better). Apart from that you have to careful for few things -

  1. Never return m_connection to out side world. To any function in any form (like returning handler and all, never do that with the resource, which you want to be thread safe).
  2. Dont pass m_connection to any user define function, he may copy that in global scope and use it, which will make your code not thread safe. Or he may delete m_connection, which will give you bad results at run time.
Okay so we have finally learnt how to protect our resource. Now the last but not the least issue (fourth problem statement)- "Your code is thread safe or not is depend upon the way you implement the business logic, even you have protected your resource 100% in your class " lets see this with a very simple example -

class Stack
{
    public:
        int pop();
        int top();
        void push();
        // and many more
    private:
        int *m_array;
        mutex m_mutex;
}

void SomeFunction(Stack& obj)
{
    int data = obj.top();
    obj.pop();
    SomeFunction2();
}

Think for a moment that, we have implemented our stack class, fully thread safe (if I put full implementation of stack class, then it will become very big, and my focus here is not implementing stack class, but to understand the implementation issues of mutex). So please focus on the business logic ( function - SomeFunction()), how our function is using thread safe class.

in above mentioned program, m_mutex will protect different thread to execute same function simultaneously, for ex function pop() will be executed once at a time from a thread. If we have 4 values in our container - 
                                                                    1 - top
                                                                    2
                                                                    3
                                                                    4
Think we have 2 threads, executing SomeFunction() function simultaneously, first thread will call top() and get value - 1, and at same time, second thread will call top, and this also get 1, now first thread calls pop() and delete 1, and second thread then call pop() and delete 2, hey but we never process 2 just deleted it, we were not interested to delete 2. it means this program is not thread safe.

Yes, this is not thread safe, because of the interface, the two function top and pop shall not be two atomic function, they should be part of a function. But still we can fix it by modifying the functions like mentioned below -

class Stack
{
    public:
        int pop();
        int top();
        void push();
        // and many more
    private:
        int *m_array;
        mutex m_mutex;
}

void SomeFunction(Stack& obj)
{
    // apply some business logic and check if you want to 
    // delete this number. if not want to delete then return;
    if(!doWeWantToDeleteThis(obj.top())
    {
        return;
    }
    obj.pop();
    SomeFunction2();
}

bool doWeWantToDeleteThis(int val)
{
    // implement some logic.
}

So there are 3 rues of using mutex efficiently -

  1. Use mutex to synchronize the resource.
  2. Never leak your data handler to outer world.
  3. Design your interface in such a way, it will become thread safe. Or implement your thread safe resource carefully.


Thanks for reading this, please write questions or some more useful information related to this topic on comment section. To learn more about threading, see the full learning index here, or join multithreading learning page on FB or on G++.

No comments:

Post a Comment