ThreadPoolExecutor runs as iterative rather with threads

Clash Royale CLAN TAG#URR8PPP
ThreadPoolExecutor runs as iterative rather with threads
I have the following code:
def getdata(page, hed, limit):
data =
print page
datarALL =
url = 'http://...WithTotal=true&cultureid=1&offset=0&limit=1'.format(value_offset, value_limit)
print page
print url
responsedata = requests.get(url, data=data, headers=hed, verify=False)
if responsedata.status_code == 200: # 200 for successful call
responsedata = responsedata.text
jsondata = json.loads(responsedata)
if "results" in jsondata:
if jsondata["results"]:
datarALL = datarALL + jsondata["results"]
print "page finished".format(page)
return data
def start(data, auth_token):
# # --- Get data from API --
hed = 'Authorization': 'Bearer ' + auth_token, 'Accept': 'application/json'
urlApi = 'http://...WithTotal=true&cultureid=1&offset=0&limit=1'
responsedata = requests.get(urlApi, data=data, headers=hed, verify=False)
num_of_records = int(math.ceil(responsedata.json()['total']))
value_limit = 249 # Number of records per page.
num_of_pages = num_of_records / value_limit
print num_of_records
print num_of_pages
pages = [i for i in range(0, num_of_pages - 1)]
from concurrent.futures import ThreadPoolExecutor, as_completed
datarALL =
with ThreadPoolExecutor(max_workers=num_of_pages) as executor:
futh = [executor.submit(getdata(page, hed, value_limit), page) for page in pages]
for data in as_completed(futh):
datarALL = datarALL + data.result()
return datarALL
Basically start() create pages and getdata() runs per page.
The print shows me:
start()
getdata()
0
http://...WithTotal=true&cultureid=1&&offset=0&limit=249
page 0 finished
1
http:/...WithTotal=true&cultureid=1&&offset=249&limit=249
page 1 finished
etc...
However I expected that all pages would be created on the same time then each one of them runs when the thread gets CPU time but what actually happens is that only when getdata() finishs The next page is created. Which means the threads are useless here. I should note that each getdata() call takes about 4-5 minutes to finish.
getdata()
getdata()
I suspect that the problem is here:
futh = [executor.submit(getdata(page, hed, value_limit), page) for page in pages]
It waits for getdata() to finish before the next loop run.
getdata()
How can I fix it and make it works with the threads?
ThreadPoolExecutor
ProcessPoolExecutor
@abarnert The function makes API call there is no havey calculation there.. It mostly waiting for records to be fetched. The action that takes all time there is:
requests.get(uri, data=data, headers=hed, verify=False). There is no shared data between the threads.. each thread brings another chunk of 249 then I merge them all together.– Programmer120
Aug 12 at 10:54
requests.get(uri, data=data, headers=hed, verify=False)
@abarnert see edit.. I added the full function code.
– Programmer120
Aug 12 at 10:58
2 Answers
2
The problem is that you're not executing tasks in the executor at all. Instead, you're calling the 5-minute function, then trying to execute its result as a task:
[executor.submit(getdata(page, hed, value_limit), page) for page in pages]
That getdata(page, hed, value_limit) is a function call: it calls getdata and waits for its return value.
getdata(page, hed, value_limit)
getdata
What you need to do is pass the function itself to submit, like this:
submit
executor.submit(getdata, page, hed, value_limit)
I'm not sure what you're trying to do with the extra , page, but if you wanted a list of (future, page) tuples, that would be:
, page
(future, page)
[(executor.submit(getdata, page, hed, value_limit), page) for page in pages]
I'm not sure I'm following. This is the first time I'm trying multithreading. This code is based on example I saw.. I passed the page argument just for the printing. It's not actually needed. The [(executor.submit(getdata, page, hed, value_limit), page) for page in pages] works great! I'm open to any further suggestion you have..
– Programmer120
Aug 12 at 10:47
@Programmer120 It's not about multithreading, If you want to pass a function to someone—whether it's as a task for
ThreadPoolExecutor, or as a callback for a tkinter.Button, or whatever—you have to pass the function, not call the function and pass the result.– abarnert
Aug 12 at 10:49
ThreadPoolExecutor
tkinter.Button
You have to submit a function (without actually calling it!) to executor.submit. So in your particular case, you should fix the hed and value_limit arguments in the getdata function to make it a function with a single argument page.
executor.submit
hed
value_limit
getdata
page
The easiest solution may look like the following:
getdata_partial = lambda page: getdata(page, hed, value_limit)
Then you could use it as shown below:
futh = [executor.submit(getdata_partial, page) for page in pages]
Another possible solution is to use functools.partial. You may find it even more elegant, but the idea is still the same.
You don't need to use
lambda here; execute.submit takes a function, *args, and **kwargs. And you're already taking advantage of that by passing page as an argument, so there's no reason to treat the other arguments any differently.– abarnert
Aug 12 at 10:34
lambda
execute.submit
*args
**kwargs
page
Also, I know people misuse the term all the time, but if you're going to use technical terms novices don't understand, you really should be careful to get them right. A curried function and a partially-applied function are not the same thing. A curried function is one that takes its arguments one at a time, returning a new function. If you actually had a curried
getdata function, you'd call it as getdata_curried(page)(hed)(value_limit).– abarnert
Aug 12 at 10:36
getdata
getdata_curried(page)(hed)(value_limit)
Completely agreed, thanks for your remarks! Will change
getdata_curried to getdata_partial :)– Gevorg Davoian
Aug 12 at 10:39
getdata_curried
getdata_partial
Regarding the usage of
lambda, it's more of a personal preference to explicitly show that in the current context it's only the page argument that really matters and allows us to differentiate between the submitted tasks.– Gevorg Davoian
Aug 12 at 10:51
lambda
page
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
Is that 4-5 minutes mostly CPU-bound, running heavy computation? If so, in CPython, the Global Interpreter Lock (GIL) means only one of them can progress at a time. Threads are great when you have IO-bound computation (like waiting on network servers), but not very helpful for CPU-bound work. But if that's the issue, you can usually fix it by just changing the
ThreadPoolExecutorto aProcessPoolExecutor. As long as they don't need any shared data, and the arguments and return values are picklable types and not too huge, it just works.– abarnert
Aug 12 at 10:27