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