Thanks so much Jason Pan for suggesting that method. The thread1 if statement is not atomic, so that while that statement executes, it's possible for thread2 to intrude on thread1, allowing non-reachable code to be reached. I've organized ideas from the prior posts into a complete demonstration program (below) that I ran with Python 2.7.
With some thoughtful analysis I'm sure we could gain further insight, but for now I think it's important to demonstrate what happens when non-atomic behavior meets threading.
# ThreadTest01.py - Demonstrates that if non-atomic actions on
# global variables are protected, task can intrude on each other.
from threading import Thread
import time
# global variable
a = 0; NN = 100
def thread1(threadname):
while True:
if a % 2 and not a % 2:
print("unreachable.")
# end of thread1
def thread2(threadname):
global a
for _ in range(NN):
a += 1
time.sleep(0.1)
# end of thread2
thread1 = Thread(target=thread1, args=("Thread1",))
thread2 = Thread(target=thread2, args=("Thread2",))
thread1.start()
thread2.start()
thread2.join()
# end of ThreadTest01.py
As predicted, in running the example, the "unreachable" code sometimes is actually reached, producing output.
Just to add, when I inserted a lock acquire/release pair into thread1 I found that the probability of having the "unreachable" message print was greatly reduced. To see the message I reduced the sleep time to 0.01 sec and increased NN to 1000.
With a lock acquire/release pair in thread1 I didn't expect to see the message at all, but it's there. After I inserted a lock acquire/release pair also into thread2, the message no longer appeared. In hind signt, the increment statement in thread2 probably also is non-atomic.