drogon: Running slow function without blocking requests
I tried googling this and searching for answers in past Issues, but I couldn’t really find my answer even tho some Issues where somewhat similar. Also, I am very new to c++ async, futures, etc. I have only ever used std::thread; so this might be a really dumb problem I’m having.
I have a drogon server running with 16 threads; my CPU is an i9 with 8 cores (macbook pro). I have a route “/test” that runs a very slow function and then returns “OK”. For now the function is just:
std::this_thread::sleep_for(std::chrono::seconds(10));
Now, if I request this route 16 times, the following happens:
1st request: 10 seconds (as expected) 2nd request: 20 seconds (seems like the first request is blocking next requests?) 3rd request: 30 seconds (same as above) 4th request: 40 seconds (same as above) 5th - 10th request: 50 seconds (suddenly, after 4 requests, 6 requests are being run at the same time) 11th - 16th request: 60 seconds (again, 6 requests at a time)
I don’t understand why the initial 4 requests run 1 request at a time. Also, once the requests run simultaneously, why is it only running 6 requests at a time when it’s supposedly using 16 threads (although just 8 cores).
I’m not sure if I’m supposed to run the “really slow function” using a new thread, using std::async with std::future or what.
About this issue
- Original URL
- State: closed
- Created a year ago
- Comments: 22 (9 by maintainers)
@VladlenPopolitov I don’t quite agree. The
gotostatement doesn’t come with thread switching, but when a coroutine resumes its execution, it’s possible (not necessarily) to continue in another thread. When a coroutine’s sleep statement is executed, it doesn’t block the current thread. Thus, coroutines are indeed asynchronous. Therefore, IMHO, coroutines should be referred to as “flattened callbacks.” The asynchronous programming paradigm aims to reduce idle waiting of threads, enabling all threads to operate efficiently. This allows a small number of threads to handle a large volume of concurrent requests. Therefore, for compute-intensive tasks, asynchronous solutions do not offer significant advantages. For such tasks, executing them directly within the current IO thread is sufficient. If there’s concern about impacting the response latency of simple APIs, these tasks can also be placed in a thread pool for execution. Simultaneously, proper flow control should be implemented (please refer to the Hodor plugin) to prevent excessive requests from keeping the CPU under prolonged high load. Note that compute-intensive tasks do not include timers, database queries, Redis queries, requesting results from other services, and similar tasks. These tasks can improve throughput by yielding the current thread during IO waiting through callbacks or coroutines.@ErikTheBerik trantor::ConcurrentTaskQueue look like excellent option, it is ready solution. I did test code: Declarations and definitions:
Controllers code - version with lambda doing intensive work, and version with switching controlling to other thread from thread pool (code from hello world example):
@biospb Load measurements are:
Almost all code from Drogon. I did awaiter based on Drogon code too.
@biospb If you insert sleep_for in the controllers code, you will block frameworks main loop and block all controllers dependent on this thread instead of delaying the execution in your controller. If you need to make a pause in your controller for some reason, you have to use runAfter(delaySec,function) and specify delay time in seconds. Example here:
The output for hellodelay handler load test is (ab -c 100 -n 100 ):
1127 includes delay and overhead of the framework and granularity of the timer (it does not mean, that overhead is 127ms, probably delay timer has granularity 100ms.
Frankly speaking, if you need to asset the delay in frameworks, you have to run both of them with minimal user code, otherwise you evaluate the user code delays.
I hope, it solves your doubts.
Probably it can be achieved by running in separate threads instead of corotines. Also usings thread pools as it was somewhere suggested. Let’s wait for other suggestions