- So let's take a look at that wait for task pattern
in a much more practical way around Goroutine pooling.
Now I do want you to hesitate when creating pools
of Goroutines, because as I told you,
the Go schedule is very intelligent
and those Ps are kind of like pools of Goroutines already.
So we don't have to worry about imagining
our own pools of Goroutines,
though there may be times where you have a limited resource
that has to require limited access to something
and you might need to limit access
to something through a pool.
All right, so let's go through this code here.
Right out of the box on line 95,
we're saying we want to have guarantees
and we're signaling with string data, pooling
and the whole idea's got Goroutines waiting for work to do.
So the work is gonna be string based and we want guarantees.
You absolutely want guarantees with pooling,
because later on we want to be able to apply deadlines
or timeouts when a pool is, let's say, underload
and not responding fast enough.
Can't do that with buffered channels.
Now look at what we do here, I've got a loop of two,
and we're currently gonna create two Goroutines in our pool.
So what we can imagine is, I'm gonna create
this pool of Goroutines right here.
There it is, and these , and we're gonna end up with
two paths of execution in the pool, Goroutine1, Goroutine2
and they're both gonna be here in this waiting state.
What makes them wait?
What makes them wait is the for range.
I want you to look at what we're doing.
We are ranging over the channel.
When you range over a channel,
you are basically in a channel receive.
So I've got two Goroutines blocked on the same CH channel.
We're using closures there, by the way
and we are now blocked in a channel receive.
Now order better not matter, because once data
comes into the channel, the scheduler
can choose any Goroutine that it wants to do the work.
Okay, how does the for range terminate?
Well that's gonna be through a signaling change.
We're gonna go from open to closed
and that will terminate the loop.
I'll show you that.
So look what I got here, two Goroutines in my pool.
They're both blocked on the channel receive right here
and now we go off, right we go off, we got the pool
in place and now we go off right there on line 108.
So here we are, right?
We're on line 108 right here.
Here's our main Goroutine.
This is where we were, this is where we created that pool.
So we create the pool of the two Goroutines
right there on line 98.
This is all, all on line 98 right there
and now we come down and we get to line 108.
Now on line 108 what we do is we end up in this work loop.
We're gonna end up in this work loop
where what we're gonna do is pass work into the pool,
ten pieces of work and that's why you see on line 109,
you're gonna be setting the work into the channel.
Remember that channel send looks like this,
channel, send that data right here.
Now in order for the send to complete
we need a corresponding receive
and again, the scheduler now has to choose a Goroutine.
Which Goroutine is the scheduler gonna choose?
I absolutely have no idea.
when all things are equal, it is un deterministic.
So let's just say on the first generation, this send,
right here, maybe it ends up binding to that receive.
Now I've got a send and a receive coming together.
This Goroutine gets the data, it's starts doing work.
Then what happens?
Then we iterate and we do it again.
Maybe on the next channel send,
maybe on the next channel send of data,
maybe because that ones busy, the scheduler says,
okay I'm gonna allow this one to bind to this.
Now that work is getting done, right?
Now if another, on the third piece of work,
here we go again, we're gonna have the third piece of work,
with the data, but this time I don't have
a single Goroutine to perform the receive.
We have guarantees, so now this is blocked.
How long is this going to block?
It's unknown, the guarantees, we have to wait
for one of these Goroutines, right,
to finish their work, finish their work, come back in,
and say I'm ready to do more work,
then the scheduler can come in
and go okay brilliant, I finally have a Goroutine for you.
Let's get going.
So in this particular case, we're gonna try
to pass ten pieces of work to the pool,
but with only two Goroutines,
this Goroutine's going to have some latency.
There's definitely gonna be some signaling,
sending latency here depending on
how fast these Goroutines complete the work.
If we want to reduce that latency,
we gotta add more go routines to the pool,
but you can see here that we're doing this.
Why we want to use the guarantee,
is we could later on encode, put a time out here.
We might say, hey you've got one second, that's it,
one second for this send to complete
and if not, we're gonna move on,
and that's a way of being able to say,
hey, you know what, we might be underload
and we don't want to wait.
When you're running multi threaded software,
you've got to deal with back pressures and latencies
and one way to deal with back pressure and latency
is timeouts.
Timeouts, timeouts, timeouts, timeouts are everything,
and so we may eventually want to put timeouts
on these channel sends to make sure
that we're not blocking here forever
depending on what happens in the pool.
So this is how you go in to do that.
On line 113, I'm simulating a program shutting down.
If you notice on this channel,
we're both signaling with data and without data.
While the program is running we're signaling with data,
pushing data into the pool.
Eventually we want to shut the program down.
We call close on 113, that causes these receives now
that are based on the for range to terminate
and then these Goroutines can then,
eventually terminate and we can shut our program down.
So very cool pattern for pooling.
This is your base pattern mechanics.
You may wan to add to this one day,
adding some sort of program counters around metrics,
around back pressure.
You can do that kind of stuff.
This is the base pooling pattern
leveraging the wait for task.