0

College assignment requested that we modify the code below to stop it from deadlocking, without changing the main function.

Right now, it is deadlocking because of how the locks end up waiting for each other. My professor mentioned using os.fork, which is not possible as I am using a Windows machine.

import threading
x = 0

def task(lock1, lock2, count):
    global x
    for i in range(count):
        lock1.acquire()
        lock2.acquire()
        # Assume that a thread can update the x value
        # only after both locks have been acquired.
        x+=1
        print(x)
        lock2.release()
        lock1.release()

# Do not modify the main method
def main():
    global x
    count = 1000
    lock1 = threading.Lock()
    lock2 = threading.Lock()
    T1 = threading.Thread(target = task, args = (lock1, lock2, count))
    T2 = threading.Thread(target = task, args = (lock2, lock1, count))

    T1.start()
    T2.start()
    T1.join()
    T2.join()
    print(f"x = {x}")

main()
4
  • "Changing task to this seems to have fixed it" - it didn't. Now your locks aren't providing mutual exclusion at all, and you have a potential double release bug. Commented Aug 29, 2019 at 22:49
  • Also, answers go in the answer section, not as question edits. Commented Aug 29, 2019 at 22:50
  • When I tried your implementation, the output was (newline separated) 1 2 4 3 5 7 8 9 6.... Commented Aug 29, 2019 at 22:56
  • 1
    No, it's definitely because of improper thread coordination. That kind of thing has never happened to me unless my threading implementation was bad in all my years of using Python. Commented Aug 29, 2019 at 23:02

1 Answer 1

3

Your threads need to lock the locks in a consistent order. You can do this by locking the one with the lower id value first:

def task(lock1, lock2, count):
    global x
    if id(lock1) > id(lock2):
        lock1, lock2 = lock2, lock1
    for i in range(count):
        lock1.acquire()
        lock2.acquire()
        # Assume that a thread can update the x value
        # only after both locks have been acquired.
        x+=1
        print(x)
        lock2.release()
        lock1.release()

With a consistent lock order, it's impossible for two threads to each be holding a lock the other needs.

(multiprocessing, subprocess, and os.fork are all unhelpful here. They would just add more issues.)

Sign up to request clarification or add additional context in comments.

4 Comments

So what you did was make it so they take turns going? If I understand correctly, isn't that the point of multiprocessing? To automatically do that? Thanks
No. What I did was make sure both threads try to lock the same lock first, instead of going for different locks. They don't take turns with anything. They do avoid trying to access x at the same time, which is a consequence and goal of using the locks correctly, but they don't consistently switch every iteration, and they don't coordinate whose turn it is to make an acquire call.
None of this is the point of multiprocessing. The point of the multiprocessing module is to avoid the parallelism limitations of the Python global interpreter lock by performing work in separate OS-level processes (and it makes a lot of confusing or performance-inhibiting tradeoffs to achieve this). multiprocessing is not a system to make threads take turns.
Thanks, you are correct. She clarified that the subprocess thing is used in the next part of the assignment. What you did makes a lot of sense.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.