The most common example of this is a network server that needs to handle multiple connections at the same time.
One possible approach to achieve concurrency is to use threads, and despite their bad reputation in Python, they
actually might be a good choice when it comes to I/O for reasons that span far beyond the scope of this tutorial.
If you choose to use threads, there are a couple things you can do, involving what is known as _thread synchronization
primitives_ and _thread pools_, but once again that is beyond the purposes of this quickstart guide.
A library like giambio comes into play when you need to perform lots of [blocking operations](https://en.wikipedia.org/wiki/Blocking_(computing)),
and network servers happen to be heavily based on I/O: a blocking operation.
Starting to see where we're heading?
## A deeper dive
Giambio has been designed with simplicity in mind, so this document won't explain all the gritty details about _how_ async is
implemented in Python (you might want to check out [this article](https://snarky.ca/how-the-heck-does-async-await-work-in-python-3-5/) if you want to learn more about all the implementation details).
For the sake of this tutorial, all you need to know is that giambio is all about a feature added in Python 3.5:
asynchronous functions, or 'async' for short.
Async functions are functions defined with `async def` instead of the regular `def`, like so:
```python
async def async_fun(): # An async function
print("Hello, world!")
def sync_fun(): # A regular (sync) function
print("Hello, world!")
```
First of all, async functions like to stick together: to call an async function you need to put `await` in front of it, like below:
```python
async def async_two():
print("Hello from async_two!")
async def async_one():
print("Hello from async_one!")
await async_two() # This is an async call
```
It has to be noted that using `await` outside of an async function is a `SyntaxError`, so basically async
functions have a unique superpower: they, and no-one else, can call other async functions.
This already presents a chicken-and-egg problem, because when you fire up Python, it is running plain ol'
synchronous code; So how do we enter the async context in the first place?
That is done via a special _synchronous function_, `giambio.run` in our case, that has the ability to call
asynchronous functions and can therefore initiate the async context. For this
reason, `giambio.run`**must** be called from a synchronous context, to avoid a horrible _deadlock_.
Now that you know all of this, you might be wondering why on earth would one use async functions instead of
regular functions: after all, their ability to call other async functions seems pretty pointless in itself, doesn't it?
Take a look at this example below:
```python
import giambio
async def foo():
print("Hello, world!")
giambio.run(foo) # Prints 'Hello, world!'
```
This could as well be written the following way and would produce the same output:
```python
def foo():
print("Hello, world!")
foo() # Prints 'Hello, world!'
```
To answer this question, we have to dig a bit deeper about _what_ giambio gives you in exchange for all this `async`/`await` madness.
We already introduced `giambio.run`, a special runner function that can start the async context from a synchronous one, but giambio
provides also a set of tools, mainly for doing I/O. These functions, as you might have guessed, are async functions and they're useful!
So if you wanna take advantage of giambio, and hopefully you will after reading this guide, you need to write async code.
As an example, take this function using `giambio.sleep` (`giambio.sleep` is like `time.sleep`, but with an async flavor):
__Side note__: If you have decent knowledge about asynchronous python, you might have noticed that we haven't mentioned coroutines
so far. Don't worry, that is intentional: giambio never lets a user deal with coroutines on the surface because the whole async
model is much simpler if we take coroutines out of the game, and everything works just the same.
```python
import giambio
async def sleep_double(n):
await giambio.sleep(2 * n)
giambio.run(sleep_double, 2) # This hangs for 4 seconds and returns
```
As it turns out, this function is one that's actually worth making async: because it calls another async function.
Not that there's nothing wrong with our `foo` from before, it surely works, but it doesn't really make sense to
make it async in the first place.
### Don't forget the `await`!
As we already learned, async functions can only be called with the `await` keyword, and it would be logical to
think that forgetting to do so would raise an error, but it's actually a little bit trickier than that.
Take this example here:
```python
import giambio
async def sleep_double_broken(n):
print("Taking a nap!")
start = giambio.clock()
giambio.sleep(2 * n) # We forgot the await!
end = giambio.clock() - start
print(f"Slept for {end:.2f} seconds!")
giambio.run(sleep_double_broken, 2)
```
Running this code, will produce an output that looks like this:
```
Taking a nap!
Slept 0.00 seconds!
__main__:7: RuntimeWarning: coroutine 'sleep' was never awaited
```
Wait, what happened here? From this output, it looks like the code worked, but something clearly went wrong:
the function didn't sleep. Python gives us a hint that we broke _something_ by raising a warning, complaining
that `coroutine 'sleep' was never awaited` (you might not see this warning because it depends on whether a
garbage collection cycle occurred or not).
I know I said we weren't going to talk about coroutines, but you have to blame Python, not me. Just know that
if you see a warning like that, it means that somewhere in your code you forgot an `await` when calling an async
function, so try fixing that before trying to figure out what could be the problem if you have a long traceback:
most likely that's just collateral damage caused by the missing keyword.
If you're ok with just remembering to put `await` every time you call an async function, you can safely skip to
the next section, but for the curios among y'all I might as well explain exactly what happened there.
When coroutines are called without the `await`, they don't exactly do nothing: they return this weird 'coroutine'
object
```python
>>> giambio.sleep(1)
<coroutineobjectsleepat0x1069520d0>
```
The reason for this is that while giambio tries to separate the async and sync worlds, therefore considering
`await giambio.sleep(1)` as a single unit, when you `await` an async function Python does 2 things:
- It creates this weird coroutine object
- Passes that object to `await`, which runs the function