 Can everyone hear me Yeah, great. I think there was supposed to be one of the organizers here to announce me But I don't see anyone and it's time. So I guess I'll just start Yeah, so when I started looking into the sync IO I noticed that a lot of people thought it was quite Complicated to use and especially to get started with it So I gave this talk the soft title do you need to be a wizard to use it and the answer is a little bit It's not that hard, but when you're just getting started there are quite a few problems that you'll run into and Well concurrency is intrinsically complicated. So it's not anyone's fault, but there are a lot of new concepts there are things you need to learn that are not just standard Python and It has a very long history all the way from the back porch to 2.7 to the new keywords in 3.5 the syntax has changed a lot and the things you do have changed a lot So when you try to figure out how to do something and there's not one Way to do it. There are different ways in different versions and they will change quite a lot and sometimes the documented best best practice you will find is maybe deprecated now so Yeah, that can be a problem and if you don't know the internals some things will just be weird but you get used to those quite quickly and Fortunately, the code is quite easy the async.io code and did have a squad easy to read so you can look at the internals and figure those out and There's a lot of incompatible terminologies like Core routines are generators except when they're not generators and not all generators are core routines and You know, you have API's like future that Implements the same API, but it's not you cannot really use them interchangeably and you'll run into trouble with those things and Since it's so new there's this bottom-up approach to learning async.io. So you want to It's very common that you start Understanding first core routines that were first generators core routines and have to understand all the internals before you actually start writing code with Async.io So my idea was try to simplify this and try to go through how I've learned async.io and how I started writing things and the errors I Run into and see how other people can start writing code with the sync.io from this and I work at a web agency and I came to async.io because sometimes the things we have to do are Concurrent in nature and I've noticed that some of my co-workers just started going to other languages mainly go and say Well, this is too complicated to do in Python I'll just write it and go create a service and then we can call the service to do To solve this problem, but I Realize that Python just added this whole thing to do Concurrency that has changed the way concurrency is supposed to be done in Python And we were not really using that so I tried to see well Can we do this with the sync.io instead and some of the problems? We're trying to solve our things like creating a back-end service for a server for real-time with server scent events or web sockets and That would look something like this. This is a very simple echo server But it's not very far from something you could actually use to run a web socket server We could for example try to fetch data from several APIs concurrently Which I think is something that is very common Especially when you're doing service-oriented architecture You may have tons of APIs that you want to get data from and if each one of these takes about a hundred milliseconds And you want to query five to ten you might end up spending a whole second just waiting for IO Well, you could do this concurrently So this is quite easy to do with a sync.io. It would look something like this You don't have to pay too much attention to this example. So I look at them in more detail later You can do some sort of pipeline processing which is very similar to before Instead of just fetching the URLs you may want to process them afterwards. Maybe you want to put them in Redis Maybe you want to parse them as JSON and all these things you can do asynchronously and You could copy a site into disk again fetch a bunch of URLs then lock a file and write to that file or You may want to have a complex data structure that you want to have in memory and you want to access but you wanted every now and then this gets loaded from a store somewhere and And that would look something like this. This is just a sample code, but Yeah, it would work kind of in the same way and So as you can see all of these examples follow kind of the same pattern and that pattern is the concurrency model that we're going to be using and There are several models that you could use and different languages implement Different ones and they're normally a combination of different concurrency models and So I think I always basically a formalization of how we should do concurrency in Python Now I think there are two parts to concurrency models The first one is how we write and understand our code and I think this is the most important one this is the one that we should focus on and You can see that as an API if you wanted to write an API for concurrency You could come up with something like this. You could execute something in the foregrounds execute something in the background wait for a result or Schedule execution for things later and you can write some functions to do this And this is just like a pseudo code of something We may want to do if we wanted to have this concurrency model And if we did that concurrency model that would yield programs that look a certain way now a sink I will give us programs that look in the async IO way and that will be Unified model of how we do concurrency in Python so the other part of a concurrency model is when the code runs and It can run traditionally in threads and processes who can run in different machines if you use something like MPI Or you can run it on event loop and event loop is a big part of what the Async IO model builds upon and I'm not gonna look too much into this part of things because I don't think it matters that much I'm gonna look into the other one that is how is the code gonna look and how we're gonna understand that that code is correct And that's what we want from the point of view of concurrency and then these things might be interchangeable later so Let's start looking a little bit to Python's answers to these questions but before we do that I want to tell you what a sync programming in Python is not and That is just one thing and it's a solution to your guild problems. So if you Are writing code that runs a lot of Python spends a lot of time in the CPython interpreter You're still gonna run into the global interpreter lock and this is not gonna save you from it There are some projects like the galectomy and pi pi STM that uses software transactional memory That are trying to fix this in different ways, but you know, we'll have to see what they come up with but for now if you're gonna do something that is in That the guild is getting in the way then you'll just have to go and Run them in different processes like we've been doing for years So, okay, let's get started. Let's write some code But before we do that I wanted to Discuss this one just to get it out of the way and we said the things can run and threads and processes and That's fine. We can create thread pools and with thread pools We can then tell them to execute a process and it will look something like this But we can also run things on an event loop and this is the one that is new if we want to run something an event loop we first need to know what an event loop is and Anybody that's worked with JavaScript probably knows this and many of you probably do but in case you don't it looks something like this It's just a loop that will run essentially forever and it will pick a task and based on some policy and ask that Ask that task to be executed and that task will execute it until it decides to suspend execution and As you can see there's nothing here that is preemptive like with threads the task will execute and eventually that task will have to tell The loop that it has returned from function so that it can continue running. Otherwise, the loop will be blocked right there Once the task suspends execution, then it can reschedule that task decide if the task has a result and he wants to do something with that Result, maybe we want to store it. Otherwise, we just put that task back in our queue and select it again when we look back and If we want to use an event loop in Python, it looks kind of like this You have a function and you can define the function with the same keyword in Python 3.5 and when you Define a function that is supposed to run asynchronously that is The sync keyword tells the function that it's supposed to run asynchronously So that function will not execute unless a loop calls it and then we have the await keyword to tell the loop when it's supposed to suspend execution So then we get an event loop and we tell it to run that function until it completes and that actually works and If we wanted to specify we want to run these things on threads instead We don't write a synchronous functions and we take a normal blocking function that we want to run on a thread and We have this utility called running executor that will actually allow us to just run that function And it will return something that is a synchronous and that the loop knows how to handle So this gives us a flexibility of being able to write our code write code That will look the same way and then choose whether it's gonna run in threads processes or in an event loop and have everything Control through this event loop abstraction Now let's start writing some a sync code. What do we need to know? We need to know at the a sync and await syntax Which if we want to have a function that is Not a synchronous that just blocks then that's fine We just write a regular function and we call it whenever we need it if we want to have a function that runs an event loop And the can suspend execution then instead we use the async keyword and that function will not run by itself Inside that function we can use a wait and a wait will just it's very similar to a return Except when the function comes back it will continue execution from there So we tell the loop that we will suspend execution until some other Function or awaitable has finished and in this case it will be suspend execution or that executor where we're running the blocking function and What one of the advantages of this is that this way we can specify Explicitly when our code will suspend when it will execute So if we were to run this with threads the operating system will tell us when the function can Stop or when it can't so we have to be a lot more careful in terms of synchronization Whether here the function will continue to run until that function decides by itself that it will stop The other building block that we will need with this is futures. So a future is basically a wrapper That can you can check it has three methods you can check if it's done You can check if it has a result or you can add a result to it So when you have a future and you check if it's done It normally isn't unless you have added a result to it then if You ask for a result and it doesn't have it it will yield an exception. Otherwise, it will you can set a result and then you will get a result and Now we now we know this background information. So let's try to get a loop and start running things So we have a loop we want to create it run it stop it and schedule things on it so to create a loop we use a sink.io get event loop and that's it now we have a loop and It's not doing anything, but we have one Now if you want to run it you can just call run forever on it and it will do as it says It will run forever unless there's an exception inside the loop in which case you would just throw the exception Now if we wanted to stop a loop and I think this is a little bit confusing a Loop can only be stopped if it's running. So if you have a loop and you want to tell it to stop you will have to Ask it after you ask it to be marked to stop you need to ask it to run once Otherwise the loop will not be considered stop So if you want to stop a loop that is not currently running you have to ask it to run forever and it will Run once and then stop Which is confusing is the type of things that you have to look at the internals to understand how it works, but Most of the times you don't have to do this Instead what you do is something like this you want to run things on a loop instead of managing it manually So let's say we have this function that we want to run Something it just prints a bunch of numbers and it waits one second between each number So you ask the loop to run and to complete and that's it it prints as we expected it to it's just a little bit boring So instead what we would want to do it. We've been talking about concurrency is run something more like this we say well we Want to have two of those functions running concurrently and if we do that We can use this ensure future that will just create a future and attach it to the loop and We write an extra function because we only know so far how to run one thing So I say okay go loop and then when we execute that that would actually stop very quickly and give us an error And the reason this is happening is because we created these two futures, but we didn't really nobody's waiting for them So when the route lock the loop runs It runs the go function and it knows that will it will stop when the go function completes So that function creates one future it doesn't wait for it creates the second future doesn't wait for it And then it completes so the loop stops running and if you're debugging I think I owe you will get this nice helpful messages So instead what we can do is just a weight them and then we use a weight and we tell the loop well I want to run these two things, but I want to wait for them don't and go until they are complete and if you do that it will work But if you look at the result you can see that it's actually running one task first and the second task afterwards So it's not really concurrent. It's just sequential and you might as well just have written regular functions so instead what we can do is try to figure this out and Go back to the specification and see that we have a synco that as completed So we can create this Task that we want to run and then we ask a synco to give it back to us in a for loop in the order They complete and when one of them completes we get the future we wait for its result and Then this works except it's really ugly It's this doesn't really look very pitonic and it's very complicated for just running a couple of things concurrently So what we have instead is this I call them aggregators or the iter tools of a sync IO Where we can do things like a sync IO dot wait, which just will take a lot of core routines or a list of tasks and Not return until all of them have completed so if we run something like that, we're actually running concurrently now and It still looks quite ugly, but we can Realize that if you if we're waiting for a sync IO though wait Then that means that a sync IO though wait is an awaitable and that means that we can run that directly on our loop And then it would look something like this and this looks a lot more like an API We want to work with create a bunch of tasks and ask the loop to wait for all of them and execute them concurrently and Okay, I wanted to make an aside here and Remember the internals and how they can be complicated. We have also this function for a loop to run once instead of running Forever or until something completes and we shouldn't call it's an underscore function, but it's there So you call it and then you can see that you can run a loop step by step So whenever you're debugging a sync IO, you can just start running your loop step by step and printing stuff And you'll see what's actually happening Okay, but those are quite boring functions, they're just printing stuff So let's try to build something that retrieves some results so I changed the function a little bit now it's a countdown and Now it just multiplies the numbers as it goes down and returns a result So we create our list of tasks and we wait on them and we assume that the tasks are gonna have some information for us When we get back Thing is what we get is these coroutines and they're coroutines that have already executed So they're not very useful. We can't really get any information from them So we can try to go back and say well I remember futures were something that I could use for knowing the results of things that haven't happened. So We create in less futures instead and run that and yeah, now we have this task and the task is just Is Just a coroutine wrapped in a future that the loop is using Internally and that's good. We can access things from tasks. So we Iterate on over them and that's it. We get the results there. It's just we made this the googly again and so Instead what we can do is go back to our aggregator list and the sync IO which they're not called aggregators Or by the way, that's just the name I give them and We have this one called gathered that does very something very similar to wait Except it will actually return all of the results and since the loop returns whatever The function was running returns then we can just print those results and it does the same thing as before except It just looks a lot better now So, okay What have we learned so far? We have a sink and a wait we can create a waitable functions whose execution can be suspended and With a wait we can suspend until another waitable returns We have a loop that we can create we can ask it to run we can ask it to run something or run forever And we can stop it and we have this functions that allow us to wait for things so that we can run many things Concurrently and at the same time get their results after or just make sure that we wait for all of them So, okay, that's good with that. We can probably start writing some a little bit more realistic code and we can Write something like this. This is an example as I said before just fetching a bunch of URLs You want to get those URLs and you want to get them all at the same time Or concurrently and then just gather their results and you could do something like that and this works it will Fetch all the URLs and all the URLs will be there at the end The only thing is that Maybe we don't care about all of those URLs So we can try to do something a little bit more complex and say well, maybe I just care about the first URL to return So let's write something that wants to solve that so we can write the first completed and It's very similar to before so we just went back to this as completed idea that has a time out and We just wait for those For those core routines to complete and when the first one completes we just return that and that's it and this looks like It would do the work except when we execute it we get a lot of errors and The reason we this is actually returning you can see the HTML up there Then was supposed to be returned but then we're getting all this test that have been destroyed and loop that hasn't been closed and The thing is with I think I owe you have to do explicit cleanup You're supposed to close your own loops and clean up your own mess So let's start just by closing the loop manually so that it will internally clean up some stuff for us and that looks good Except at least we got rid of one error But we still have this test pending and the reason those tasks are pending is because if we Yes, yeah, if we if we go back to our original code, you can see that We're returning after the first task completes. That's not the one We're returning after the first task complete So the other ones are still in the loop and they're supposed to be executed But we're never waiting for them. No one is waiting for those tasks. So what we can do instead is Something like this we say, well, we're gonna execute do the exact same code Except we're gonna wait for all the tasks before this function returns and that works that gives us the right result The only problem is that this takes as long as the longest wait And if some APIs are slower than the others you get some they're returning one millisecond and some they return 100 milliseconds Then you're gonna get the first one. That's the result you're returning But then you're gonna have to wait a hundred milliseconds until all the other ones complete So instead what you can do is Something like this, I guess you start working with it. You write another functions I well now I want to cancel all my tasks and then within your cleanup you You first run your function that returns what you want great and on cleanup before closing the loop you Cancel all the existing tasks And yeah, that works It's not nice. It's not very bitonic anymore. I think so instead what you have is well if we go back to the API and We use wait before and wait actually has other parameters like a time out and return when and we can tell way to return when the first test completes and What wait returns is actually two sets of futures one for the test that have been completed and one for the test that are pending So we do something like that for the pending tasks. We ask them to cancel and for the completed one we just Get the first future and return that and this will work This prints what we want and get us the result we want the only issue is that this example is very well matched to what wait does so Yeah, if we wanted to return not the first completed, but maybe the first two then this doesn't do the work anymore So we would probably have to go back to something like cancel all or have to write our own function that deals with these things but it's good to have this The separate options that we Yeah, we can either do wait and then deal with the pending ones or we can just Write our function and then cancel the other ones Or we could cancel and manually somewhere else But yeah, so whenever we're working with the sync IO the Important thing here is we have to do cleanup and it's not always trivial and We have to close everything whenever we use Sockets readers executors everything has to be closed manually. We shouldn't expect the framework to close things for us And we should know where our tasks are So, okay Gonna Make this a little bit more fun before we end and now we have this first First completed API and let's say this is a very critical part of our code. We normally query hundreds of APIs and it's very important that we just get the first one quick and We might have something else in our stack that is not Python and wants to use this so let's just build a server that returns this and So for this we just we're gonna use the same code as we used before and Then we're gonna try to get a server that serves first completed So we go to our documentation we get the this is basically the Easiest server you can build this right from the documentation of slightly modified and Right now this is an echo server. It's like, okay So this runs in a sync IO it runs on a loop and if we want to Build our own function into it. We can just do something like this instead where This is exactly the same as the echo server. We worked on before we just changed this code here in the middle That is just calling first completed and waiting for it so And this works you can run this code and he will Execute he will run a server you can do net cap to it and you and the good thing is that it will Right right just from the example and adding a function there You will have a working server that executes and can Handle a lot of requests But if we wanted to do instead of using just plain sockets do HTML We can start looking at a sync IO libraries and look for example to into AIO HTTP again, just the same this is the Basic AIO HTTP server just hook our function there and We have a completely a synchronous server that is returning while running fetching things concurrently So, okay Is this an overkill? Yeah, probably it's a We don't really need to build a server for this But it's good that we can and we can do it while running everything within one thread in a loop while using the same Framework that we will use for all concurrency in Python so for Other use cases, maybe something we would want to do instead is just run the loop on a thread and we can do that too we can have our synchronous code and That that is our normal Python that is Python code that will be running and we can put the loop on a separate thread and then assign things to it for it To run him and then get him back Things get a little bit more complicated here But this will work and you just have to get your Make sure you clean things up and make sure you always call for the loop to run thread save and There's a lot much to a sync IO that I'm not gonna have time to go into there are low-level API is there's Specifically queues for synchronization. I are quite useful and you can build your own protocols for your servers And there are a lot of cool hacks and you should go check out the internals there if you go to get a hot python That's in KO you have the actual code there that is very easy to read and that's basically the whole implementation and Out of the external stuff for a sync IO the one that they wanted to name in itself is the possibility of creating Alternative event loops we can do quite can use this framework while using Event loops that do not come with a sync IO and one of those is UV loop which Yeah would use us a Lot of things internally, but it claims to be very very fast and if you Look at their benchmarks is up there faster than no JS and about as fast as go and This is something that you can just plug in you can run any of the code We've been writing just change the loop and use UV loop instead and it will work with a separate loop so The the flexibility that this gives us is that we can write the same code and understand the code using this framework But at the same time we can change the internals that will run it in different ways and change how it will execute and There are a lot of third-party libraries for pretty much everything you want So if you want to start playing with the sync IO get some of these and start writing some asynchronous code Thank you