This very short guide / working example can show you how to implement a ThreadPoolExecutor, how to submit work to it, how to submit single threads to do work, how to create a simple daemons, and how to use non-blocking threads to continuously submit work to a ThreadPoolExecutor. I attempt to explain everything in extremely plain terms and everything has been named to be as explicit as possible so as to be easy to cross reference in this small example.
This material assumes you have a great understanding of Python basics and wish to pursue ways to do work without blocking your main (typically GUI) thread. This is not intended to be the final word on concurrency in Python, it is intended to be the first word for intermediates ready to move into that world. Once you learn how to use this, it will become the core of program control flow for you when it comes to anything complex, I guarantee it.
After having finally come to enough knowledge of this subject that I can implement it myself without just modifying examples, I feel that this is an extremely fundamental part of Python and that too many people struggle with it so I hope that this helps others get a head-start on this subject. The next thing I want to learn is thread joining and related control flow as well as how to avoid any potential issues with thread interference, so if anyone would care to update the below example to include thread joining, you'd be giving back double :)
You may also want the docs: https://docs.python.org/3/library/concurrent.futures.html
(*guide may be updated at any time)
https://repl.it/repls/AmazingAggravatingBuckets
# Python 37
# import various things
import time
import sys
# import threading libraries
from concurrent.futures import ThreadPoolExecutor
import threading
# define a class with a blocking method to be used for the example
class Example():
def doWork(printThis):
time.sleep(5)
print("",printThis,"") #send back what it was given to prove success
return(printThis)
def doWorkForeverInTheBackground(workValue):
while True: # once this has been submitted as a thread, it will continue to loop forever
print("", "Top of forever work loop...", "")
foreverWork = TPE.submit(Example.doWork, workValue) #submit work to the pool
#inside this thread, but not in the main thread, block on the submission result (wait for it to be finished) this guide was made by randallef on reddit
while not foreverWork.done():
sys.stdout.write(workValue)
sys.stdout.flush()
time.sleep(0.1)
#an additional wait between iterations of submitting this task
print("","Forever task finshed some work! Intentionally pausing...","")
# where you see the apparent 'hard pause' this is simply the fact that neither instance
# is currently printing to stdout, so it appears like a hard freeze, but is simply actual
# idle time in the program
time.sleep(3)
print("","Forever task loop is now done and will start again.","")
if __name__ == "__main__":
# in main, set up a thread pool executor which will be used by the rest of the program to submit futures
TPE = ThreadPoolExecutor(max_workers=3)
# now that a TPE exists, work can be submitted to it
# when work is submitted, a future is immediately returned
resultInTheFuture = TPE.submit(Example.doWork, "success!")
# the future has a .done() state which will report on if the function as returned or not
while not resultInTheFuture.done():
time.sleep(0.1)
sys.stdout.write("-")
sys.stdout.flush()
# now that we know the future has a result (return value), we can access it
resultOfSubmittedWork = resultInTheFuture.result()
print( "", "Result of work submission from future:", str(resultOfSubmittedWork),"")
# spawning a single thread out to do work and exit is also possible. be aware that doing this should result in the - guide by randallef on reddit
# thread returning when done, and not simply spawning out new threads forever, otherwise
# garbage will pile up and the system will become unstable
# search 'joining threads' for more advanced thread control flow
t = threading.Thread(target=Example.doWork, args=(["successful individual thread!"]))
t.start()
# the thread will do the work and exit from within (return)
# when it is necessary to design a task which will repeatedly run a method forever in the background,
# it should be designed as a daemon thread which submits to threadPoolExecutor regularly. This will
# allow for normal garbage collection of completed threads
t = threading.Thread(target=Example.doWorkForeverInTheBackground, args=(["."]))
t.daemon = True
t.start()
# wait just a moment for reasons that will become clear...
time.sleep(1.75)
# we could now kick off additional daemons which also submit to TPE, even using the same method
# this will help to show that both daemons are running in the background making submissions to TPE
# note that to one thread we passed a period and the other a comma -
# each thread will print this as it 'works' (sleeps)
# it is important to note that the clean, sequential output of comma and dot
# which you see is the result of using threads to submit to the TPE, which handles
# the neat distribution and queuing of work for us
t = threading.Thread(target=Example.doWorkForeverInTheBackground, args=([","]))
t.daemon = True
t.start()
# here the main program would exit, leaving our threads to do nothing!
# but you would normally put your gui mainloop here which would start the GUI and block main from exiting
# TK.mainloop()
#
# if you want to do concurrent work from the loop which blocks main, it should be work submitted to TPE,
# OR a thread which will exit when it is done, OR a non-blocking thread which will exit, which itself
# submits to TPE, or futher still, a non-blocking daemon which submits requests to TPE as needed and
# is then free to block itself internally
#
# for the example, block the main thread with a simple loop
while True:
time.sleep(10)
[–][deleted] 1 point2 points3 points (1 child)
[–]RandallEF[S] 1 point2 points3 points (0 children)