The output near the point you are in this case is to keep a "record" of how many threads you have already fired, and only add new threads when you hear "space."
Your program creates a maximum number of threads, but it does not have any code to add new threads (or pass new elements to existing threads) after reaching that maximum number. The logic for this is sweating 'while', 'if' and one or two variables to count how many threads are active, and fire more if the number of threads is less than their limit (in case 10).
The "standard" solution for this type of problem is a little more elegant, however: it passes through the creation of a fixed set of threads - with the desired number of threads - this set in the literature is called "threadpool "- in practice it is a collection - which can be a list, where each element is one of its threads - which in this context is called" worker "
In this case a data structure called "queue" is used which is fed by a main thread, and from where each worker thread pulls elements. This way, a worker thread can pull a new element to process as soon as it finishes the previous job, regardless of what others are doing.
In other words: you put elements in the queue on the main thread - each of the previously created worker threads gets in a continuous loop by taking the next element in the queue and processing it.
You need some other way to pass information to the worker threads to say that the processing is gone, and they may be terminated. Typically this is done by placing a "Marker" object in the queue, and the threads stop when they find it. In your scenario however, it would take a little more logic to get the elements in line gradually so that the Marker would not stay at the end of the queue (and you get back to your initial problem) - then for simpler scenarios: simpler solutions: a global variable "COMPLETE" is used, and set by a worker thread that finds the result.
Note that both in thread theory and implementation in lower-level languages, this would be much more complicated: there are race conditions for the global variable to be used, which would have to be taken into account - in the case of Python, the GIL (Global Interpreter Lock) takes care of this for us - and, the Queues are already existing classes, using internally the required locks - so it is very simple to use them without major concerns.
(The price paid for this is fairly ). If the CPU-intensive threads are in a pure Python algorithm, the GIL is not released during execution of the algorithm, and its gain using threads The alternative would be to use "multiprocessing" rather than "threading" - this puts each worker in a separate process, and ends the GIL problem (but you will need another not the global variable to synchronize the Workers) - Or, write your execute
function in Cython, and use the call available in that Python super-set to release GIL.
Here's the example using Python3's threading and Queue with your scenario:
from threading import Thread
from queue import Queue
import random
import time
COMPLETE = False
class Worker(Thread):
def __init__(self, queue, output):
self.queue = queue
self.output = output
super(Worker, self).__init__()
def run(self):
while not COMPLETE:
element = self.queue.get()
self.execute(element)
def execute(self, palavra):
global COMPLETE
print('\ntentando palavra'+palavra)
time.sleep(1)
print(palavra+' finalizada')
if random.randint(0, 10) == 5:
COMPLETE = True
self.output.put(palavra)
def main(palavras, nworkers):
queue = Queue()
result_queue = Queue()
threads = [Worker(queue, result_queue) for _ in range(nworkers)]
for t in threads:
t.start()
for p in palavras:
queue.put(p)
result = result_queue.get()
print ("O resultado final é:", result)
palavras = ['palavra_{}'.format(i) for i in range(50)]
main(palavras, nworkers = 10)
To learn more, see the Queue documentation: link (even has an example similar to this there)