First off, awesome to see more benchmarks (even if it&#x27;s just personal experimentation) for synchronous vs asyncio performance. I think the real argument for asyncio <i>right now</i> is that it makes it very easy for you to write extremely efficient code, even for hobbyist projects. Even though your experiment is only handling 320 req&#x2F;s, that you were able to do that so quickly and with very, very little optimization is, I think, a testament to the potential for asyncio.<p>Some pointers:<p>The event loop is still a single thread and therefore subject to the GIL. That means that at any given time, only one coroutine is running in the loop. This is important for several reasons, but probably the most relevant are that<p>1. within any given coroutine, execution flow will always be consistent between yield&#x2F;await statements.<p>2. synchronous calls within coroutines will <i>block the entire event loop</i>.<p>3. most of asyncio was not written with thread safety in mind<p>That second one is really important. When you&#x27;re doing file access, eg where you&#x27;re doing &quot;with open(&#x27;frank.html&#x27;, &#x27;rb&#x27;)&quot;, that&#x27;s something you may want to consider moving into a run_in_executor call. That <i>will</i> block the coroutine, but it will return control to the event loop, allowing other connections to proceed.<p>Also, more likely than not, the too many open files error is a result of you opening frank.html, not of sockets. I haven&#x27;t run your code with asyncio in debug mode[1] to verify that, but that would be my intuition. You would probably handle more requests if you changed that -- I would do the file access in a run_in_executor with a max executor workers of 1000. If you want to surpass that, use a process pool instead of a threadpool, and you should be ready to go, though it&#x27;s worth mentioning that disk IO is hardly ever cpu-bound, so I wouldn&#x27;t expect you to get much performance boost otherwise.<p>Also, the placement of your semaphore acquisition doesn&#x27;t make any sense to me. I would create a dedicated coroutine like this:<p><pre><code>    async def bounded_fetch(sem):
        async with sem:
            return (await fetch(url.format(i)))
</code></pre>
and modify the parent function like this:<p><pre><code>    for i in range(r):
        task = asyncio.ensure_future(bounded_fetch(sem))
        tasks.append(task)
</code></pre>
That being said, it also doesn&#x27;t make any sense to me to have the semaphore in the client code, since the error is in the server code.<p>[1] <a href="https:&#x2F;&#x2F;docs.python.org&#x2F;3&#x2F;library&#x2F;asyncio-dev.html#debug-mode-of-asyncio" rel="nofollow">https:&#x2F;&#x2F;docs.python.org&#x2F;3&#x2F;library&#x2F;asyncio-dev.html#debug-mod...</a>