 Right, so hey guys and welcome back to another Python tutorial. So today's tutorial is a lot different to what we've been doing recently because in today's tutorial we're going to be talking about synchronous and asynchronous functions and how they work or the execution types and we're also going to learn how to use multi-processing to pretty much run two functions at the same time. So synchronous basically just means that a program runs in the order of the instructions. So as usually Python does, if they give it a couple of instructions it runs the instructions in a row. So let me open up Visual Studio really quickly to explain what I mean. So if I give Python a few instructions it does print hello and then if I do print hello again and then if I run this quickly this execution of code right here would be an asynchronous type of execution because basically what happens is it runs the code line by line. Now in asynchronous type of executions what you can do is run functions or run specific code at any stage of where you want it to be run. So technically it's a lot harder to explain but if I put it in a simple way it just means that you can run a function or multiple functions at the same time or at specified times. So instead of just following the flow of instructions from line one to whatever line it ends on. Cool. So since we kind of understand how that works what we're going to be doing now is understanding how we're going to be using multi-processing to actually understand how to run two functions at the same time. So what I'm going to need to do is import time first because we're going to be using time to actually figure out how long the functions take to execute. Now when we run the functions in a synchronous method it should take way longer than when we run it in an asynchronous method because when we run it in asynchronous it pretty much runs at the same time whereas in synchronous it just runs one by one. So that's how we're going to be proving our point and we're going to be needing multi-processing which is a standard library that's installed on pretty much every Python 3 version so you don't need to install anything. Cool. So what you want to do first of all is create a function called I'm just going to call it do something. Let's just call it function I'm going to call this sleep for a bit, a weird function name but hey that's what the function is going to be doing it's going to be sleeping for a bit and then we need to give it a parameter of how many seconds we want it to sleep. So I want it to sleep for so and so amount of time that's the amount of time I'm going to enter as a parameter when I run this function. So what I'm going to do is I'm going to type in print and then pass in an F string and then say sleeping and then I'm going to use my seconds variable which is going to be passed to me later on when this function is run and then I'm going to type sleeping for so and so seconds and then second brackets S. Cool. And then once it's done sleeping we're actually going to use the time dot sleep to make it sleep so and then we're going to make it sleep for the amount of seconds that is specified by the user and lastly we're going to say done sleeping. Cool. So literally all this function does is it prints out how many seconds the user wants it to sleep. It sleeps that amount of seconds and then when it's done sleeping it will say done sleeping. Cool. So in a normal scenario if you wanted to run this function at the same time you can't really do that without multiprocessing or threading or any other module that does the similar, pretty much has a similar functionality. So if I try to run these two at the same time this is going to be my approach. So technically it's going to run it once, one at a time. I can prove that by typing in obviously I'm going to sleep for one second. Let's sleep for one second and I'm going to run these two times. So this is going to run the synchronous method because it's going to go line by line. It's going to go from line one to pretty much line ten which involves running this first and then this later. So if I run this now this is a synchronous execution of it. So it says sleeping one second then done sleeping then it's sleeping one second and then done sleeping again. So it's pretty much done a line by line execution of code which is a synchronous type. So what we're going to be doing is learning how to make this an asynchronous or just running it at the same time. So we're going to be needing to first of all create an object using the multiprocessing library. So create a variable to which we're going to be assigning the object. So p1 equals multiprocessing and then we access the method or class. So multiprocessing.process and then in that you need to specify the target which for me is going to be sleep for a bit. That's the name of the function I want to run. And then I need to pass in the args or arguments attribute which is going to store all the arguments that I want to pass into this function. Now this needs to be passed in as a tuple so I'm going to do a square bracket and a closing square bracket and then I'm going to type in one because I want it to sleep for only one second. Now I'm also going to create another process which is going to be called p2. So process one and process two they're just abbreviations. And then in here nothing else changes so it's just p1 and p2. Now this is kind of like cached into these variables so these variables know that this is like a process to them now. Now at this stage nothing's going to happen. We actually need to go ahead and run these two processes at the same time. And before that as a standard requirement to using multi-processing we need to use the if name equals main approach. So we're going to do that. And then it's just a requirement that multi-processing has. If you don't use this it's not going to work. So we type in that and then we type in p1.start. We're just going to start our first process and then p2.start which is going to start a second process. Now since we want to track the time that this finishes on what we're going to be doing is typing up a new variable called finish which is going to be at the same indent as the rest of the code. And we're going to be assigning that to time.perf counter which is pretty much going to return the execution time of this whole program until where this variable is. And then we're going to print out finish, finish, running after. It's usually going to take seconds for this program to run. So I'm going to say finish running after plus, oops, plus. Let's just comma and finish. Yeah that should be the trick so after seconds. So finish running after seconds and it's going to say how long it took to run. So let's run this now to see if it actually works. So let's run it up. And as you see right here what happens is it says it took about 0.72 seconds to actually execute the whole thing. Now you might be saying, hey, there's an actual sleep of one second in these functions right here. So why is the time only literally one third of the sleep time? So that's because what happens here is that this, if I go back to my code. So when the program goes through all of this code, what it does is it's going to skip these lines here and then actually go on to the next line and then print out the finish time before it actually has run the two functions which is why we kind of get an inaccurate time. So for it to actually go in the right order and run this stuff first and then this at the end, what we need to do is type in p1.join and then p2.join. So this way it's going to give us accurate results. So let's run this up now and if I can show you now, it literally says at the end finish running after 1.15 so and so seconds. So as proof, technically these ran at the same time because if they had run at different times, they would have taken two seconds and a bit more of time because technically each function sleeps for a second. So since the fact that they've actually run in one second, ignoring the decimals obviously, they've technically run in one second which means both of them will run at the same time. So that's our asynchronous done. Cool. Now just to prove my point about actually if we did it the other way of synchronous, it would take two seconds. Let's try doing it that way. So if I just commented this out and I did it the old approach where I do sleep for a bit, one second, sleep for a bit and another second, it should take two seconds and some decimal places to run this entire program. So let's see. And as you see right here, it finished after two seconds and a few more decimal places after that. So the previous method that I showed you using multi-processing pretty much does it in one second and a few decimals which pretty much proves my point of running two functions at the same time. So the way that it does it is using the different cores that your CPU has. It's pretty clever the way it does it. So I'm going to uncomment this now. Now, if you want to actually see the improvements that this does, you'd probably use this when you have to have a lot of functions pretty much running at the same time. So these are just two functions we're running at the moment. What we had about let's say 10 functions of these and each function was sleeping for one second. Now imagine how long that would be. That would take about 10 seconds for the whole program to execute. Whereas if we done it all at the same time, it would take about a single second. That would create a major performance improvement than having to wait 10 seconds for the entire program to execute. So let's actually do that. Let's run 10 of these processes instead of running just one. So for this, instead of taking the dumb method and the longer method, we're actually going to write a loop and use an array. So I'm going to get rid of this code right here. So from here, let's get rid of this. And I'm also going to get rid of this. Cool. So what I'm going to do now is I'm going to create a variable called processes. And then I'm going to assign that to an array. Now, this array right here is going to store all the processes that we're going to be running together at one time. So we're going to need a for loop. And we're going to say for x in range 10, which pretty much means that this loop is going to run 10. And all the instructions inside it are going to be run 10 times. What we want to do is we're going to grab this line right here that says P1 equals so and so. And then we're going to paste it in our loop. I'm going to take off one because it's just a process now. So it's P. And then we're going to put this in our loop. Now, technically, this is going to run 10 times. Right. So once we're done with that, what we want to do is actually type in if name underscore underscore equals underscore underscore main because it's a requirement to pretty much run the multi-processing module and start the processes that we have. What we want to do is P dot start, which is pretty much just starting the process that we have going. Now, at this stage, you might say, hey, are we done yet? And can we just do P dot join over here? And my answer would be no. Because if I run this now, it's pretty much going to take its sweet time. So as you see right here, it's waiting for 1.5 seconds before it's actually done anything else. It's literally going to take about 10 seconds for the entire program to run. So that's obviously not how we want it to work. We want it to finish in one second because technically a bit more than one second because I'm waiting for 1.5 seconds. Let me take that off and make it one second. So we want it to finish in one second because we want to take all the 10 processes, start them at the same time. Now, if you want it to work like that, what you've got to do is you've got to take each of your process that you're starting and then append it to your process list that you created earlier. So process dot append and then you pass in the P, which is the process reference or the object reference. So once you're done with that, what you want to do is go ahead and create another loop. So for P in processes, what you want to do is type in P dot join. So it's going to literally grab all the processes that you've stored in your processes array right here. It's going to grab all of them one by one and then join them individually. So when it does that, it pretty much knows that it wants to wait for all of these processes to be run at the same time first and then it's going to run these functions right here or these lines of code right here. So if I run this now, as you see right here, it took about 1.22 seconds, which is perfect because 22 seconds, we can ignore that because technically the other things or the other instructions such as imports take a bit of time too. So as I've proved my point, this would save you a lot of time if you had to do a lot of processing at the same time, such as running 10 functions or the same function 10 times at the same time instead of running it at different times or synchronously, which would pretty much take forever. So this function synchronously would literally take about 10 seconds because it would sleep one second, then run again, then sleep one second and blah, blah, blah. Whereas in asynchronous, it can just run in one second because we're using the cores of our CPU to pretty much run these 10 processes at the same time. Anyway, guys, this is the old way of how we do it using multi-processing. In the next tutorial, I will introduce you guys to the most recent and new way of how this can be simplified and we can receive the same outcome. So I hope you're excited for the next one. If you guys would like to support the channel, you can do so by signing up as a patron using the Patreon link in the description. You can also buy a Super Chat emoji or a highlighted message in the chat. I'm not forcing anyone to do so, but if you'd like to do so, that really helps out too. Also consider following up my socials and my Discord, which is going to be stated in the description for a load of fun. And guys, I will see you beautiful faces in the next tutorial. Peace.