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.
 
    