Next-Level Concurrent Programming In Python With Asyncio

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
modern software regularly interacts with an api a database or a file that means there's a lot of waiting and you need to make sure that your software handles that efficiently if you don't your application is going to be much slower and the more data you process the more you interact with apis the worse this is going to get the way to fix this is to rely on concurrency in python you use the async io package for that i'll give you a brief overview of how the package works but then i'd like to go a bit deeper and also show you how to turn a regular blocking function into something you can run concurrently which could make your program a lot more efficient i don't even have to modify the original function for this it's really easy i'll also talk about how concurrency affects your software design and architecture so make sure to watch this video till the end if you want to learn more about how to design a piece of software from scratch i have a free guide for you you can get this at ironcodes.com design guide contains the seven steps i take when i design new software hopefully it helps you avoid some of the mistakes i made in the past ironcodes.com design guide and the link is also in the description of this video you may have heard the terms concurrent and parallel computing before but what's the difference true parallel computing means that an application runs multiple tasks at the same time where each task runs on a separate processing unit concurrency means that an application is making progress on more than one task at the same time but may switch between these tasks instead of actually running them in parallel if an application works say on tasks a and b it doesn't have to finish a before starting b it can do a little bit of a then switch to doing a little bit of b back again to a and so on this answer on stack overflow nicely illustrates the difference concurrency is two lines of customers ordering from a single cashier and lines take turns ordering parallelism is two lines of customers ordering from two cashiers each line gets its own this year if you translate this back to computers each cashier is a processing unit a cpu core each customer is a task that the processor needs to take care of modern computers use a combination of parallelism and concurrency your cpu might have two four eight or more cores that can perform tasks in parallel your os will run tens to hundreds of different tasks concurrently a subset of those tasks are actually running in parallel while the os seamlessly switches between the tasks parallelism in python has caveat which is the global interpreter lock anytime you run python code it needs to acquire a lock on the interpreter there are reasons for this that i won't go into in this video but effectively means that python code is single threaded even if you stop multiple threads there are ways around this for example by relying on multiple processes instead of multiple threads or by switching to an interpreter that doesn't have to lock this concerns parallelism though concurrency on the other hand works really well in python especially since version 3.10. why is concurrency a smart way to do computing well it so happens that many tasks involve waiting or applications are waiting for files to be read or written too they're constantly communicating with other services over the internet or they're waiting for you to input your password or click a few buttons to help identify traffic lights and recaptcha i hate those things it considerably speeds things up if a computer can do something else while waiting for that network response or for you to finish cursing about recaptchas in other words concurrency is a crucial mechanism for making our computers work efficiently in this age of connectivity the async io package in python gives you the tools to control how concurrency is handled within your application as i've talked about in a previous video the async and weight syntax is the mechanism to achieve this if you write async in front of a method or function you indicate that it's allowed to run this method or function concurrently a weight gives you control over the order that things are being executed in if you write a weight in front of a concurrent statement this means that the portion written below that statement can only be executed after the concurrent statement has completed being able to do this is important when the next part of your code relies on the result of the previous part and this is often the case you need to wait until you get the data back from the database or you need the confirmation from the api that your user is logged in in order to continue and so on i want to start with a quick recap of how concurrent programming in python works so for this you need to use the async and weight syntax i have a simple example program here that retrieves pokemon names so i'm using a free api here to do this so as you can see here is a synchronous version of that code it's a function get a random pokemon name that picks an id between one and the maximum pokemon id that's available we have the url and we construct it using this pokemon id and then i'm using a function http getsync this function i made myself i'm going to explain later on how this works exactly and then we return the name of the pokemon as a string here we have an asynchronous version which does exactly the same thing but uses http get which is another function that gets data yeah using a get request but this one works asynchronously this one works concurrently and it also returns a name currently i'm using the non-concurrent version of this code this just gets a pokemon name synchronously and then prints that name and when you run this then this is what you get so we now get a nice pokemon name randomly selected changing the code to use the concurrent version is really easy in this case what we need to do is we need to change main into an asynchronous function as well like so and now that we've made the main function asynchronous we can add an await and then of course i'm also going to have to remove the underscore sync here like so and now main is asynchronous the only thing we still need to do is to make sure that main is run asynchronously and that's by using the asyncho dot run function there we go and now if we run the code again we should get exactly the same result except of course now we get a mew 2 which is a different pokemon now for the moment there's not a big advantage in using concurrent programming here because we're just doing a single http request but suppose you want to do multiple requests instead of only one so the way you could do that is for example by running a for loop so now we're running a for loop oh i should remove this column here and put it there so now we have for loop that retrieves a pokemon name 20 times and prints out the name and then this is what happens it takes quite a while because every time we're launching a new request we're waiting for that request to complete and then we call the next request so this takes a few seconds now this is where you can really benefit from concurrent programming because why do we have to wait for every request to complete before we can send out the next request obviously it's possible that the api that you're using has a rate limiting factor so you can't send thousands of requests within a single second but you can definitely batch things so you don't have to wait every time and this is where we can rely on the possibilities of async io so let me write an alternative to this for loop using asyncio.gather so what i'm now going to do is provide gather with a sequence of get pokemon name calls and i'm going to use a list comprehensive for this and then unpack it and let me use the same kind of for loop inside that list comprehension so then this is what we get and now we write a weight in front of it and then let's have a result so now the result is going to be a tuple of strings and then we can print this as well like so so let me put this into comments like so and now let's run this program again and as you can see it's now much faster let's take a look at the time to see how much faster this actually is so from time i'm going to import the performance counter function and then let's store the time before and then let's print the total time in the synchronous case and that's going to be performance counter minus the time before there we go and let's also do the same thing for the gather option so i'm going to copy this line paste it here and i'm also going to copy this line and paste it here and this is going to be the asynchronous version so now if we run this code again then this is what we get so the synchronous version took over two seconds and the asynchronous version took 0.2 seconds so that's a 10 times increase in efficiency and all of that happens because we're just sending out the requests all at once instead of waiting for every request to complete async and await are actually pretty well integrated into python especially since 3.10 let's take a look at a few things you can do with them one thing you can do is combine the async and await syntax with generators so for example let me write a function that's called next pokemon which is going to give me the next pokemon from a range of totals so next pokemon this is getting one argument which is a total and then this is going to give me an async iterable object and let's say that returns a string we just want the pokemon name so what this does is we put the for loop in this that's going over the total range range there we go and the name is await get random pokemon name and then we're going to yield the name so this is a generator now in the main loop it's really easy to retrieve the next pokemon names like so and let's say we want the next 20 pokemon and we're going to print the name and let's just leave it like this and now let's run the code and see what happens so as you can see asynchronous generators work but it still calls these things in order if you want a different behavior you need to use gather like i showed you just before another thing you can do is create asynchronous list comprehensions and that works in exactly the same way so instead of doing this for loop here what you then do is names equals and then we're going to create a list comprehension name async for name in next pokemon 20 and then let's also print the names like so and now when we run this code as you can see we get more or less the same timing so it's still doing these in sequence but now we have them in a list and again as i said if you want different behavior use gather that's going to run them concurrently another thing i'd like to show you now is how to turn non-asynchronous code blocking code into code that you can run concurrently by the way if you're enjoying this video so far give the like let me know if this is helpful to you in the comments so i have here another example which has a couple of functions in there there is an asynchronous counter function that starts a counter and then goes to a range there is a sleep function call so this simply tells the interpreter to to wait in other words you can run other tasks concurrently and then it prints out what's asleep for a number of milliseconds there's also a send request call that sends some http request to a url and returns the status code and my main function which is an asynchronous function then sends that request to rmcodes.com which is my website and we get an http response with the status code and then it awaits the counter so if i run this then this is what happens we get the hp request status 200 after we get the response we start the counter now what should you do in order to make these things run concurrently problem is that send request is not concurrent code at the moment it's blocking so one way you might think you could fix this is by using the async io create task function so we already create the task that's create task and we're going to provide it the call to the counter function and then instead of awaiting the counter here we're just going to await the task here and then maybe it already starts the task and does this at the same time right so let's try this and see what happens so as you can see it didn't change anything we still first have to wait until we get the http response and then we start the counter so that was not the solution another thing you might want to try to do is to use async asyncio.gather like we saw before but that also doesn't work because send request is not concurrent it's not asynchronous what we actually need to do in order to solve this is to turn send request into an asynchronous function and there's a very simple way to do this with async io and for that we're going to use the to thread function so let me create another function here async send async request so this is going to be a wrapper around the synchronous send request function so this is getting a url and this returns a hint in this function we're going to return async io dot to thread which is the function that we're going to use to turn send request into an asynchronous function so going to provide to thread with the send request function and we're going to pass the url as an argument to the to threat function as well so it passes it along to the synchronous function what this does is that it creates a separate thread in which to run that particular blocking task in this case the call to the send request function and then you can use it as part of a concurrent program so then here we're going to call the send async request so we put an await in front of that and now we've turned this into an asynchronous program one thing i forgot is that we also need to write a weight in front of this because we're awaiting the two thread function so now when i run this code again you see that we start the counter we send the http request and we're doing these things concurrently so that works now and now because we have two asynchronous functions we can also use gather again to do the same thing so instead of writing this i could also write something like this so we're gathering the two function calls and awaiting their result the second one counter doesn't return anything so i'm simply putting an underscore here and then when i run this we get exactly the same result when i started explaining the pokemon example i used an http get an http get function that i said i coded up myself so what does it look like well that's in this module and these functions are actually really simple so i'm using the request package to do http requests so i have a synchronous version here that calls the get function of the request package and then returns the response as a json object and i have an asynchronous version that uses async io.2 thread that calls this function but then turns it into something that you can run concurrently so as you can see this is really easy to use really easy to set up you can use this kind of approach whenever you need to interact with an api and it's going to help you make your code run much more efficiently because you can group api calls which will save you a lot of time waiting for the result if you don't want to write a synchronous code for doing these kinds of api requests you can also use another package for that that's called ao http which i'm using here in this particular example so here i have an alternative of the http get function that uses ao http creates a session and then the session has a get function that you can call and then it returns a response as a json value just what you see here you also see an example of using async with context manager which is also possible so in this case creating the session is asynchronous and the get method is also a context manager that you can run concurrently using the with statement here i have a confession to make i don't really like these nested with statements so unless you really need to use the session for some reason if you just want to do simple get requests just use the two thread function to do it in the way that i just showed you it's much simpler how does concurrent programming change the way design patterns work in principle not at all due to the very clean await async syntax there is no effect of coupling or cohesion by introducing a synchronous code into your application for example if you want to use the strategy pattern you can create an asynchronous method in your strategy class and then call that and await it if you want to use the factory asynchronously no problem create objects asynchronously if you want to the basic pattern doesn't change just parts of the pattern become available on the architectural level concurrent programming might have some impact depending on what you do you can imagine that if you're processing data in a pipeline that relies on asynchronous operations which is quite common you'd want to adapt the data structure so that it allows you to specify what to run concurrently versus what to run in sequence i gave an example of this in a previous video where i showed you how you can create a nested structure of sequential and concurrent function calls if you'd like me to go into more detail on this in another video let me know in the comments below as you can see there are lots of ways you can use concurrent code in python to write more efficient programs i hope you enjoyed this video if you did give the like consider subscribing to my channel if you want to learn more about software design and development thanks for watching take care and see you soon
Info
Channel: ArjanCodes
Views: 111,884
Rating: undefined out of 5
Keywords: concurrent programming, concurrent programming in Python, python tutorial, asynchronous programming, python programming, learn python, python await, learn python programming, python tutorial for beginners in english, python tutorial for beginners, synchronous programming, python tutorials, python asyncio, python programming tutorial, asynchronous programming python, python tutorial advanced, python tutorial 2022, asynchronous programming with async and await
Id: GpqAQxH1Afc
Channel Id: undefined
Length: 19min 18sec (1158 seconds)
Published: Fri Jun 17 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.