When it comes to threads, your brain should have the impression that we can control when it starts but not when it ends, so how do you get the return value of a thread? Today we will share some of our own practices.
Method 1: Use a list of global variables to hold the return value
ret_values = []
def thread_func(*args):
...
value = ...
ret_values.append(value)
One reason to choose lists is that their append() methods are thread-safe, and in CPython, GIL prevents concurrent access to them. If you use a custom data structure, you need to add thread locks where the data is modified concurrently.
If you know in advance how many threads there will be, you can define a fixed-length list and store the return value according to the index, e.g.
from threading import Thread
threads = [None] * 10
results = [None] * 10
def foo(bar, result, index):
result[index] = f"foo-{index}"
for i in range(len(threads)):
threads[i] = Thread(target=foo, args=('world!', results, i))
threads[i].start()
for i in range(len(threads)):
threads[i].join()
print (" ".join(results))
Method 2: Override Thread's join method to return the return value of the thread function
The default thread.join() method just waits for the end of the thread function and does not return a value, where we can return the result of the function run with the following code.
from threading import Thread
def foo(arg):
return arg
class ThreadWithReturnValue(Thread):
def run(self):
if self._target is not None:
self._return = self._target(*self._args, **self._kwargs)
def join(self):
super().join()
return self._return
twrv = ThreadWithReturnValue(target=foo, args=("hello world",))
twrv.start()
print(twrv.join()) # 此处会打印 hello world。
So that when we call thread.join() and wait for the thread to finish, we also get the return value of the thread.
Method 3: Use the standard library concurrent.futures
I think the first two ways are too low-level. Python's standard library concurrent.futures provides a more advanced thread operation that can get the return value of a thread directly, which is quite elegant, with the following code.
import concurrent.futures
def foo(bar):
return bar
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
to_do = []
for i in range(10): # Simulation of multiple tasks
future = executor.submit(foo, f"hello world! {i}")
to_do.append(future)
for future in concurrent.futures.as_completed(to_do): # Concurrent execution
print(future.result())
The results of a particular run are as follows.
hello world! 8
hello world! 3
hello world! 5
hello world! 2
hello world! 9
hello world! 7
hello world! 4
hello world! 0
hello world! 1
hello world! 6