Qt 6 - Episode 23 - Thread pools

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
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 uh so this becomes a nightmare and on top of that 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 pull 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 gonna i can figure out how to get him bring forward there we go got this little thread here and i'm gonna grab another now you notice a pattern i'm starting to create threads another and another and another but it's gonna 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 the pool is going to go in a queue and just sit there 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 cute company they have released the long term support version of q6 and they also published the new cute six qml book this thing's awesome here's the link right here cute dot io slash product slash qt6 slash qml-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 six or you can search for my name brian cairns and you'll see all my cute six courses out there and i have all my q5 courses along with a migrating from five to six 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 cute application so you don't have to add any external library 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 going to make let's go ahead and call this manager i help it by spell manager correctly there we go manager there we go make sure this is a cue object because we want signals and slots next finish i'm going to hit no on that it'll still pop up and then add new we're going to make another class we're going to call this worker make sure this is a cue object as well because again we want to 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 they're in there make sure they are loaded into the ide properly so what are we going to 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 pull exists they just know that they're on a thread somewhere so let's start with the simplest one we're going to do the manager and we're going to flip over here and what the manager is going to do is well manage things i'm going to 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 going to add in a cue thread pool and a cue runnable cue thread a q2bug and our worker class this takes a little bit of explaining though you need a cue runnable to run inside of a q thread pull so highlight cue 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 want to 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 cue runnable throw it in a thread pull and then they can't talk to it so to get around that we have our worker which is a cue 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 i'm going to plop that in we're going to do q debug q runnable q thread so we're going to say public cue runnable now simply right click on key runnable and refractor insert virtual function of base class and we want to go ahead and make sure that the cue runnable run is selected notice this is a virtual void so this is just simply an interface if you don't want an interfaces 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 dude we can go to refractor oh 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 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 through 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 huge leap of faith on that 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 queue object and current or you should say we're inheriting queue object and implementing qrunable it's super confusing i know that's one of the caveats of c plus plus 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 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 4 and we're just going to make one worker for now just one you're gonna see why because this thing's gonna be super noisy and it's gonna 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 a pointer to this is a cue object so instinctively you're going to go i should put a parent in here for memory management don't do that and here's why the worker is a cue 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 feel free to pause the video if needed we're going to connect the worker started signal which doesn't exist yet and the finished signal to the manager started and finished and then the manager's work's going to go to the worker work which doesn't exist yet we're going to get to that but notice we have cued 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 cued connection i'm just going to put that back for now and then we're going to go ahead and say cue thread pool global instance when we do this this is the instance or the thread pool that is created automatically when your cute 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 ah i accidentally 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 let's 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 this time so worker and we're going to do a q object cast a lot of people out there 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 plus thing cute object cast is built right into cute and it's specifically designed for casting cue 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 cue object we can access from the sender function it is going to return a pointer to a queue object and we're going to cast that pointer using cue object cast into our worker class if this line completely mystified you please go back to c plus plus 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 gonna 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 gonna jump right out of there we're gonna pull the rip cord and just eject right out what we're gonna do next is we're just going to say q 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 pooled 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 gonna 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 oh dope 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 cute does under the hood with mock 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 [Applause] and i'm going to put a little note 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 cue 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 cue runnable that gets pushed onto it so while we're in here we can do some actual work so i'm gonna 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 finish 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 gonna say i is less than five i'm gonna 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 cue 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 your 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 fairly 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 we're connecting started to started so when the worker starts it's going to tell the manager it started when the worker's 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 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 full 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 q thread and our manager we need q thread 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 key object we're going to set that name name thread so we can see what's going on then we say manager i feel like i should have named the class karen instead of workers so the manager is telling karen what to do you know that that internet meme joke the 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 q 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 cue 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 pool right there tells you that it is in the thread pool so this is a thread that cute 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 going to say thread pool global instance start worker and in the worker it's going to 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 finished ing i don't even think that is a word i'm gonna have to fix that that's gonna bother me let's go in here and fix that really quick finish where is that worker sometimes we just like creating words finishing man i hope somebody caught that finish ing there we go finished 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 pull thread doing its thing and then we've got this annoying finish thing 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 cue object that implements the cue 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 pull now i did say pay special attention to work we're going to mess around with this this is what i see newbies do all the time we're in the manager unstarted this is where the worker has emitted a signal started and the manager is being called viacute through the signal and slot right up in here so we're going to 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 yet don't do that bad bad bad don't do that but let's see what it does all right our 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 pulled 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 pull 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 going to 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 by 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 going to read about this and we're going to go here and we're going to 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 qt 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 cued 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 cued connection the slot is executed in the receivers thread i want 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 we'll admit the worker will call the work slot and this is cued 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 going to call it at uh is this a bug and 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 because i don't like trying to figure out what's going to change in qt'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 pool 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 going to 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 pool and how to work with it and how to communicate a cross thread to that object running on the thread pool the biggest thing you need to do is create a cue object implement cue runnable and then start that out on the q thread pool 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 void realms 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
Info
Channel: VoidRealms
Views: 1,266
Rating: undefined out of 5
Keywords: QThread, QThreadPool, QRunnable, Threads, Threading, Multi-threaded
Id: rwidwg3nL00
Channel Id: undefined
Length: 29min 48sec (1788 seconds)
Published: Thu Sep 30 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.