As @lutz had written, there is no danger of race conditions in case there are no shared data between two instances of the common_function. This is what is meant by referential transparency and generally all programming languages aim that such functions should be thread safe. Sometimes, you will need to write and use functions which change some global state. In such cases the modern adage is to use event driven programming - which means to not communicate directly between threads but communicate via some thread safe queueing system. In python I am a huge fan of the queue module for that matter. Another good queue module is the multiprocessing.queue for which a good example is here. I am also pasting the code here.
from multiprocessing import Process, Queue
def f(q):
q.put([42, None, 'hello'])
if __name__ == '__main__':
q = Queue()
p = Process(target=f, args=(q,))
p.start()
print(q.get()) # prints "[42, None, 'hello']"
p.join()
Finally in case you are not confident about some function (probably it is a big function and you dont understand the nuts and bolts), I would recommend that you use the fuzzying method. Here you define a simple function
FUZZ = True
def fuzz():
"""
fuzzing is a technique to make the race condition errors more visible
"""
if FUZZ:
time.sleep(random.random())
and then drop this function at random places inside your code. That should amplify any race conditions that there are in the code. This is of-course not a guaranteed method and so if your function is in a production application that gets called millions of times the better strategy will be to break the function to smaller more digestible parts. Watch Raymond Hettinger deliver a lecture about concurrent code in his famous talk on python threading. You can get the code that he is talking about here.