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)-
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 -
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 -
So there are 3 rues of using mutex efficiently -
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++.
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 -
- 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).
- 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.
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 -
- Use mutex to synchronize the resource.
- Never leak your data handler to outer world.
- 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