00:00:00
imagine programming is a journey from
00:00:02
point A to D in traditional synchronous
00:00:04
programming we travel in a straight line
00:00:07
stopping at each point before moving to
00:00:09
the next this means if there's a delay
00:00:11
at any point everything pauses until we
00:00:13
can move on now a synchronous
00:00:15
programming changes the game it allows
00:00:18
us to start tasks at b c and d even if
00:00:21
the task at a isn't finished yet this is
00:00:23
like sending out Scouts to explore
00:00:25
multiple paths at once without waiting
00:00:28
for the first Scout to return before
00:00:29
sending out the next this way our
00:00:32
program can handle multiple tasks
00:00:34
simultaneously making it more efficient
00:00:36
especially when dealing with operations
00:00:38
that have waiting times like loading a
00:00:40
web page and that's the essence of
00:00:42
asynchronous programming making our code
00:00:44
more efficient by doing multiple things
00:00:46
at once without the unnecessary waiting
00:00:48
so now let's quickly discuss when we
00:00:50
should use async iio because when we
00:00:52
build software choosing the right
00:00:54
concurrency model and picking between
00:00:56
asyn iio threads or processes is crucial
00:00:59
for performance and efficiency now async
00:01:02
iio is your choice for tasks that wait a
00:01:04
lot like Network requests or reading
00:01:07
files it excels in handling many tasks
00:01:09
concurrently without using much CPU
00:01:12
power this makes your application more
00:01:14
efficient and responsive when you're
00:01:15
waiting on a lot of different tasks now
00:01:18
threads are suited for tasks that may
00:01:19
need to wait but also share data they
00:01:22
can run in parallel within the same
00:01:24
application making them useful for tasks
00:01:26
that are IO bound but less CPU intensive
00:01:29
IO meaning input output now for CPU
00:01:32
heavy tasks processes are the way to go
00:01:35
each process operates independently
00:01:37
maximizing CPU usage by running in
00:01:40
parallel across multiple cores this is
00:01:42
ideal for intensive computations in
00:01:45
summary choose asyn iio for managing
00:01:47
many waiting tasks efficiently threads
00:01:50
for parallel tasks that share data with
00:01:52
minimal CPU use and processes for
00:01:55
maximizing performance on CPU intensive
00:01:58
tasks now that we know when to use async
00:02:00
iio let's dive into the five key
00:02:02
Concepts that we need to understand the
00:02:04
first concept is the event Loop in
00:02:07
Python's async iio The Event Loop is the
00:02:09
core that manages and distributes tasks
00:02:12
think of it as a central Hub with tasks
00:02:14
circling around it waiting for their
00:02:16
turn to be executed each task takes its
00:02:18
turn in the center where it's either
00:02:20
executed immediately or paused if it's
00:02:22
waiting for something like data from the
00:02:24
internet when a task awaits it steps
00:02:27
aside making room for another task to
00:02:29
run ensuring the loop is always
00:02:31
efficiently utilized once the awaited
00:02:34
operation is complete the task will
00:02:36
resume ensuring a smooth and responsive
00:02:38
program flow and that's how async io's
00:02:41
event Loop keeps your Python program
00:02:42
running efficiently handling multiple
00:02:45
tasks a synchronously so just a quick
00:02:47
pause here for any of you that are
00:02:48
serious about becoming software
00:02:50
developers if you want to be like Max
00:02:52
who landed a 70k per your job in Just 4
00:02:54
months of work consider checking out my
00:02:57
program with course careers now this
00:02:59
teaches you the fun fundamentals of
00:03:00
programming but also lets you pick a
00:03:02
specialization taught by an industry
00:03:04
expert in front end backend or devops
00:03:07
beyond that we even help you prepare
00:03:09
your resume we give you tips to optimize
00:03:11
your LinkedIn profile how to prepare for
00:03:13
interviews we really only succeed if our
00:03:15
students actually get jobs that's the
00:03:18
entire goal of the program so if that's
00:03:20
at all of interest to you we do have a
00:03:21
free introduction course that has a ton
00:03:23
of value no obligation no strings
00:03:25
attached you can check it out for free
00:03:27
from the link in the description so now
00:03:29
that we understand understand what the
00:03:30
event Loop is it's time to look at how
00:03:32
we create one and then talk about the
00:03:34
next important concept which is co-
00:03:36
routines now whenever we start writing
00:03:38
asynchronous code in Python We Begin by
00:03:41
importing the async io module now this
00:03:44
is built into python you don't need to
00:03:45
install it and for the purpose of this
00:03:47
video I'll be referencing all of the
00:03:49
features in Python version 3.11 and
00:03:52
above so if you're using an older
00:03:53
version of python just make sure you
00:03:55
update it because some things have
00:03:56
changed in the recent versions so we
00:03:59
begin by the module then we use the
00:04:01
command or the line async i.run and we
00:04:05
pass to this something known as a
00:04:06
co-routine function which will return a
00:04:09
co- routine object now asyn i.run is
00:04:12
going to start our event Loop and it's
00:04:14
going to start that by running a co-
00:04:16
routine now in our case there's two
00:04:18
types of co- routines we're concerned
00:04:20
with we have a co- routine function
00:04:23
which is this right here and we have
00:04:25
what's returned when you call a co-
00:04:28
routine function I know it seems a bit
00:04:30
strange but when you call Main like this
00:04:33
when it's defined using this async
00:04:35
keyword this returns to us something
00:04:37
known as a co- routine object now the
00:04:41
co-routine object is what we need to
00:04:43
pass here to async i.run it's going to
00:04:46
wait for that to finish and it's going
00:04:47
to start the event Loop for us where it
00:04:50
handles all of our asynchronous
00:04:51
programming so recap import the module
00:04:55
Define some asynchronous functions so
00:04:57
async and then you write the function
00:04:59
name out this is known as a co- routine
00:05:01
function you then call the function and
00:05:03
pass that to async i.run and that's
00:05:06
going to start your event Loop and allow
00:05:08
you to start running asynchronous code
00:05:10
that starts from this entry point now to
00:05:12
illustrate this a bit further let's look
00:05:14
at the difference between an
00:05:15
asynchronous function something defined
00:05:17
with this async keyword and a normal
00:05:20
function so watch what happens if I go
00:05:22
here and I simply call this function
00:05:24
some of you may assume that it's simply
00:05:26
going to print out start of main Cod
00:05:28
routine but you'll see that that's
00:05:29
actually not the case I know that my
00:05:31
terminal is a little bit messy here but
00:05:33
it says co-routine main was never
00:05:36
awaited now the reason we get that issue
00:05:39
is because when we call the function
00:05:41
here what we're actually doing is we're
00:05:43
generating a co-routine object this
00:05:46
co-routine object needs to be awaited in
00:05:48
order for us to actually get the result
00:05:50
of its execution now if we want to see
00:05:52
this even more visually we can actually
00:05:54
print out what we get when we call this
00:05:56
main function so let's call it here and
00:05:59
notice that we actually get this
00:06:00
co-routine object so when you call a
00:06:03
function defined with the async keyword
00:06:05
it returns a co-routine object and that
00:06:07
coroutine object needs to be awaited in
00:06:10
order for it to actually execute so
00:06:12
that's why we use the async i.run syntax
00:06:15
because this will handle awaiting this
00:06:16
Co routine and then allow us to write
00:06:19
some more asynchronous code now the next
00:06:21
thing that we need to look at is the
00:06:23
await keyword now the await keyword is
00:06:25
what we can use to await a coverou
00:06:27
tetine and to actually allow it to
00:06:29
execute and for us to get the result the
00:06:31
thing is though we can only use this
00:06:32
awake keyword inside of an asynchronous
00:06:35
function or inside of a code routine so
00:06:37
let's write another code routine and see
00:06:39
how we would await it and how we get its
00:06:41
result so now I've included a slightly
00:06:43
more complex example where we're
00:06:44
actually waiting on a different code
00:06:46
routine just to see how that works so
00:06:49
notice that we have a code routine up
00:06:50
here and what this is aiming to do is
00:06:52
simulate some input output bound
00:06:54
operation now that could be going to the
00:06:56
network and retrieving some data trying
00:06:59
to read file something that's not
00:07:01
controlled by our program that we're
00:07:02
going to wait on the result from so in
00:07:05
this case you can see that we fetch some
00:07:06
data we delay so we're just sleeping for
00:07:09
a certain amount of seconds just to
00:07:10
simulate that input output bound
00:07:12
operation we then get the data and we
00:07:14
return it now we know this is a co-
00:07:16
routine because we've defined it as an
00:07:18
asynchronous function now remember that
00:07:20
in order for a co- routine to actually
00:07:22
be executed it needs to be awaited now
00:07:25
in this case what we do is we create a
00:07:27
task and this task is the co-routine
00:07:30
object now the co-routine object at this
00:07:32
point in time is not yet being executed
00:07:35
and the reason it's not being executed
00:07:37
yet is because it hasn't been awaited
00:07:39
what I'm trying to show you is that when
00:07:40
you call an asynchronous function it
00:07:43
returns a co- routine that co- routine
00:07:45
needs to be awaited before it will
00:07:47
actually start executing so in this case
00:07:49
here we now await the task when we await
00:07:51
it it will start executing and we'll
00:07:54
wait for it to finish before we move on
00:07:55
to the rest of the code in our program
00:07:57
so let's run the code and see what the
00:07:59
output is here and you can see it says
00:08:01
start of main code routine data fetched
00:08:04
it then receives the results and it says
00:08:06
the end of the main code routine now
00:08:08
let's clear that and let's look at a
00:08:10
slightly different example so let's take
00:08:13
this result code right here and let me
00:08:15
just get rid of this
00:08:16
comment and let's put this actually at
00:08:20
the end of this function so now what we
00:08:22
have is print start of main co- routine
00:08:25
we create the co- routine object we then
00:08:27
print end of main routine object then we
00:08:30
await the code routine and I just want
00:08:31
to show you the difference in the result
00:08:33
that we're going to get so let's run the
00:08:35
code and notice we get start of main
00:08:37
code routine end of main code routine
00:08:39
and then we get fetching data data
00:08:41
fetched and then we get the result now
00:08:44
the reason we got this is because we
00:08:46
only created the code routine object
00:08:48
here we didn't yet await it so it wasn't
00:08:51
until we hit this line right here that
00:08:53
we waited for the execution of this to
00:08:55
finish before moving on to the next line
00:08:58
it's really important to understand that
00:09:00
fact that a code routine doesn't start
00:09:02
executing until it's awaited or until we
00:09:04
wrap it in something like a task which
00:09:05
we're going to look at later so I've
00:09:07
made a slight variation to the last
00:09:09
example and you can see what we're doing
00:09:11
now is we're creating two different code
00:09:13
routine objects and we're then awaiting
00:09:15
them now I want you to pause the video
00:09:17
and take a guess of what you think the
00:09:19
output's going to be and how long you
00:09:21
think it will take for this to execute
00:09:23
go ahead pause the video I'm going to
00:09:25
run the code now and explain what
00:09:27
happens so when I run this if if we move
00:09:30
it up here you'll see that we get
00:09:31
fetching data id1 data fetched id1 we
00:09:35
then receive the result and then we go
00:09:36
ahead and we fetch it for id2 now let's
00:09:40
clear this and run it one more time and
00:09:42
you can see that it takes 2 seconds we
00:09:43
fetch the first result it takes another
00:09:45
2 seconds and we fetch the second result
00:09:48
now this might seem counterintuitive
00:09:50
because you may have guessed that when
00:09:52
we created these two coroutine objects
00:09:54
they were going to start running
00:09:55
concurrently and that means that it
00:09:56
would only take us a total of 2 seconds
00:09:58
and we'd immediately get both of the
00:10:00
results but remember a code routine
00:10:02
doesn't start running until it's awaited
00:10:05
so in this case we actually wait for the
00:10:07
first co- routine to finish and only
00:10:09
once this has finished do we even start
00:10:11
executing the second co- routine meaning
00:10:13
that we haven't really got any
00:10:15
performance benefit here we've just
00:10:17
created a way to kind of wait for a task
00:10:19
to be finished that's all we've really
00:10:21
learned at this point in time now that
00:10:23
we understand this concept we can move
00:10:25
over and talk about tasks and see how we
00:10:27
can actually speed up an operation ation
00:10:29
like this and run both of these tasks or
00:10:32
these co- routines at the same time so
00:10:34
now we're moving on to the next
00:10:36
important concept which is a task now a
00:10:38
task is a way to schedule a co- routine
00:10:40
to run as soon as possible and to allow
00:10:43
us to run multiple co- routines
00:10:45
simultaneously now the issue we saw
00:10:47
previously is that we needed to wait for
00:10:49
one co- routine to finish before we
00:10:51
could start executing the next with a
00:10:53
task we don't have that issue and as
00:10:55
soon as a co- routine is sleeping or
00:10:57
it's waiting on something that's not in
00:10:59
control of our program we can move on
00:11:01
and start executing another task we're
00:11:03
never going to be executing these tasks
00:11:05
at the exact same time we're not using
00:11:08
multiple CPU cores but if one task isn't
00:11:11
doing something if it's idle if it's
00:11:13
blocked if it's waiting on something we
00:11:15
can switch over and start working on
00:11:17
another task the whole goal here is that
00:11:19
our program is optimizing its efficiency
00:11:22
so we're always attempting to do
00:11:24
something and when we're waiting on
00:11:25
something that's not in control of our
00:11:27
program we switch over to another task
00:11:29
and start working on that so here's a
00:11:31
quick example that shows you how we
00:11:33
would optimize kind of the previous
00:11:34
example that we looked at what we do
00:11:36
here is we use the simple create task
00:11:39
function now there's a few other ways to
00:11:40
make tasks which I'm going to show you
00:11:42
in a second but this is the simplest
00:11:44
what we do is we say task one is equal
00:11:46
to asyn io. create task and then we pass
00:11:48
in here a co-routine object it's a
00:11:51
co-routine object because this is a
00:11:52
co-routine function we call the function
00:11:54
and that returns to us a co- routine so
00:11:56
in this case we pass an ID then we pass
00:11:58
some time delay now if this was running
00:12:01
synchronously so if we had to wait for
00:12:03
each of these tasks to run it would take
00:12:05
us 2 seconds plus 3 seconds plus 1
00:12:07
second so a total of 6 seconds for this
00:12:10
code to execute however you'll see now
00:12:13
that what will happen is we'll be able
00:12:14
to execute this code in simply 3 seconds
00:12:17
because as soon as one of the tasks is
00:12:18
idle and we're waiting on this sleep we
00:12:21
can go and execute or start another task
00:12:24
now what I do is I still need to await
00:12:26
these tasks to finish so I just await
00:12:28
them all in line here and then collect
00:12:30
all of their different results so let's
00:12:33
bring the terminal up and let's run this
00:12:35
code and make sure it works and notice
00:12:37
that it starts all three Co routines
00:12:39
pretty much immediately and then we get
00:12:41
all of the data back at once in about 3
00:12:43
seconds again that differs from if we
00:12:46
were to use just the normal C routines
00:12:48
and we didn't create a task we'd have to
00:12:50
wait for each of them to finish before
00:12:51
we can move on to the next one so as a
00:12:53
quick recap when we create a task we're
00:12:56
essentially scheduling a code routine to
00:12:57
run as quickly as possible possible and
00:12:59
we're allowing multiple Co routines to
00:13:01
run at the same time as soon as one co-
00:13:04
routine isn't doing something and it's
00:13:05
waiting on some operation we can switch
00:13:07
to another one and start executing that
00:13:10
now all of that is handled by the event
00:13:12
loop it's not something we need to
00:13:13
manually take care of however if we do
00:13:15
want to wait on one task to finish
00:13:17
before moving to the next one we can use
00:13:19
the await syntax so it would be possible
00:13:21
for me to go here and write some code
00:13:24
like this and now we would see if we
00:13:25
execute the code and we can go ahead and
00:13:27
do that that we'll start the first and
00:13:29
the second code routine but we won't
00:13:31
start the third one until the first and
00:13:33
the second one are done so using a
00:13:35
synchronous programming gives us that
00:13:37
control and allows us to synchronize our
00:13:39
code in whatever manner we see fit so
00:13:41
now we move on to a quick example where
00:13:43
I'm going to show you something known as
00:13:45
The Gather function Now The Gather
00:13:47
function is a quick way to concurrently
00:13:49
run multiple co- routines just like we
00:13:51
did manually before so rather than
00:13:53
creating a task for every single one of
00:13:54
the co- routines using that create task
00:13:57
function we can simply use gather and it
00:13:59
will automatically run these
00:14:01
concurrently for us and collect the
00:14:03
results in a list the way it works is
00:14:05
that we pass multiple code routines in
00:14:07
here as arguments these are
00:14:09
automatically going to be scheduled to
00:14:10
run concurrently so we don't need to
00:14:12
wait for them to finish before we start
00:14:14
executing the next one and then we will
00:14:16
gather all of the results in a list in
00:14:18
the order in which we provided the co-
00:14:20
routines so the result of this one will
00:14:21
be the first element in the list second
00:14:23
element in the list third element in the
00:14:25
list Etc and it's going to wait for all
00:14:27
of them to finish when we use this await
00:14:30
keyword which just simplifies this
00:14:31
process for us that then allows us to
00:14:34
have all of the results in one place so
00:14:35
we can parse through them using this for
00:14:37
Loop so let's go ahead and run this code
00:14:40
and you see that it starts all three of
00:14:41
our Co routines we wait 3 seconds and
00:14:43
then we get all of our different results
00:14:45
now one thing you should know about
00:14:47
gather is that it's not that great at
00:14:49
error handling and it's not going to
00:14:51
automatically cancel other co- routines
00:14:53
if one of them were to fail now the
00:14:55
reason I'm bringing that up is because
00:14:57
the next example I show you does
00:14:58
actually provide some built-in error
00:15:00
handling which means it's typically
00:15:02
preferred over gather but it's just
00:15:04
worth noting that if there is an error
00:15:05
that occurs in one of these different
00:15:07
code routines it won't cancel the other
00:15:09
code routines which means you could get
00:15:11
some weird state in your application if
00:15:13
you're not manually handling the
00:15:14
different exceptions and errors that
00:15:16
could occur so now we're moving on to
00:15:17
the last example in the topic of tasks
00:15:20
where we're talking about something
00:15:21
relatively new known as a task group now
00:15:24
this is a slightly more preferred way to
00:15:25
actually create multiple tasks and to
00:15:27
organize them together and the reason
00:15:29
for this is this provides some built-in
00:15:31
error handling and if any of the tasks
00:15:33
inside of our task groups were to fail
00:15:35
it will automatically cancel all of the
00:15:37
other tasks which is typically
00:15:39
preferable when we are dealing with some
00:15:41
Advanced errors or some larger
00:15:43
applications where we want to be a bit
00:15:44
more robust now the fetch data function
00:15:46
has not changed at all all we've done
00:15:49
here is we've started using async i.ask
00:15:51
group now notice that what I'm using
00:15:53
here is the async width now this is
00:15:55
what's known as an asynchronous context
00:15:58
manager you don't to understand that you
00:16:00
don't have to have seen context managers
00:16:02
before but what this does is give us
00:16:04
access to this TG variable so we create
00:16:06
a task group as TG and now to create a
00:16:09
task we can say TG our task group.
00:16:12
create task just like we did before in
00:16:14
that first example we can create an
00:16:16
individual task we can then add this to
00:16:19
something like our tasks list if we care
00:16:21
about the result of it and now once we
00:16:23
get by this asynchronous width so once
00:16:26
we get down here to where I have the
00:16:29
comment what happens is all of these
00:16:31
tasks will have already been executed so
00:16:34
the idea is this is a little bit cleaner
00:16:36
it's automatically going to execute all
00:16:38
of the tasks that we add inside of the
00:16:40
task group once all of those tasks have
00:16:42
finished then this will stop blocking
00:16:45
when I say stop blocking that means we
00:16:47
can move down to the next line of code
00:16:49
and at this point we can retrieve all of
00:16:50
the different results from our tasks now
00:16:53
there's various different ways to go
00:16:54
about writing this type of code but the
00:16:56
idea is you simply create a task here as
00:16:59
soon as it's created inside of the task
00:17:01
group we now need to wait for that and
00:17:03
all the other tasks to finish before we
00:17:05
unblock from this block of code then
00:17:08
once they're all finished we move on to
00:17:09
the next lines of code now similarly to
00:17:12
any other task that we looked at before
00:17:14
these are all going to run concurrently
00:17:16
meaning if one task is sleeping we can
00:17:18
go on and we can start another task and
00:17:19
work on something else so those are
00:17:21
tasks obviously there's a lot more you
00:17:23
can do here but understand that you run
00:17:25
tasks when you want to execute code
00:17:27
concurrently and you want multiple
00:17:29
different operations to be happening at
00:17:31
the same time so now we're moving on to
00:17:33
the fourth important concept which is a
00:17:35
future now it's worth noting that a
00:17:37
future is not something that you're
00:17:39
expected to write on your own it's
00:17:41
typically utilized in lower level
00:17:43
libraries but it's good to just be
00:17:44
familiar with the concept in case you
00:17:46
see it in asynchronous programming so
00:17:48
I'll go through this fairly quickly but
00:17:50
really what a future is is a promise of
00:17:52
a future result so all it's saying is
00:17:54
that a result is to come in the future
00:17:57
you don't know exactly when that's going
00:17:58
to be that's all future is so in this
00:18:01
case you can see that we actually create
00:18:02
a future and we await its value what we
00:18:05
do is we actually get the event Loop you
00:18:07
don't need to do this you'll probably
00:18:09
never write this type of code we create
00:18:11
our own future we then have a new task
00:18:14
that we create using async iio and you
00:18:16
can see the task is set future result
00:18:19
inside here we wait for 2 seconds so
00:18:21
this is some blocking operation and then
00:18:24
we set the result of the future and we
00:18:26
print out the result here we AIT the
00:18:29
future and then we print the result now
00:18:32
notice we didn't actually await the task
00:18:34
to finish we awaited the future object
00:18:37
so inside of the task we set the value
00:18:40
of the future and we awaited that which
00:18:43
means as soon as we get the value of the
00:18:45
future this task may or may not actually
00:18:47
be complete so this is slightly
00:18:49
different than using a task when we use
00:18:51
a future we're just waiting for some
00:18:53
value to be available we're not waiting
00:18:56
for an entire task or an entire co-
00:18:58
routine to finish that's all I really
00:19:00
want to show you here I don't want to
00:19:01
get into too many details that's a
00:19:03
future really just a promise of an
00:19:04
eventual result so now we're moving on
00:19:07
and talking about synchronization
00:19:08
Primitives now these are tools that
00:19:10
allow us to synchronize the execution of
00:19:12
various co- routines especially when we
00:19:14
have larger more complicated programs
00:19:17
now let's look at this example so we can
00:19:19
understand how we use the first
00:19:20
synchronization tool which is lock let's
00:19:23
say that we have some shared resource
00:19:25
maybe this is a database maybe it's a
00:19:27
table maybe it's a file doesn't matter
00:19:29
what it is but the idea is that it might
00:19:31
take a fair amount of time for us to
00:19:33
actually modify or do some operation on
00:19:35
this shared resource and we want to make
00:19:37
sure that no two co-routines are working
00:19:40
on this at the same time the reason for
00:19:42
that is if two co-routines were say
00:19:44
modifying the same file if they're
00:19:45
writing something to the database we
00:19:47
could get some kind of error where we
00:19:49
get a mutated state or just weird
00:19:52
results end up occurring because we have
00:19:54
kind of different operations happening
00:19:55
at different times and they're
00:19:56
simultaneously occurring when we want
00:19:58
really wait for one entire operation to
00:20:00
finish before the next one completes
00:20:03
that might seem a little bit confusing
00:20:05
but the idea is we have something and we
00:20:06
want to lock it off and only be using it
00:20:09
from one co- routine at a time so what
00:20:12
we can do for that is we can create a
00:20:13
lock now when we create a lock we have
00:20:16
the ability to acquire the lock and we
00:20:18
do that with this code right here which
00:20:20
is async with lock now this again is an
00:20:23
asynchronous context manager and what
00:20:25
this will do is it will check if any
00:20:27
other code routine is currently using
00:20:29
the lock if it is it's going to wait
00:20:32
until that code routine is finished if
00:20:34
it's not it's going to go into this
00:20:35
block of code now the idea is whatever
00:20:38
we put inside of this context manager
00:20:40
needs to finish executing before the
00:20:43
lock will be released which means we can
00:20:45
do some critical part of modification we
00:20:48
can have some kind of code occurring in
00:20:49
here that we know will happen all at
00:20:51
once before we move on to a different
00:20:53
task or to a different code routine the
00:20:56
reason that's important is because we
00:20:57
have something like an await maybe we're
00:20:59
waiting a network operation to save
00:21:01
something else that could trigger a
00:21:03
different task to start running in this
00:21:05
case we're saying hey within this lock
00:21:08
wait for all of this to finish before we
00:21:10
release the lock which means that even
00:21:12
though another task could potentially be
00:21:14
executing when the Sleep occurs it can't
00:21:16
start executing this critical part of
00:21:19
code until all of this is finished and
00:21:21
the lock is released so all the lock is
00:21:24
really doing is it's synchronizing our
00:21:26
different co- routines so that they
00:21:28
can't be using this block of code or
00:21:30
executing this block of code while
00:21:32
another code routine is executing it
00:21:34
that's all it's doing it's locking off
00:21:36
access to in this case a critical
00:21:38
resource that we only want to be
00:21:39
accessed one at a time so in this case
00:21:42
you can see that we create five
00:21:43
different instances of this Co routine
00:21:46
we then are accessing the lock and then
00:21:48
again once we get down here we're going
00:21:49
to release it so if we bring up the
00:21:52
terminal here and we start executing
00:21:54
this you'll see that we have resource
00:21:55
before modification resource after
00:21:58
before after before after and the idea
00:22:00
is even though that we've executed these
00:22:01
cortines concurrently we're gating them
00:22:04
off and we're locking their access to
00:22:06
this resers so that only one can be
00:22:08
accessing it at a time moving on the
00:22:10
next synchronization primitive to cover
00:22:12
is known as the semaphore now a
00:22:14
semaphore is something that works very
00:22:16
similarly to a lock however it allows
00:22:19
multiple Cod routines to have access to
00:22:21
the same object at the same time but we
00:22:23
can decide how many we want that to be
00:22:26
so in this case we create a semaphore
00:22:28
and we give give it a limit of two that
00:22:30
means only two co- routine story can
00:22:32
access some resource at the exact same
00:22:34
time and the reason we would do that is
00:22:36
to make sure that we kind of throttle
00:22:38
our program and we don't overload some
00:22:40
kind of resource so it's possible that
00:22:42
we're going to send a bunch of different
00:22:43
network requests we can do a few of them
00:22:46
at the same time but we can't do maybe a
00:22:48
thousand or 10,000 at the same time so
00:22:50
in that case we would create a semaphor
00:22:52
we'd say okay our limit is maybe five at
00:22:54
a time and this way now we have the
00:22:56
event Loop automatically handled this
00:22:58
throttle our code intentionally to only
00:23:00
send maximum five requests at a time
00:23:03
anyways let's bring up our terminal here
00:23:05
and run this code so Python 3 semap 4.
00:23:09
piy and you can see that we can access
00:23:10
the resource kind of two at a time and
00:23:13
modify it but we can't have any more
00:23:15
than that now moving on to the last
00:23:17
primitive we're going to talk about this
00:23:19
is the event now the event is something
00:23:21
that's a little bit more basic and
00:23:22
allows us to do some simpler
00:23:24
synchronization in this case we can
00:23:26
create an event and what we can do is we
00:23:28
can await the event to be set and we can
00:23:31
set the event and this acts as a simple
00:23:33
Boolean flag and it allows us to block
00:23:36
other areas of our code until we've set
00:23:39
this flag to be true so it's really just
00:23:41
like setting a variable to true or false
00:23:43
in this case it's just doing it in the
00:23:44
asynchronous way so you can see we have
00:23:47
some Setter function maybe it takes two
00:23:49
seconds to be able to set some result we
00:23:51
then set the result and as soon as that
00:23:53
result has been set we can come up here
00:23:55
we await that so we wait for this to
00:23:57
finish and and then we can go ahead and
00:23:59
print the event has been set continue
00:24:01
execution so we can bring this up here
00:24:04
and quickly have a look at this so
00:24:05
Python 3 if we spell that correctly
00:24:08
event. pi and you'll see it says
00:24:10
awaiting the event to be set event has
00:24:12
been set event has been set continuing
00:24:14
the execution okay pretty
00:24:16
straightforward it's just a Boolean flag
00:24:18
that allows us to wait at certain points
00:24:20
in our program there's lots of different
00:24:21
times when you would want to use this
00:24:23
but I just wanted to quickly show you
00:24:24
that we do have something like this that
00:24:25
exists now there is another type of
00:24:27
primitive here that's a bit more
00:24:29
complicated called the condition I'm not
00:24:31
going to get into that in this video in
00:24:33
fact I'm going to leave the video here
00:24:35
if you guys enjoyed this make sure you
00:24:37
leave a like subscribe to the channel
00:24:39
and consider checking out my premium
00:24:40
software development course with course
00:24:42
careers if you enjoy this teaching style
00:24:44
and you're serious about becoming a
00:24:46
developer anyways I will see you guys in
00:24:48
another YouTube
00:24:50
[Music]
00:24:57
video