 Hey everyone, it's Brian and welcome back. We're going to continue our conversation on threading. Now in the last video we talked about how you can connect signals and slots between threads. So the newbie is going to go out here and they're going to say, I got an app, I got a thread, but would my app be faster if I just made another thread and another and another and so on and so on. And what ends up happening is you end up making all these threads and it becomes this hobbled mess and it's very unstructured and you can see what I'm doing here. I'm just going to keep making threads, thread, thread, thread, thread everywhere. So this becomes a nightmare and on top of that, this is really not efficient at all because each thread has a cost associated with it in terms of CPU and RAM. So what you're really doing is you're not making your program much faster. You'll see some short-term gains, but as you keep creating threads, you'll exponentially see your computer slow down further and further and further until either your application crashes or the operating system just simply says, you know what, I'm done and you get like the dreaded blue screen of death. All right, so what is a better way? Let's get rid of all this stuff. This video we're going to talk about what's called a thread pool, which is exactly what it sounds like, but it takes a little bit of description here. So let's go ahead and move him off to the side. We've got our app and we've got this little guy and let's give him a different color and this is our pool. Now, what does that really mean? Think of the pool as a manager class of threads and I'm going to, I can figure out how to get him bring forward. There we go. Got this little thread here and I'm going to grab another. Now you notice a pattern. I'm starting to create threads another and another and another, but it's going to stop at a certain limit. And usually this limit is tied to the number of cores in your machine. But you notice what I'm not doing. I'm not making tons of these. I have a pool, which is a manager, and it's going to manage these threads. And as I send work from the app to the pool, let's actually do that. And let's just put it right here. As I send work to the pool, the thread pool is going to determine what thread it goes on, not us. So bear in mind, these threads may not all be used or they may be all flooded with work at which point your little workload that you're sending to the pool is going to go in a queue and just sit there and wait until a thread becomes available. This may sound like a bad idea, but it's actually highly efficient and highly structured. It's also extremely easy to implement. So we're going to dive in and take a look. But before we do, real quick, I wanted to shout out to Qt Company. They have released the long term support version of Qt 6, and they also published the new Qt 6 QML book. This thing's awesome. Here's the link right here, Qt.io slash product slash Qt 6 slash QML dash book. I'll try to remember to put a link down below. If I forget, somebody please in the comments, just drop it down there. And if you're looking for more structured kind of learning, I do have courses out on Udemy. Just search for Qt 6, or you can search for my name, Brian Carons. And you'll see all my Qt 6 courses out there. And I have all my Qt 5 courses along with a migrating from 5 to 6 course out there. But enough of that, let's dive in and take a look at Thread Pools. First off, Thread Pools are fast, easy, efficient, and well, they are already baked right into your Qt application. So you don't have to add any external library or anything. They're already there. You're already using them. You just don't realize it yet. So let's make a few classes and let's tap into that power. So we're going to right click, add new. And first class I'm gonna make, let's go ahead and call this manager. I help if I spelled manager correctly. There we go, manager, there we go. Make sure this is a Q object because we want signals and slots. Next, finish. I'm gonna hit no on that, it'll still pop up. And then add new. We're going to make another class we're gonna call this worker. Make sure this is a Q object as well, because again, we wanna be able to use signals and slots. That's what makes Qt so powerful. Now because we're using CMake, we have to add these and at the time of this recording, you have to manually do it. Maybe someday that'll change, I don't know. All right, once we're there in there, make sure they are loaded into the IDE properly. So what are we gonna do here? We've got our main file, which is going to take a manager and that manager is going to run some workers and those workers are going to run on what's called the thread pool. The workers, however, really don't even know the thread pool exists. They just know that they're on a thread somewhere. So let's start with the simplest one. We're gonna do the manager and we're gonna flip over here and what the manager's going to do is, well, manage things. I'm gonna do a little bit of copy and paste because this code's pretty rudimentary and I think you got it by now. So we're gonna add in a Q thread pool and a Q runnable. A Q thread, a Q debug and our worker class. This takes a little bit of explaining though. You need a Q runnable to run inside of a Q thread pool. So highlight Q runnable, hit F1 and you'll see the Q runnable class is the base class for all runnable objects. When you see the word runnable, it means it's going to run inside the thread pool. Now, you should also note that this is not a Q object. That's right, I wanna just say that one more time. When you look at this, notice what it is not. It is not a Q object, meaning runnable itself does not have signals and slots. This often confuses people because they will create an object using Q runnable, throw it in a thread pool and then they can't talk to it. So to get around that, we have our worker which is a Q object and we're going to turn this into a runnable. Doing that is extremely simple and let me bring up the header file here. In my notes, we're going to plop that in, we're gonna do Q debug, Q runnable, Q thread. So we're gonna say public, Q runnable. Now, simply right click on Q runnable and refractor, insert virtual function of base class. And we wanna go ahead and make sure that the Q runnable run is selected. Notice this is a virtual void. So this is just simply an interface. If you don't want an interface is, it's a contract between objects. Or I should say a contract between classes that says they will have certain properties, methods available. So the Q runnable is an interface to the thread pool saying it will have a run function in there. And because it's virtual, we have to implement that. Now we got this. We can go to refractor, it might already be in there. Let's find out. It's already in there for us. So what does this really do? Well, two things. First run is what's going to be called by the thread pool. Remember, the manager is what we're going to do to work with that. So let's focus on the manager real quick. And I realize I'm flipping back and forth and that might get a little bit annoying, but trust me, this is ridiculously simple. So we're going to just do the magic copy and paste, boom. We're going to have a signal from the manager called work, which is going to tell the worker to do something. This is really not the correct way of doing it. So I'm just going to put a note in there. Not the best way. We're going to cover other ways in other videos, but I'm going to explain why later. Just usually put faith on that. But then we have some public slots. Start, started and finished. Start is going to start the workers. Started is when the worker tells us it started. Finished is when the worker tells us it's finished. That's right. We have some communication back from that worker now. We have that via signals and slots because we are implementing both Q object and Q runnable. I should say we're inheriting Q object and implementing Q runnable. It's super confusing, I know. That's one of the caveats of C++ is that you can have multiple inheritance. All right, so we're just going to add the definitions here. And I've been asked by a lot of people to try to slow down. I have a tendency to make these videos slightly slower than I actually work in real life, which is usually Mach 5, so I go pretty quick. But I'm going to try and slow it down. Again, use the buffer bar below for anything that you want to just skip through. So recap, we have a signal, work. We're going to tell workers to do things. It's not the best way to do that. And then we have slots, start, meaning we're going to start some workers, started. The workers started and finished, meaning the workers are finished. All right, that's a mouthful. All right, let's flip in here and let's take a look at our code. We just want to be able to say, you know what? We created something. We created our manager. Now, start, this is where things get a little bit interesting here. I'm going to say four, and we're just going to make one worker for now, just one. You're going to see why, because this thing's going to be super noisy and it's going to print a bunch of stuff out on the screen. So let's just focus on one for the moment here. Now, because we're doing a cross-thread operation, we should not have a parent. So we're going to say worker, and a pointer to, this is a queue object. So instinctively you're going to go, ah, I should put a parent in here for memory management. Don't do that. And here's why. The worker is a queue runnable and it has a special getter and setter in there called auto-delete. So we're going to say set auto-delete and we're going to set this to true. What this is going to do is tell the thread pool that executes this to delete it when it's done, because guess what? We don't want to worry about memory management. We just don't want to have to deal with that. Now, because I absolutely hate writing this code, I'm just going to copy and paste it. But feel free to pause the video if needed. We are going to connect the worker started signal, which doesn't exist yet, and the finish signal to the manager started and finished. And then the manager's work is going to go to the worker work, which doesn't exist yet. We're going to get to that. But notice we have queued connections. I'm going to explain this a little later on in the video. Super important you understand right off the bat, this will use what's called an auto-connection, which under the hood will attempt to flip this into a queued connection. But I'm just going to put that back for now. And then we're going to go ahead and say queue thread pool global instance. And when we do this, this is the instance or the thread pool that is created automatically when your queued application starts. You don't have to do anything, it's just there. And we're going to go ahead and say start. And we're going to go ahead and start that worker. Now, this does a few things, but really what it's doing is it's taking that worker, putting it over to the thread pool and when a thread is available, it moves it to that thread and then it goes into the worker and calls run. Lots of words, all the words. And I absolutely deleted that, there we go. So your main takeaway here is your manager is going to create a worker without a parent. We're going to set auto-delete to true because we want the thread pool to manage the memory. Then we're going to say thread pool, current instance, whatever it is, just go ahead and start this specific object. We're just doing one for now. Now we're going to go down to started and finished. This is where the worker is going to talk back to us. Usually you don't like it when a worker talks back to you, but you know, we're going to allow it this time. So I'm going to say worker. And we're going to do a queue object cast. A lot of people out there are going to get very, very confused if you don't know what this is and go, why aren't you using a static cast or some sort of C++ thing? Queue object cast is built right into queue and it's specifically designed for casting queue object and it takes a lot of the complexity out of it. So while I'm not going to dive into it in this video, we are going to probably cover it a little more in depth later on. So what we're doing here in this slot, something called this and that something is a queue object we can access from the sender function. It is going to return a pointer, the queue object and we're going to cast that pointer using queue object cast into our worker class. If this line completely mystified you, please go back to C++ 101. But basically we're getting a pointer, we're casting that pointer into something we can work with. Now, because we do not trust pointers, we need to test it. So I'm going to say, if not worker again, there are a billion different ways you could do that but if we don't have a pointer, we're just going to jump right out of there. We're going to pull the ripcord and just eject right out. So what we're going to do next is we're just going to say queue info started our worker object on whatever our current thread is, special note on current thread. This is going to be running on what's called the main thread, our manager. But our workers when they start will be on a pool thread and you'll see that later on. And we can grab this and just do the old copy and paste right here. We're going to say finished and put that right there. So very, very simple code. There's really not a lot of complexity to this. There are a few caveats. So for example, when we start, we're going to tell the worker to work. We're going to have that special function in here that we have not put in yet. So that's the highlight of the manager. Now we're on the worker and this is the little buddy here who's going to do the actual work. So what I'm going to do here is I'm going to just copy out of my notes just to save a smidge of time. So we've got a constructor, a deconstructor, two signals, our public slot, work. And then we have our runnable interface. So let's go ahead and flesh these out. Nope, those are signals. See, I get tripped up even sometimes too. I just get so focused on what I'm doing here. In case you're wondering, no, you can't implement a signal. That's what Qt does under the hood with Mach. So anyways, we've got a few things going on here. Very, very simple class. We have our constructor. So I'm just going to say this was created. And then we're going to say this was destroyed. In case you're wondering how I can just magically do that, I'm copying and pasting for my notes off the screen here. And we're going to say work. Special note to this line right here. We're going to play around with this later on, but just remember this work function or this work slot. Now run, this is where we actually start in the thread pool. So I'm going to say starting and then finishing. And I'm going to pull them out here. Starting in the thread. Finishing in the thread. And what I mean by this is when this is done right here, this little line, that thread is going to check our worker because it's a Q runnable. And it's going to see if that auto delete flag is set to true like we did in our manager, set auto delete true. And if it's true, it's going to wipe out this object and then free that thread up for the next worker or the next Q runnable that gets pushed onto it. So while we're in here, we can do some actual work. So I'm going to say emit started. And right here, we're going to emit finished. Wow, I cannot type today. What is going on here with my keyboard? How embarrassing. Okay, there we go. So we're emitting started, we're emitting finished. Sometimes when I slow down, it just does not help my horrible typing. So what we're doing here is we're telling any object that's connected to us, in this case, the manager, that we are starting and then finished and then we're going to do some work in between. And this is where you can just do anything. I'm just going to simulate, we're doing some sort of computation or work or something. I'm going to say, I is lesson five. I'm going to go ahead and increment I. And now let's go ahead and say, this is what I mean by this program is going to be really chatty. Queue thread and we want the current thread MS sleep. And what this is going to do is put the thread to sleep for a number of milliseconds. Now, in the background, we won't see that because this will be running on another thread, but this thread literally goes to sleep and does nothing. It's like your niece or your nephew or your son or daughter, when they go to sleep, they do nothing. So for one second, which is an eternity in computer time, this is going to do absolutely nothing but sit there. Just simulating some sort of work. If you look at the structure of this program, it's actually barely simple. We're just printing a bunch of garbage out on the screen, but we're going to show it's created when it's destroyed. We're going to be able to call this slot from another class. Remember, special attention to this, it's going to cause issues. And run is when this is started on the thread pool. And all of this and anything you call in here runs on that thread, which is what we're going to demonstrate really quickly. All right, let's jump back into our manager. Suddenly all these connections really start working because we've got everything in there. But I want to highlight, the worker is connected to the manager and we're connecting started to started. So when the worker starts, it's going to tell the manager it started. When the worker is finished, it's going to tell the manager it's finished. And we're doing something backwards here. We're saying the manager work is going to connect to the worker work. So what does that really mean here? Manager has a signal called work. And I said this is not the best way. You're going to see why in just a second. And the worker has that slot work, which really does nothing but prints work out on the screen. See, right here. All right, is your brain fully yet? Because this seems like it's overly cumbersome. We're going to wrap this up really quickly and we're going to go down to our main and let's start filling this out here. So what do we need to do first off? Well, we need two things. We need Qthread and our manager. We need Qthread because I want to rename that main thread so we can see the difference on the screen. And we're going to say set object name because yes, this is a Q object. We're going to go ahead and set that name, main thread. So we can see what's going on. Then we're going to say manager. I feel like I should have named the class Karen instead of worker. So the manager is telling Karen what to do. You know that internet meme joke that Karen's. Whoa, I got thinking about memes and I couldn't concentrate there. Sorry about that. And we're going to go ahead and start. Now somebody out there is going to say, now wait a minute, I see that little icon right there which means this is a slot. Why are you calling this as a function? Well, that's the beauty of Qt. You can call it both ways. You can call it as a normal function or you can call it via signals and slots. It simply doesn't matter. All right, let's give this a good build. Make sure we don't have any little gremlins laying around in our code. All right, we've got a successful build. Let's go ahead and give this a test. Let's go ahead and test this. Go ahead and say run. And let's see what we got here. I'm just going to expand that out. All right, we've got our full life cycle of a single worker right now. We have created the manager on our main thread. We've created the worker on the main thread. Then we're starting the worker and notice suddenly it is on a thread on a thread pool. So we got our Q thread pool thread, say that five times real fast. There's the memory address and it's just simply named thread pooled. You notice that little pooled right there tells you that it is in the thread pool. So this is a thread that Qt made for us. We don't have to worry about it. Now we've started the worker on the main thread, meaning what we're doing is we're starting it and now it's coming back and telling us, hey, we have started. And let's go ahead and show what I mean by that. So we're in the manager. We're gonna say thread pool global instance, start worker. And in the worker, it's gonna go ahead and when we're ready, run. And it's going to say starting. And let's flip back here, starting. And this is on the pooled thread. Then we're going to emit started. And remember in the manager, we have our signal slots. This can get really confusing for newbies. But when the worker is emitting started, the manager started slot will get called. And that's exactly what's going on here. Started is being called back in the manager on our main thread. Yes, that's right. So now we can have communication between that object, even though it lives on a thread pool in another thread. This is extremely cool stuff. So we can see it started and we can see when it was finished. We also see it was destroyed and finished-ing. Finished-ing. I don't even think that is a word. I'm going to have to fix that, it's going to bother me. Let's go in here and fix that really quick. Finished, where is that worker? Sometimes we just like creating words, finished-ing. Man, I hope somebody caught that, finished-ing. There we go, finished-ing. And just making a whole new language. Anyways, back to this. Really what we have here is we have a manager. It's creating a worker. It's starting the worker. The worker is letting us know it started and then it's running on the pool thread doing its thing. And then we've got this annoying finished-ing. I can't believe I typed that. And it is running on the thread pool and it's destroying it because that auto-deletes there and then we get that signal at the very end saying, hey, it was destroyed. This is actually really cool stuff. And when you think about it, you don't need to do a whole lot. At a high level, really, you just need a queue object that implements the queue-runnable interface, which is as simple as just including it. Adding it, right-clicking and then going to Refractor and then Insert Virtual Functions of Base Class and adding in the run. It's really ridiculously simple. As long as you have that everything inside of run is going to be run on that thread pool. Now, I did say pay special attention to work. We're gonna mess around with this. This is what I've seen newbies do all the time. We're in the manager on started. This is where the worker has emitted a signal, started, and the manager is being called via cute through the signal and slot right up in here. So we're gonna tell that worker to do something. And what I'll see a lot of newbies do is they'll do something like this. Worker dot, and let's go ahead and say we want to call a function directly. Yeah, don't do that, bad, bad, bad. Don't do that, but let's see what it does. All right, right away, I can see the problem I hope you do as well. Our work is running on the worker, which is living on the thread pool, but it's being called on the main thread, not on the pooled thread. Oh, no. So if you were doing any sort of heavy number crunching and you try calling that on the work function, on the worker, you just defeated the whole point of even using a thread pool because you're right back on the main thread. Yeah, so that's super frustrating. So what a lot of people do is they go, hmm, how do I solve this problem? Well, there's two ways. And I'm gonna tell you one way which I do not believe is correct, although I've seen it work and honestly I've used it myself and that's signals and slots. So instead we're going to emit work. And this is our signal on the manager that is connected via signals and slots to the worker. And we're saying, hey, it's time to work. And the manager work will emit and call the work slot on the worker. Notice I have this cute connection here. There's a reason for that. But let's go back to the normal auto connection. Let's run this and see what happens. All right, you notice how it's still running on the main thread. That's not good. So totally defeats the purpose of doing that. This is where you start diving into Google and you go, hmm, all right, well, I think I'm gonna read about this and we're gonna go here. We're gonna add in a cute connection. And let's just highlight this and F1 on the screen to bring up the help system. So auto connection, this is where cute tries to figure out if it's on the same thread or not. And if it's on the same thread, it does a direct connection. If it's not, if it's on a different thread, which clearly this is on a different thread, it does a cute connection. So what's the difference here? A direct connection or same thread, the slot is executed in the signaling thread, meaning the main thread. Cute connection, the slot is executed in the receiver's thread. I want you to highlight that, the receiver's thread, meaning this would be the thread that the object was initially created on. And let's go ahead and demonstrate that. A lot of people think it's the thread the object's on and it couldn't be anything further from the truth. So I've got this manager work will emit, the worker will call the work slot and this is cute. So this should be the receiver thread. And let's run this, see what happens. Oh, you can already see the issue right there on the screen here, main thread. Even though we're using the cute connection, it's still being called on the main thread and that is simply because we created the worker on the main thread, so that's where it's gonna call it at. Is this a bug in cute or is this a design flaw with my application? Well, you could argue that it's both, to be brutally honest with you. I don't like doing either of those cause I don't like trying to figure out what's gonna change in cute's code base from one version of the next, even though it's been amazing and very stable, it doesn't really work the way you think it would. So what I do typically is I don't do this at all. What I will do is I will close that and I will not do either of those. I will simply run my application, but when I do, I give this worker everything that it needs for its entire existence when I start it and anything that it needs should not get outside stimuli unless it's getting that stimuli from something that was created in this run function on the actual thread that it's living on. So I hate to give you that bad news, but that's one of the little caveats of using a thread pull is you will get into some weird cross thread operations where you're scratching your head trying to figure this out. Now I know you're gonna ask, yes there are ways of calling it directly on the other thread, but it gets a little bit more involved than where we are now. I just wanted to throw that out there in case you're working with this and you get stuck. So quick review. This is a thread pull and how to work with it and how to communicate across thread to that object running on the thread pull. The biggest thing you need to do is create a queue object, implement, queue runnable and then start that out on the queue thread pull. Happy coding and I hope to see you later. I hope you enjoyed this video. You can find the source code out on github.com. If you need additional help, myself and thousands of other developers are hanging out in the Voidrealms Facebook group. This is a large group with lots of developers and we talk about everything technology related, not just the technology that you just watched. And if you want official training, I do develop courses out on udemy.com. This is official classroom style training. If you go out there and the course you're looking for is just simply not there, drop me a note, I'm either working on it or I will actually develop it. I will put a link down below for all three of those. And as always, help me help you. Smash that like and subscribe button. The more popular these videos become, the more I'll create and publish out on YouTube. Thank you for watching.