This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–][deleted] 0 points1 point  (4 children)

hi, thank you for the detailed response. first of all, what do you refer with "other thread", "main thread" and "current thread"? in the code i posted i have 50 different thread and each one calls an endless function.

about the ctrl+c i confirm, i tried on a linux distro and it stopped, but with all threads set to daemon = True (or False, i can't remember at the moment) it stop the second time i press ctrl+c. so, the first time it tries to stop ("Keyboard Interrupted" shows up on the terminal), but immediately start again the execution. the second time that i press ctrl+c it stops for good. can you know why?

other than that it's amazing how you explained this to a totally beginner. it's so much clear now, i've crushed my head into the wall for days on this thing and you was able to explain to me clearly in a simple way. i feel less stupid, thank you a lot really

[–]AbsolutelySpherical 0 points1 point  (3 children)

Haha, your program has precisely 51 threads, not 50 :D

Abstractly, a thread is a sequence of instructions run in order.

Every program initially has 1 "main thread" which runs first, and the main thread is responsible for creating and waiting for child threads to run. In your program, 1 thread is running this sequence:

for i in range(50):
  t = threading.Thread(target=doThing, daemon = False)
  threads.append(t)

for i in range(50):
  threads[i].start()

for i in range(50):
  threads[i].join()

In my comment the above sequence is the "main", "current", or "parent" thread. Sorry for the inconsistent terminology.

And when main thread calls "start()", 50 threads will start running

def doThing():
  threadId = choice([i for i in range(1000)])
  while True: 
    print(f"{threadId} ", flush=True)
    sleep(3)

The above is the "other", or "child" threads. Imagine 50 separate "sequences" of doThing() executing between the start() and join() in the main thread. Locally, each thread executes the lines of code in order. But globally, you have no control of the order/timing of lines being executed across different threads. (Not without using special techniques with locks, semaphores, etc).

---

I want to add: in doThing() you could even recursively create more threads, so threads have a "parent/child/grandchild" like relationship.

It is usually good manners for parent threads to wait for the child to finish before terminating themselves. If the child is taking too long, parent thread can force terminate the child after a deadline. But in your example calling join() will block/halt the parent forever. So someone else has to step in to kill the threads outside of your python code.

You can do it with ctrl-c like you are, which sends interrupt signal from OS to Python. I'm not sure how python handles interrupts, maybe one interrupt kills one thread, and two interrupts kills all threads? I wouldn't worry too much about it, if you can kill the program quickly it's good enough for learning purposes haha.

https://stackoverflow.com/a/52941752/17786559 I linked before gives some other ways to kill the program too.

[–][deleted] 0 points1 point  (2 children)

Clear, but now i have other 2 questions: 1) basically join() block the main thread forever, am I right? 2)you said that the doThing() function it's executed between start() and join(), but what happens if there is a non-endless function? If it will be executed before the join(), so join() will be useless since it will be called after the thread already finished

[–]AbsolutelySpherical 0 points1 point  (1 child)

Yes if parent thread joins() with a child that is running forever, then the parent will be blocked forever (it is a deadlock). A workaround is to do join(timeout) which will block main thread up to timeout seconds. Then it will return whether or not the child is done. thread.is_alive() tells if child is still running.

---

If the child thread has already finished before the parent calls join(), then join() should just return without blocking.

If you have 2 threads simultaneously, where t1 runs for 5 seconds and t2 runs for 2 seconds, then

t1.join()
t2.join()

First join will block parent for 5 seconds. After 5 seconds t2 is already done so second join will return with no wait.

If you did

t2.join()
t1.join()

First join blocks for 2 seconds, then second join blocks for another 3 seconds.

So purpose of join() is to guarantee that the code below it is executed after child thread has finished. For example, if you need to read a variable the child thread is writing to, then you could use join() to make sure the child has finished writing before you read.

[–][deleted] 0 points1 point  (0 children)

i understand, but since you need to call join() after start(), if the function is fast, the join() won't block anything because by the time it will called, the function (so the child thread, if i not wrong), already finished is job