- Mastering Concurrency in Python
- Quan Nguyen
- 989字
- 2021-06-10 19:24:09
Timeout specifications
An efficient ping test application should not be waiting for responses from its websites for a long time; it should have a set threshold for timeout that, if a server fails to return a response under that threshold, the application will deem that server non-responsive. We therefore need to implement a way to keep track of how much time has passed since a request is sent to a server. We will do this by counting down from the timeout threshold and, once that threshold is passed, all responses (whether returned or not yet returned) will be printed out.
Additionally, we will also be keeping track of how many requests are still pending and have not had their responses returned. We will be using the isAlive() method from the threading.Thread class to indirectly determine whether a response has been returned for a specific request: if, at one point, the thread processing a specific request is alive, we can conclude that that specific request is still pending.
Navigate to the Chapter05/example6.py file and consider the process_requests() function first:
# Chapter05/example6.py
import time
UPDATE_INTERVAL = 0.01
def process_requests(threads, timeout=5):
def alive_count():
alive = [1 if thread.isAlive() else 0 for thread in threads]
return sum(alive)
while alive_count() > 0 and timeout > 0:
timeout -= UPDATE_INTERVAL
time.sleep(UPDATE_INTERVAL)
for thread in threads:
print(thread.result)
The function takes in a list of threads that we have been using to make web requests in the previous examples, as well as an optional argument specifying the timeout threshold. Inside this function, we have an inner function, alive_count(), which returns the count of the threads that are still alive at the time of the function call.
In the process_requests() function, as long as there are threads that are currently alive and processing requests, we will allow the threads to continue with their execution (this is done in the while loop with the double condition). The UPDATE_INTERVAL variable, as you can see, specifies how often we check for this condition. If either condition fails (if there are no alive threads left or if the threshold timeout is passed), then we will proceed with printing out the responses (even if some might have not been returned).
Let's turn our attention to the new MyThread class:
# Chapter05/example6.py
import threading
import requests
class MyThread(threading.Thread):
def __init__(self, url):
threading.Thread.__init__(self)
self.url = url
self.result = f'{self.url}: Custom timeout'
def run(self):
res = requests.get(self.url)
self.result = f'{self.url}: {res.text}'
This class is almost identical to the one we considered in the previous example, except that the initial value for the result attribute is a message indicating a timeout. In the case that we discussed earlier where the timeout threshold specified in the process_requests() function is passed, this initial value will be used when the responses are printed out.
Finally, let's consider our main program:
# Chapter05/example6.py
urls = [
'http://httpstat.us/200',
'http://httpstat.us/200?sleep=4000',
'http://httpstat.us/200?sleep=20000',
'http://httpstat.us/400'
]
start = time.time()
threads = [MyThread(url) for url in urls]
for thread in threads:
thread.setDaemon(True)
thread.start()
process_requests(threads)
print(f'Took {time.time() - start : .2f} seconds')
print('Done.')
Here, in our URL list, we have a request that would take 4 seconds and another that would take 20 seconds, aside from the ones that would respond immediately. As the timeout threshold that we are using is 5 seconds, theoretically we should be able to see that the 4-second-delay request will successfully obtain a response, while the 20-second-delay one will not.
There is another point to be made about this program: daemon threads. In the process_requests() function, if the timeout threshold is passed while there is still at least one thread processing, then the function will proceed to print out the result attribute of each thread:
while alive_count() > 0 and timeout > 0:
timeout -= UPDATE_INTERVAL
time.sleep(UPDATE_INTERVAL)
for thread in threads:
print(thread.result)
This means that we do not block our program until all of the threads have finished their execution by using the join() function, and the program therefore can simply move forward if the timeout threshold is reached. However, this means that the threads themselves do not terminate at this point. The 20-second-delay request, specifically, will still most likely be running after our program exits out of the process_requests() function.
If the thread processing this request is not a daemon thread (as we know, daemon threads execute in the background and never terminate), it will block the main program from finishing until the thread itself finishes. By making this thread, and any other thread, a daemon thread, we allow the main program to finish as soon as it executes the last line of its instructions, even if there are threads still running.
Let us see this program in action. Execute the code and your output should be similar to the following:
http://httpstat.us/200: 200 OK
http://httpstat.us/200?sleep=4000: 200 OK
http://httpstat.us/200?sleep=20000: Custom timeout
http://httpstat.us/400: 400 Bad Request
Took 5.70 seconds
Done.
As you can see, it took around 5 seconds for our program to finish this time. This is because it spent 5 seconds waiting for the threads that were still running and, as soon as the 5-second threshold was passed, the program printed out the results. Here we see that the result from the 20-second-delay request was simply the default value of the result attribute of the MyThread class, while the rest of the requests were able to obtain the correct response from the server (including the 4-second-delay request, since it had enough time to obtain the response).
If you would like to see the effect of non-daemon threads that we discussed earlier, simply comment out the corresponding line of code in our main program, as follows:
threads = [MyThread(url) for url in urls]
for thread in threads:
#thread.setDaemon(True)
thread.start()
process_requests(threads)
You will see that the main program will hang for around 20 seconds, as the non-daemon thread processing the 20-second-delay request is still running, before being able to finish its execution (even though the output produced will be identical).
- Getting Started with ResearchKit
- 三維圖形化C++趣味編程
- Python王者歸來
- 從Excel到Python:用Python輕松處理Excel數據(第2版)
- Learning jQuery(Fourth Edition)
- Python語言實用教程
- GameMaker Essentials
- HoloLens與混合現實開發
- Python Machine Learning Blueprints:Intuitive data projects you can relate to
- JavaScript Concurrency
- PHP動態網站開發實踐教程
- Pandas入門與實戰應用:基于Python的數據分析與處理
- Python編程零基礎入門
- Visual C++實用教程
- 網頁設計理論與實踐