C++ Thread Tutorial - Command Queue | C++ in 2021

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey what's up guys welcome to this next lesson of c plus plus in 2021. um we're going to take a slightly different gear this time and we're going to start looking at threads and concurrency in c plus today we're going to start off simple because one because i have less time this week for a video and secondly because it can get complicated quickly and i want to make sure i truly grasp everything and give you practical examples of how to do everything [Music] so what we're going to do today is we're actually going to build a very basic let's say task queue where you can give your task queue we're going to call task manager so you can give your task manager commands and it will cue them up and run them on a separate thread for executing in parallel with the rest of your application let's just jump into it now and we'll try to do this in a fairly straightforward way that you can understand so um again if you want to hear more if there's anything that i don't go over that you would like me to focus on next time leave a comment down below hit like subscribe all that crap and let's get into it all right so i didn't mention it last time but you can find all the source code for all of these tutorials on um gotta push the tags but you you can find all the tutorials on my github github.com slash mobile technologies in cpp in 2021 you'll have all the new uh code here so current code is all the input manager code from last time so uh go there you'll have the newest stuff there as well um i'll push the tags soon i have the tags made locally but for some reason i i didn't push them yet okay so uh let's do our task manager so the first thing we're going to do is this is not properly organized for a real project but we're not going to deal with worry about that too much now we'll call this tasks and in here we are going to create a new module and we're going to call this c module task manager task manager dot ixx and we are going to also make a task manager.cpp as well so here we're going to we've already got our module exported here in our task manager cpp we're just going to put module task task manager and we've got our everything set up so so what we want to do is when we run our program we want during the runtime of our application we want to be able to dispatch uh tasks to our task manager to be run concurrently with our application now we're going to be doing it in a very naive way there's going to be one thread it's going to always be running so it's not going to be very efficient you're going to want to probably put some sleeps in there or something but we'll get that and get to that as we move forward so um let's let's let's start building stuff out so the first thing we need that we need to actually define our task manager so we're going to export our struct task manager and that's going to be the basics now we're going to put a private field in here uh public field [Music] we'll need another private field and i think that's everything we'll need so what we what do we want to do with our task matter we're going to want to be able to create it which will we'll just have a bunch of default constructors for all of our fields so we don't need an explicit default constructor but let's put it in there just to be explicit let's do that we're gonna need a destructor we'll we'll see why in a bit but we're gonna need a destructor um we are going to be need to be able to start our task manager so this is going to be we're going to give it a start function we're going to need to be able to queue up commands so let's say void cue command and what we're going to do here is we're actually going to do uh export i think i can do this let's try export using uh command equal int is that yeah and q command isn't working let's we're going to put a command in here like this so our commands themselves are just going to be integers but we're just going to call it command to be explicit um i think that's everything we're going to need for now uh as part of our interface um there's gonna be one more thing but we'll we'll add that when when we need to so look we've got our interface let's go into our main.tpp and implement it right so first we are going to import our task manager task manager and how are we going to do this so the first thing we've got to add our task manager to the bottom so task manager task manager and default constructor and we are also going to introduce our let's call it game loop which requires a boolean running variable and this one will be initialized to false so in our running in our run function here we are going to say while running here and then we'll just add some uh log messages so we can see see things happening uh game loop ended and do that and uh one last thing at the beginning of our run function we are going to set running to true so when we call run it'll set running to true and while it's running it will run the game loop so this is just so that we don't uh just run so we notice here we call run and then we exit immediately this will just make it so we don't exit immediately we keep on running so when we call run uh what we are going to need to do what we're going to need to do is when we call run running is equal to true and we are going to start our task manager now this is just uh at the beginning of our application we'll start the task manager we can actually start it whenever but this is when we're going to start it then in our running loop what we're going to do is we're going to take input we're going to take an integer we're going to assume they're always going to give an integer and then we are going to um send that command to the uh task manager so what we're going to do is we're going to do int we'll say command uh a number let's call it command zero and let's just make sure that built uh to make sure that i can export the using uh okay so this well we'll deal with that after it's complaining that this stuff isn't uh defined yet so we'll go back to it in a second so command zero and then what we're going to do is we're going to say stood count give me some give me a command must be an integer and we'll give that a new line then we will take in the thing into our command and we are going to just print out which command we got you gave me oops we will use stud format you gave me command there and we are going to give it command and we are just going to um give it an exit case so we're going to say switch command and if case is minus 99 this is gonna be our exit case we're gonna start running equal to false and break that so in theory now uh we should be able this should start a loop it will ask for input and then if we give it minus 99 it'll quit and it'll say game move ended so let's solve these intellisense errors before we can build so we need to define all of these functions here so we'll create a definition here and it's creating them all in a bad spot but we can copy paste them in a minute we build them here so this is um c plus module 20 stuff that hasn't quite figured out its code generation yet but we can do that now let's try building this and see what that gives us and that builds properly so we should be able to do this all right so let's say we give it 0 and we give it 2 and if we give it minus 99 game loop ended and it closes uh gracefully and that does exactly what we want to do [Music] that's good so um what we're going to do now is we are going to give it a default case so anything that is not an explicit command we are just going to pass it into our task manager task manager dot q command command right and here we should give it command here key command good um so start we're going to do a few things here um now we will take this and we will put this inside our queue command we're going to say cueing command cueing cueing command command and we will import format format and input import iostream all right and now we should be able to build this it should run i believe yes it runs we'll play this and now if we give it here it'll cue the different commands like this there we go so we've got the basics going on we've got it so that um we've got a task manager and we can run uh cue command through it and we will put another thing here and we don't need a stood format here task manager starting all right so is there anything missing here we've got the task manager start task manager cue command and we have a way to exit so we got everything in place for our basic task manager right so what we're going to do now is we're going to add a thread to it and we're going to start queueing command all of the stuff that we're going to need for threading is going to be stored inside the standard library thread header that's all we're going to be using for today that's related to threads and the object type that we are interested in storing is going to be a stood thread and we'll call it command thread so with our stood thread defined here what we can now do is start start the thread when we call the start function so how do we do that the basic thing the syntax of starting a thread goes like this so let's go into our main.cpp uh i'll do it like this let's say we define a just a basic global scoped function here let's say void my function and we put here while four uh and i equals zero i smaller than a hundred let's put thousand i plus plus should count good format all right and let's put a new line here ah let's not put a new line who cares all right so we have a function here that just prints out ten thousand let's make it a hundred one million let's say one million eyes and let's put a space in between them so we print out a million eyes a million numbers and we want that to run in the background for whatever reason let's ignore the q command for now and let's just say while we call start so if we want to call this function from a thread what we would do is let's say we do command thread equals and we call the constructor of thread with the function itself the function pointer or the name of the function like this and this would start the thread that's all you need to do start a thread using the standard functions so let's assume that let's put this up here let's do that at first and we call start and then let's say while running we will say um we're going to going to actually this is going to break some stuff we're going to see some errors but we'll do this for now just to show you what it does um stood count game loop and we are just going to pad this with spaces right so in theory what we should see is we should see this this get called it will start the thread it will uh run this a million times and in between this you should see that this loop is running at the same time and not waiting for all this stuff to happen so let's see what happens when we press play all right so let's wait for this to stop and you'll see that the numbers and the game loop um is interspersed with each other each other here i'll stop it um it's going to take a while because printing takes you see here they're running concurrently or they're sharing time so you hear it it seems pretty one-to-one game loop this game move that game with that but here you see a bunch of numbers in a row and then game loop so it's not necessarily one at a time let's close that and if you control c out under the thread running and you don't do it gracefully you're going to get a compiler error and it's gonna just basically tell you that you had a kernel panic or some crap but ignore that um it was expected but you saw you saw the output there where the the game loop uh words were being printed were competing for processor time with the printing of these uh this my function right so what do we want to do to be able to um let's say for example we wanted to wait until this was done before returning from this function right so when you create a thread like this it is said to become joinable meaning that you can um wait for it or that you have access to the uh thread so you could do what you would usually do is you would put like an if function and say if command thread dot joinable so what joinable is means that the thread is both running currently running and it is and you can block on it you would do something like command you would then do command thread dot join and what join does it join waits for this function to return or the function that you created that is running in the thread to return and join with the calling thread that's why it's called join before continuing on to this function so let's just say stood count finished joining right now let's put a new line here let's make this smaller because we don't we don't want to have to go over that many every single time and if we go to main now you'll see that game loop doesn't show up until task manager start is finished so let's go into that now all right all right stop stop stop all right let's go back into here and hopefully we still have the yeah so you see here printed all the numbers called finish joining and then went into the game loop so that that's probably the most one thing you need to get a grasp on the most is knowing um how to properly join your threads and how to keep track of them properly now that's not what we want for our implementation ideally our implementation we want to be able to add we want to be able to call stuff on the task manager stuff add stuff to the queue and have the thread pull from the queue and execute the commands so you're not able to do for example like this dot you can't do that right um we will need a run function so let's let's create that run function now um let's go inside here we will go this can be a private function so we'll do void run help if i did that all right good and now let's create the definition and that did it in here properly all right so we got void run so what void run is going to do is we are going to move this here and we're going to add some couch here we're going to say thread starting and thread shutting down and this is just for demonstration purposes so we want to be able to call this run function um like this and we don't want to call my function let's get rid of this now so what do we want to do so the stood thread has an overloaded constructor which takes um similar to the function bind syntax that i think i went over last in the function pointer video but it's similar to function pointer syntax where you would do end then class name task manager function name run and then the object that you're binding to so in this case it would be this and what this will do is it will create a thread that runs this run function and again we can just call this and this should show off what we're looking for and let's get rid of game loop so you'll see here when we run it thread starting thread shutting down finish joining that's exactly what we were expecting to see right so it created the thread it ran the function in the thread and then it and and then it waited for it to be finished and once it finished it finished it it exited out of everything so that's all we need in our start function but what we want to do is we just want to make sure that we don't um when we call start we don't override the any existing running thread so we're just going to um put this here we're going to put this at the top so if it's joinable it means that it's already running we are going to print a calc statement just for our records i want to say thread task manager already running do that and we can return out of this function and this will just prevent us from overriding the threat and we don't need to keep this either all right so when we call start it's going to run the threat and it's going to call run so let's um let's define our run function now so our run function is going to be relatively simple so the first thing we're going to do we're going to say running equal true which i believe we haven't defined yet so we can go here and we're going to need another bool running variable and we'll call it we'll initialize this as false then we will say while running like this and this is going to be pretty much our entire loop and what we're going to need to do is we're gonna need to define a way to uh turn off uh running right so let's um at the top here uh we're just going to do things in a dirty way so in when you're using modules you can use this module keyboard with just a semicolon with no thing and you can put any defines you need here or any includes as well any old hash pragma pragma functions you would put here now we're just going to do this for simplicity right so let's just say we say exit okay exit thread is going to be -1 and we'll say define we could have done this as an enumerator as well but we're going to do it this way just [Music] for simplicity i guess and let's just say print bananas is going to be our second command and that's going to be command 1. so these are going to be the commands we support for now um like i said it should be in the numerator uh ideally or in something a little bit better than just defines and integers but that's not the purpose of this lesson what we're going to do in while running is we are going to maintain a queue we're going to pull from a queue and then we are going to execute the next command in the queue and it's always going to be first in first out so we're going to write we're going to implement a fifo queue that executes in a thread um yeah so what we're going to do is we're going to import uh the queue which is going to be dq i believe like that and then we're going to create the q here good uh dq command q and that we can give a default constructor so um when we restart our command q we're also going to our command thread we're also going to uh clear our command q so that we if we know we just know that if we are restarting q we should flush the queue just to make sure that there's no old commands inside and what we're going to do in here is if the queue is empty we are going to just continue uh if [Music] command q dot size equal zero continue we'll do that otherwise we are going to uh execute the command so we're going to oh our dq here will be uh commands like that so we are going to execute command which we haven't implemented yet but we'll get that in a second command q dot front so we'll get the thing at the front of the queue and then we are going to uh pop it after and we will pop it off the front of the queue once we're done executing it so that makes sense that's pretty much our entire uh thread loop here um but we just don't have our execute command function so let's implement that and this is going to be fairly simple as well so it's going to be another private function called void execute command it's going to take a command and let's implement that let's just wait for uh let's not wait for intellisense to catch up [Music] because clearly it's having issues i'll just do it like this and we're going to say execute command command all right so to execute our command what we're going to do is we're just simply going to put a switch statement inside here switch command case exit thread right here and case uh print bananas print bananas and we'll put another break here all right and i think that's yes that should do it that should do it now let's just make sure it builds and see what happens and it all builds fine so we're good all right so now that's all built uh let's finish writing this so exit thread is going to be simple we're just going to call running equal false here and what that's going to do is it's going to execute the command and it's going to say running equal false and then it's going to fall out of the function now it might be worthwhile um putting a while loop here and making it not equal zero um that will make sure that the entire queue gets executed before it exits but i would imagine if we're doing a fifo and exit comes before commands it should not execute any commands after a request to exit so let's assume that's fine and print bananas we're just going to say bananas and give it a new line all right so that's most of our our queuing task manager complete so when the thread is run when we call start it's going to check if the thread is already running if it's already running it's just going to do nothing it's going to be a no op but if it's not running it's going to clear the command queue and it's going to run the call it's going to start a new thread with the run function uh running on its own thread which is here then the run function is going to run a while loop that's going to run until we call the exit thread command that's pretty simple now there's going to be some problems with with this but we'll we'll uh we'll get to that and more um a little bit later in this lesson and then we're gonna go more in depth in future lessons as well so what's left to do is to implement our cue command function uh what we need to do is do if not running so if it's not running we are going to print a message stood couch task manager is shut down please call start again to begin queue and call that and then we are going to return from here and then we'll move this down here and then this one simply after we check to make sure it's running we just call it command q dot push back command like that so we have everything that we technically need to run our thread so let's let's run that now so uh what do we have here so let's uncomment this stuff and in theory this should work right and yes now there's going to be an issue if we call minus 99 now but that's what one of the things i want to show you so let's do this now thread starting now we give it a command so if we give it anything other than let's say we give it zero it's you'll see here now it's queuing the command if we give it eight doing that but if we give it one it prints bananas right and if we give it minus one thread shutting down and if we give it try to print bananas now it tells you that the task manager is shut down and we need to call start again so that's pretty much everything but now let's say we want to quit the application with our minus 99 you see we get an error now what this error is is basically you shut down the application or you're trying to shut down the application but you still have a thread running in the background you don't know if that thread has been cleaned up properly or if there's data that needs to be saved out before it quits and all that that's one of the things you need to take care of it's one of the reasons that we gave um we created a destructor up here so it's actually fairly simple so i showed you um the join command before right which basically says that it's going to wait for a thread to finish before continuing on the function so and you need to call that the join function from the original thread that from the original thread that created the thread that is shutting down that you're joining so um to make it a little bit clearer when you run main this is going to be on one thread your main function here is going to be on one thread when we get here and we call start we're creating another thread and when we get to the destructor of task manager that is actually going to recall the call the destructor on the main main thread as part of the stack unwinding process so what we need to do now is to in our destructor um wait makes ensure that uh we wait for the thread to be complete before we continue so we'll say running you go false this will force shut down this loop here which will shut down the thread then we will check to see if it's joinable if the command thread is joinable so what this will oh that is joinable just joinable so what this will do is that if the thread is still running it will fall into this if statement if the thread was previously stopped it will actually just skip it all together and everything will work fine and we'll do command thread dot join and what this will do is it will wait for the command thread to finish so if you've got a command in flight so you've called execute command you've got some really expensive database command in here and it's saving stuff out it's going to finish and fall out of this while loop before finishing the destructor which will give it time to clean up gracefully and that should be everything we need to fix our minus 90 our minus 99 problem here so let's start that again right and let's say thread starting give me a command minus 99 and there we go no errors that is precisely what we want now i am mildly confused so let's do this um let's comment this out and go back here now i'm mildly confused because if we go to we say -1 that shuts down the thread right but then if we do 99 now we still get an error even though the thread is already shut down so let's just say here still joinable minus one shuts it down minus 99. so that's odd so if the thread exits normally it's you still need to call join on it to properly shut down the thread to clear its resources it won't just clean itself up it's good to know that's good to know because it means that this is really important so now you can prob you can probably do this put the running equal false in here then and close this right and start thread.joinable and exit thread running equal false so what this needs to do then so if running equal false then not running then we should command thread.join and then you know we're just going i'm just um we're learning together here um thread finished but wasn't joined joining to clean up then else one of the rare times you'll see me use an else statement there we go so now it'll check to see if it's joinable if it's joinable and it's not running then it means that it should be um that it's already been shut down but it wasn't cleaned up properly it had never joined if it's running then it's already running we shouldn't do anything so that makes sense now we've um we can implement a restart command inside here so let's say case 98 and this will be restart task manager and we're going to say task manager dot start call break here okay so let's try that and see what we get all right so if i call minus one now and then we call minus 98 yeah we have thread finished but wasn't joined joining the cleanup and then we thread start the thread that's exactly what we want and then we can give it more commands like bananas then we say minus one shut it down we call one task manager shut down excellent that's perfectly what we want [Music] um task manager let's all right so doing control c will always give you an error and that's you need to you need to implement a signal interrupt to fix that so we're going to ignore that for now let's just assume that we always shut down gracefully with our minus 99 here so let's go here and here we have while running is true cue command this is using running ah okay so that's that's what we have here we have a task manager which we can queue commands in um so here let's create one more command and this is going to let's say define uh print a bunch of numbers and we'll make that command two and put that case inside here print a bunch of numbers and 4 and i equals 0i smaller than 1000 i plus plus just good count numbers all right let's do it like that i should have used stood format but this is easier to remember how to do easier to do so now we can send it a command uh two i believe with what i gave it and that'll start printing a bunch of numbers we can print bananas in the middle i believe so let's see what happens thread starting so let's say we get two and two one two one now let's see what happened there so you see here we have cueing command one so cue to command mid command and then at the end it called bananas right so let's make this bigger let's make it ten thousand and uh let's so let's say two one one one one one so it's going to print 10 000 numbers like this and then at the end it's going to print all the bananas that iq'd that's precisely what we wanted so let's say minus 99 shut down the thread game blue bending exactly what we want so i believe that's pretty much the basics of using threads now there are a lot of things that can go wrong here like i said so in general when you have a an object that's using a thread like this that's maintaining its own thread or thread pool you always want to make sure that you're clearing the the joining the threads in the destructor so it's a resource acquisition is initialization or whatever rai pattern is a good way to go about it you want to make sure that just like files when you call open you want to call close if you you're using a thread that you're maintaining control of you will always want to call join now one thing you can do if none of this matters to you if you don't need a handle to command thread you can have as many of them running as you want you can there's one command you can make i call thread detach now you can only call detach on a thread that is uh joinable so to get around this issue here what we might be able to do and let's just see if it'll let us do this it might not what you might be able to do is in our run command if we do here we might be able to do command thread dot detach now let's see what happens this may or may not work so let's call minus 1 there and now if we call start again which i believe was 98. before when we called start it would be joinable so it would join the cleanup right but now if we call minus 98 you see it just starts the thread without worrying about whether or not it had already been joined which is actually an improvement so that gets rid of this if statement and we can close that so basically what this does is once the thread completes at the end here oops let's uh close this minus one i don't know minus 99 there we go all right so once we do this uh what this will do is make it so that once the thread is closed we don't have to worry about it so we can actually just do run it run equal false and we can probably get rid of this and it should probably give us the same result now without giving us our errors so -1 uh shut down we give it minus 98 it starts up and if we say minus 99 we get our error so um that didn't fix the error because it goes too quickly so this this this is useful for starting and re starting and stopping your service but not so much if you need to um clean it up at the end so i would recommend keeping your your joinable join commands here just to be safe even though i think this might actually throw an error let's see what happens here it started -99 yeah so this throws an error because [Music] it never join never never you can never join after calling detach so what if we do it like this just playing around now let's see and we get a different error so we can probably do thread dot detach that work and this is going to give a race condition oh it worked so i guess you can call what happens if you called attached twice on a thread like this so let's do -1 it gives an error [Music] so we will do if command thread dot joinable and we will detach that okay so this will this puts in a couple guards and this is clearly um probably not so the kind of thing you would want to do um doing it both inside your run command and inside here calling detached in two different places it's probably not a good idea the best idea is probably so maybe maybe this is okay um for now yeah we'll do it like this and actually we can this is the same code basically so what we could do really is create a shutdown method here shutdown let's define our shutdown method here we are going to do this and put this inside here and then we are actually just going to call shut down here shut down and we are going to call shutdown up here as well now in theory that should remove the need to take this out because it will detach as soon as we call shutdown and i believe that that's going to solve our issues so let's go minus 90 minus 1. that worked fine minus 99. that worked okay let's do the other way around minus 99 and no error so that's that's the better way of doing it so our shutdown will check to see if the thread is still joinable if it's joining joinable we're going to kill the thread and detach it just so that we don't throw any errors on exit because we know that it's going to naturally run its core course throughout now again without ranting too much more this probably won't scale once we get the thread pools and stuff we're going to get a little bit more complicated but this is just to show you a general idea of how you're going to start working with threads how you're going with the different things you need to think of when working with threads making sure that everything closes properly like a file like if you have an open you need a close on a file if you open a thread in general you need to either join or or detach your thread and it shows uh a general queuing system as well so let's let's show you show off the cue one more time so let's uh we're gonna cue that and we're going to put a whole bunch of bananas now i just built i'm just building a whole bunch of cues there and you see here it printed bananas a whole amount of time on that cue without interrupting the process of printing out everything on that cue so what this does is it allows us to take input from one area and then up and then use that input to affect a process somewhere else so you'll see this with input as i said web servers use a lot of threading render loops in game engines will often have their own thread or at least where how they build their render commands will often be threaded physics will usually be on its own thread so knowing how to use threads is very important in the modern day so that's why we're going through it anyways um i think that is good for now we don't need any more contacts for now we are very early on into this book c plus plus concurrency in action so if you'd like to see get to the next topic on threading earlier uh let me know otherwise um we'll see i don't know what i'm gonna be doing on the next one kind i'm kind of jumping around as i i i want to gain more knowledge or gain more practice myself uh through different things so this was a phonics experience experiment to build a little task manager system when we get to thread pools that's going to be even more interesting and trying to scale your number of threads based on your hardware that's going to be another thing we're going to look into at some point and a bunch of cool things like that so again leave a like subscribe all that crap comment down below uh what you like um seems like i'm getting to a thousand subscribers fairly soon so um there's going to be a deviation from my standard tutorial content for a subscriber special so if you want to see that happen earlier rather than later hit that subscribe button because it'd be cool anyways see
Info
Channel: Ozzadar
Views: 464
Rating: undefined out of 5
Keywords: c++ thread, c++ thread tutorial, c++ threads, c++ threading #1, std thread c++, multithreading in c++, c++ 11 thread, c++ queue, thread queue, c++ tasks, c++ tutorial, c++20, cpp20, cpp threads, cpp thread, c++20 tutorial, cpp20 tutorial, c++11 thread, cpp11 thread, c++ fifo queue, modern c++, modern cpp, multithreading in c++ program, c++ threads tutorial, c++11 threads, modern c++ tutorial, c++ tutorial playlist
Id: QUNVP6x8trs
Channel Id: undefined
Length: 52min 8sec (3128 seconds)
Published: Fri Jul 23 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.