1

I am learning about threading in Python and I'm confused about the following code, which I wrote. After starting the Heartbeat thread, how does the stuff method call work relative to the run method? Is there a race condition regarding i and, if so, how would I prevent that?

I'm wondering if this type of construction can be effectively used to communicate with a thread. I do not understand how it works under the hood, however, in terms of method calls in the Heartbeat class after run is activated. I'm assuming sleep allows stuff to execute, but I don't know why. I may be over-complicating this and missing something obvious; my lack of intuition, however, is unsettling. Any help or advice would be greatly appreciated.

   1 import threading
   2 import time
   3 
   4 class Heartbeat(threading.Thread):
   5   def __init__(self, delay):
   6     threading.Thread.__init__(self, daemon = True)
   7     self.delay = delay
   8     self.i = 0
   9   
  10   def run(self):
  11     while True:
  12       print("i = {}".format(self.i))
  13       time.sleep(self.delay)
  14   
  15   def stuff(self, i):
  16     self.i = i
  17 
  18 if __name__ == "__main__":
  19   hbThread = Heartbeat(3)
  20   hbThread.start()
  21   
  22   i = 0
  23   while True:
  24     time.sleep(1)
  25     i += 1
  26     hbThread.stuff(i)

And the output:

i = 0
i = 2
i = 5
i = 8
i = 11

Edit: In my opinion, this question is different than the suggested duplicate question for a few reasons. This is a much simpler question with a very specific point while the other is a broad question about multithreading in general. The answers are very helpful, but I don't see any that explain the inner-workings of my situation nor was there an explanation of other methods being called from a different thread as far as I could tell. If it's there, it may be beyond the scope of a beginner to fully comprehend; this question is simpler and more to the point, so I think it has a place.

Darkonaut
  • 20,186
  • 7
  • 54
  • 65
Anthony
  • 1,760
  • 1
  • 23
  • 43
  • Possible duplicate of [How to use threading in Python?](https://stackoverflow.com/questions/2846653/how-to-use-threading-in-python) – stovfl Jan 25 '19 at 16:55
  • @stovfl I read that thread, but I didn't see an answer to my question. I think the answers in that thread are helpful, but more complex than the nature of my question I believe. The answer to my question by Darkonaut is exactly what I was hoping to understand. – Anthony Jan 25 '19 at 17:01

1 Answers1

1

When you inherit from Thread your instance will be like a person standing with one foot in the kitchen and with one foot in the living room. In which thread a method will run depends on from which thread you call it. run will be the main-function in the new thread and every method it calls runs there. If you call stuff from the parent, it will run in the MainThread. Because of the GIL, only one thread can run at a time. A thread in Python 3 would hold the GIL (and hence be able to execute Python code) by default for up to 5 ms, or drop it earlier when doing blocking I/O (like your time.sleep). All threads in a process share the same memory so you can append to the instance from the new thread and from the MainThread and both will have access to the data.

Darkonaut
  • 20,186
  • 7
  • 54
  • 65
  • Thanks. I think this answers my question perfectly and succinctly. To make sure I understand, the main thread, so to speak, is running `stuff` and that's perfectly fine to do, right? I think the other part of this is the `run` method, which is the only method in `Heartbeat` that runs in the new thread because it doesn't make any other calls. Thanks for your answer and contribution. – Anthony Jan 25 '19 at 17:00
  • 1
    @Anthony It doesn't make much sense to call `stuff` from the MainThread, let `run` handle it for clarity. But you can set flags from the MainThread on the instance for example and the child-thread would check that flag to know when to exit. – Darkonaut Jan 25 '19 at 17:07
  • I should have clarified - my intent isn't to necessarily run the logic of `Heartbeat`. I am trying to figure out how to read/write values to/from the new thread and also I want to be able to terminate the thread using flags. Presuming I don't do much else in `stuff` or other methods like it, does my use case fit the code I wrote or is there a better way to do it? – Anthony Jan 25 '19 at 17:30
  • 1
    @Anthony For data exchange between the two threads yes, but I would restrict it to start and final data and not intermediate calculation steps like in your example. Keep it simple, restrict calls from the parent to managing-calls. If you need also intermediate data exchange between the threads I would prefer using a `queue.Queue`. – Darkonaut Jan 25 '19 at 17:44
  • 1
    @Anthony Example for basic usage of `queue.Queue` in my answer [here](https://stackoverflow.com/a/53432672/9059420) – Darkonaut Jan 25 '19 at 17:58
  • Thanks. That helped a lot. – Anthony Jan 25 '19 at 20:08