Excellent choice of problem to display the topic. For obvious drawbacks related to conditional variables like spurious wakeups and lost wakeups, it would be great if you have another video explaining the same with std::atomic wherein the atomic boolean will repeatedly asks for the value (pull) instead of the conditional variable notifying all the other threads (push).
Should we add this condition? void waitforallthreadinit() { unique_lock lock(mutex_lock); // Locking the mutex to protect shared state while(thread_count != thread_ids.size()) { cv.wait(lock); // Wait for threads to initialize } }
@groot9896 In line 46, the print_thread function that has been passed as parameter to create the thread is a non static member of MyPrinter. Now when you call a function in C++, the compiler passes the this pointer (a pointer that points to the address of the specific instance of the object) implicitly. In this case you are just passing the address of the non static function and hence you have to explicitly state the hidden parameter, ie the address of the object for which the print_thread has to run, which in this case is provided by the "this" pointer because the thread is being created inside a function of the class. Something to note is if print_thread was a static member then the this pointer would not be required as static members are not tied to any instance and hence "this" pointer is undefined. source: "How To Create Threads In C++" video of this playlist by cppnuts, comment by @konstantinrebrov675 For line 68, It is a bit complicated but I will briefly explain it. Lambdas are callables, like functors. Their scope is not the class and they run out of the scope and just provide values inline. hence when you use non static members inside a lambda which is defined in a class, the lambda has no way of knowing which instance of the object the non static variables inside the lambda belong to. Hence you pass the "this" pointer again in the capture list which allows C++ to implicitly, in this case kind of function like replacing thread_ids and allowed_thread (non static members of the class) with this->thread_ids and this->allowed_thread. Again you don't need to pass this pointer if you are using static or global variables inside the lambda.
Very nicely explained as always. This looks like a good example to understand how multithreading can be done. But I believe, this problem may not be the exact use case of multithreading as at a given point of time, we are allowing and expecting only one thread to be in running over the CPU. Also looks like we can get a better time complexity if we go ahead with the single threaded solution for the exact same problem.
Thanks for the example of thread synchronization. One issue I encounter is that the program runs well when the "print_thread()" method is running forever. However, if I change the while condition and only run that method for let's say 5 times, then there is a problem when joining all the threads. I get an unhandled exception when joining the threads. Does anyone knows why this happens and how it can be fixed?
great video. There maybe some edge case will cause problem. the print_chars didn't consider cases it need to go over the string more than two times. Try this case: str = "abcd"; char_count = 12; thread_count = 2; It will break. try this: void print_chars() { cout
we can optimize this code using hash_map instead of function get_ids #include #include #include #include #include using namespace std; class Printer{ string s; int len = 0; int thread_count = 0; int current_thread = 0; int char_count = 0; int current_char = 0; mutex mu; condition_variable cv; vector threads; vector thread_ids; map get_ids; public: Printer(string str, int c_count, int t_count){ s = str; char_count = c_count; thread_count = t_count; len = s.size(); } void run(){ for(int i = 0;i
I think there is no need for a conditional variable in this code. Whoever gets the lock first will try to execute and you only need this code below. If the condition is NOT satisfied, it will unlock the lock and the remaining threads will attempt to lock it. if (std::this_thread::get_id() == thread_ids[allowed_thread]) // Check if it's the first thread that was spawned during init. { print_chars(); allowed_thread += 1; if (allowed_thread == thread_count) allowed_thread = 0; if (next_char >= str.length()) next_char -= str.length(); }
I understand the point of the video is to show thread synchronization, but isnt it impossible to parallelize this? At any time, only one thread is printing. Wouldn't I get the same performance if I just use one single thread to print the string the required number of times?
Hi. I want to write this myself first without looking at your solution. But explanation wasn't clear to me. I mean how are we going to end our program? for how long this thread creation and slicing characters is going to continue? I thought after reading the whole string, we should be done but you keep creating threads. Can you clarify this more? Thanks.
@@CppNuts The destructor of unique_lock calls unlock(). The destructor is called at the end of the lifetime of the lock. That's the core concepts of RAII. So we don't need to call unlock on unique lock exclusively. I run the same program by commenting line#73 and it worked fine.
@@ParvezKhanPK It might work but theoretically speaking the waiting threads when notified will simply wake up and go back to sleep seeing that the lock is already held by someone else. Some sort of spurious wake-ups might be at play in your case or it might be internal library implementation which schedules the thread to retry and grab the lock in this specific case.
Excellent choice of problem to display the topic.
For obvious drawbacks related to conditional variables like spurious wakeups and lost wakeups, it would be great if you have another video explaining the same with std::atomic wherein the atomic boolean will repeatedly asks for the value (pull) instead of the conditional variable notifying all the other threads (push).
Thanks man, I can try!!
Should we add this condition?
void waitforallthreadinit() {
unique_lock lock(mutex_lock); // Locking the mutex to protect shared state
while(thread_count != thread_ids.size()) {
cv.wait(lock); // Wait for threads to initialize
}
}
Superb learning again.. You are my favorite teacher... Hats off to your knowledge!!!
Thanks man!!
In line 46 why are we passing (this ) as argument and also in line 68 capture list ?
@groot9896
In line 46, the print_thread function that has been passed as parameter to create the thread is a non static member of MyPrinter. Now when you call a function in C++, the compiler passes the this pointer (a pointer that points to the address of the specific instance of the object) implicitly. In this case you are just passing the address of the non static function and hence you have to explicitly state the hidden parameter, ie the address of the object for which the print_thread has to run, which in this case is provided by the "this" pointer because the thread is being created inside a function of the class.
Something to note is if print_thread was a static member then the this pointer would not be required as static members are not tied to any instance and hence "this" pointer is undefined.
source: "How To Create Threads In C++" video of this playlist by cppnuts, comment by @konstantinrebrov675
For line 68, It is a bit complicated but I will briefly explain it. Lambdas are callables, like functors. Their scope is not the class and they run out of the scope and just provide values inline. hence when you use non static members inside a lambda which is defined in a class, the lambda has no way of knowing which instance of the object the non static variables inside the lambda belong to. Hence you pass the "this" pointer again in the capture list which allows C++ to implicitly, in this case kind of function like replacing thread_ids and allowed_thread (non static members of the class) with this->thread_ids and this->allowed_thread. Again you don't need to pass this pointer if you are using static or global variables inside the lambda.
Wow, great explanation 🎉
Very nicely explained as always.
This looks like a good example to understand how multithreading can be done. But I believe, this problem may not be the exact use case of multithreading as at a given point of time, we are allowing and expecting only one thread to be in running over the CPU. Also looks like we can get a better time complexity if we go ahead with the single threaded solution for the exact same problem.
Wonderful explanation....
Please share the question link also!!
Thanks for the example of thread synchronization. One issue I encounter is that the program runs well when the "print_thread()" method is running forever. However, if I change the while condition and only run that method for let's say 5 times, then there is a problem when joining all the threads. I get an unhandled exception when joining the threads. Does anyone knows why this happens and how it can be fixed?
great video.
There maybe some edge case will cause problem. the print_chars didn't consider cases it need to go over the string more than two times.
Try this case: str = "abcd"; char_count = 12; thread_count = 2; It will break.
try this:
void print_chars() {
cout
Just wondering if accessing the size of thread_ids is thread safe in waitforallthreadinit()
we can optimize this code using hash_map instead of function get_ids
#include
#include
#include
#include
#include
using namespace std;
class Printer{
string s;
int len = 0;
int thread_count = 0;
int current_thread = 0;
int char_count = 0;
int current_char = 0;
mutex mu;
condition_variable cv;
vector threads;
vector thread_ids;
map get_ids;
public:
Printer(string str, int c_count, int t_count){
s = str;
char_count = c_count;
thread_count = t_count;
len = s.size();
}
void run(){
for(int i = 0;i
will coroutine work better here? easier to control the sequence of the tasks
I think there is no need for a conditional variable in this code.
Whoever gets the lock first will try to execute and you only need this code below.
If the condition is NOT satisfied, it will unlock the lock and the remaining threads will attempt to lock it.
if (std::this_thread::get_id() == thread_ids[allowed_thread]) // Check if it's the first thread that was spawned during init.
{
print_chars();
allowed_thread += 1;
if (allowed_thread == thread_count) allowed_thread = 0;
if (next_char >= str.length()) next_char -= str.length();
}
Plz try your implementation, my code is available on github.
And plz share the complete implementation.
I understand the point of the video is to show thread synchronization, but isnt it impossible to parallelize this? At any time, only one thread is printing. Wouldn't I get the same performance if I just use one single thread to print the string the required number of times?
Hi. I want to write this myself first without looking at your solution. But explanation wasn't clear to me. I mean how are we going to end our program? for how long this thread creation and slicing characters is going to continue? I thought after reading the whole string, we should be done but you keep creating threads. Can you clarify this more? Thanks.
Actually things like this are build to run forever.
pls share git link of the code.
github.com/cppnuts-yt/CppNuts/blob/master/Threading/print%20abcde%20in%20sequence.cpp
Please share the link of code
do we need to unlock the unique_lock() at line#73? isn't unlocked by itself?
No it doesn't unlock by itself.
@@CppNuts The destructor of unique_lock calls unlock(). The destructor is called at the end of the lifetime of the lock.
That's the core concepts of RAII.
So we don't need to call unlock on unique lock exclusively. I run the same program by commenting line#73 and it worked fine.
Thanks
@@ParvezKhanPK It might work but theoretically speaking the waiting threads when notified will simply wake up and go back to sleep seeing that the lock is already held by someone else. Some sort of spurious wake-ups might be at play in your case or it might be internal library implementation which schedules the thread to retry and grab the lock in this specific case.
PLEASE ADD SEMAPHORE , COUNTING SEMAPHORE... will be very helpful.
sure..
I made a shorter way thinking like this and wanted to share it.
void print_chars() {
cout
Nice question