This gist demonstrates the difference between threading and asyncio. To be clear, they're both limited by the Global Interpreter Lock and are both single process, multi-threaded. They are both forms of concurrency but not parallelism.
Threading (via Thread, concurrent.futures) employs time-slicing of CPU. All threads are given a slot of CPU time to do work. If the thread is blocking (sleeping or blocked on sockets), then off it goes to the next thread. With many threads that are blocked for long periods, this begins to degrade into polling (polling vs. interrupt)
asyncio uses an event loop and is more akin to a pub-sub, push notification model. Threads will announce they're blocked by using asyncio methods. The next available thread at the top of the queue is then processed on, until it completes or is blocked again. This has reduced concurrency and can allow one thread to starve out the others. If the access pattern is threads that are blocked for long time, this model will ensure that you don't bother checking a thread, you wait for it to announce it's available.