6

I have this python threading code.

import threading

def sum(value):
    sum = 0
    for i in range(value+1):
        sum += i
    print "I'm done with %d - %d\n" % (value, sum)
    return sum

r = range(500001, 500000*2, 100)

ts = []
for u in r:
    t = threading.Thread(target=sum, args = (u,))
    ts.append(t)
    t.start()

for t in ts:
   t.join()

Executing this, I have hundreds of threads are working.

enter image description here

However, when I move the t.join() right after the t.start(), I have only two threads working.

for u in r:
    t = threading.Thread(target=sum, args = (u,))
    ts.append(t)
    t.start()
    t.join()

enter image description here

I tested with the code that does not invoke the t.join(), but it seems to work fine?

Then when, how, and how to use thread.join()?

prosseek
  • 182,215
  • 215
  • 566
  • 871
  • Possible duplicate: https://stackoverflow.com/questions/15085348/what-is-the-use-of-join-in-python-threading – katronai Jul 17 '21 at 15:12
  • Does this answer your question? [What is the use of join() in Python threading?](https://stackoverflow.com/questions/15085348/what-is-the-use-of-join-in-python-threading) – katronai Jul 17 '21 at 15:13

3 Answers3

9

You seem to not understand what Thread.join does. When calling join, the current thread will block until that thread finished. So you are waiting for the thread to finish, preventing you from starting any other thread.

The idea behind join is to wait for other threads before continuing. In your case, you want to wait for all threads to finish at the end of the main program. Otherwise, if you didn’t do that, and the main program would end, then all threads it created would be killed. So usually, you should have a loop at the end, that joins all created threads to prevent the main thread from exiting down early.

poke
  • 369,085
  • 72
  • 557
  • 602
  • 1
    The idea behind `join` is that you call it _any time_ you want one thread to wait for another thread to exit. That is often seen at the end of main(), but depending on the application, there may be other times when it is desirable for one thread to wait for another. – Solomon Slow Jan 31 '14 at 19:57
5

Short answer: this one:

for t in ts:
   t.join()

is generally the idiomatic way to start a small number of threads. Doing .join means that your main thread waits until the given thread finishes before proceeding in execution. You generally do this after you've started all of the threads.

Longer answer:

len(list(range(500001, 500000*2, 100)))
Out[1]: 5000

You're trying to start 5000 threads at once. It's miraculous your computer is still in one piece!

Your method of .join-ing in the loop that dispatches workers is never going to be able to have more than 2 threads (i.e. only one worker thread) going at once. Your main thread has to wait for each worker thread to finish before moving on to the next one. You've prevented a computer-meltdown, but your code is going to be WAY slower than if you'd just never used threading in the first place!

At this point I'd talk about the GIL, but I'll put that aside for the moment. What you need to limit your thread creation to a reasonable limit (i.e. more than one, less than 5000) is a ThreadPool. There are various ways to do this. You could roll your own - this is fairly simple with a threading.Semaphore. You could use 3.2+'s concurrent.futures package. You could use some 3rd party solution. Up to you, each is going to have a different API so I can't really discuss that further.


Obligatory GIL Discussion

cPython programmers have to live with the GIL. The Global Interpreter Lock, in short, means that only one thread can be executing python bytecode at once. This means that on processor-bound tasks (like adding a bunch of numbers), threading will not result in any speed-up. In fact, the overhead involved in setting up and tearing down threads (not to mention context switching) will result in a slowdown. Threading is better positioned to provide gains on I/O bound tasks, such as retrieving a bunch of URLs.

multiprocessing and friends sidestep the GIL limitation by, well, using multiple processes. This isn't free - data transfer between processes is expensive, so a lot of care needs to be made not to write workers that depend on shared state.

roippi
  • 25,533
  • 4
  • 48
  • 73
  • minor nitpick, even without the GIL, CPU bound tasks in python are going to be slow, since cPython is an interpreted language. Fixing the bigger issue, by porting CPU intensive tasks to a more efficient language (like C) fixes both problems, since C extensions don't (ordinarily) retain the GIL. for example, see Numpy! – SingleNegationElimination Jan 31 '14 at 18:01
  • @IfLoop I'm not sure how that's a useful statement. "python is slower than C" is trivially true, but that doesn't mean that python is unfit for all CPU-bound tasks. – roippi Jan 31 '14 at 19:22
4

join() waits for your thread to finish, so the first use starts a hundred threads, and then waits for all of them to finish. The second use wait for end of every thread before it launches another one, which kind of defeats the purpose of threading.

The first use makes most sense. You run the threads (all of them) to do some parallel computation, and then wait until all of them finish, before you move on and use the results, to make sure the work is done (i.e. the results are actually there).

che
  • 12,097
  • 7
  • 42
  • 71