-2

I tried out the code found here and found some unexpected behavior. I reproduce the code below

import threading

shared_balance = 0
N = 500


class Deposit(threading.Thread):
    def run(self):
        for _ in range(N):
            global shared_balance
            shared_balance += 1


class Withdraw(threading.Thread):
    def run(self):
        for _ in range(N):
            global shared_balance
            shared_balance -= 1


threads = [Deposit(), Withdraw()]

for thread in threads:
    thread.start()

for thread in threads:
    thread.join()

print(shared_balance)

When I try to run this for N = 500, I always see 0 - that means no statements are executed out of order. However, when I set N = 500000, I always see a non zero result - proving that the statements are executed out of order.

Is there an explanation for why this happens? The time taken by shared_balance += 1 should always be the same - so I'm not sure why increasing the N causes python to switch the running thread.

Thanks!

dumbPotato21
  • 5,669
  • 5
  • 21
  • 34
  • 2
    Do the answers to this [question](https://stackoverflow.com/questions/59436505/does-new-implementation-of-gil-in-python-handled-race-condition-issue) help at all? – quamrana Aug 20 '23 at 15:55
  • Is there a particular reason why the `global` statements are _inside_ the loops? – John Gordon Aug 20 '23 at 16:06
  • @quamrana thanks for that link! Does this mean that every thread - despite whether it has completed its execution or not - will release control and switch to a different thread after a specified interval? If so, isn't that severely limiting? – dumbPotato21 Aug 20 '23 at 16:12
  • What limitation are you thinking of? – quamrana Aug 20 '23 at 16:16
  • Hmm, well I thought a switch in threads would only happen when there's a block for IO. Something like what happens in JS - control switches away from an async function only when you await a network request or something. Does it make sense to switch between threads constantly? Maybe I'm misunderstanding something here - happy to be corrected! – dumbPotato21 Aug 20 '23 at 16:19
  • Historically, threads have always had to be interrupted somehow (I think I was working on a mini computer which switched on the mains frequency clock). Python3 now also has a time based switch. You *can* use `async/await` to manually indicate when to switch tasks, which you would definitely do to take advantage of IO blocking calls. – quamrana Aug 20 '23 at 16:26
  • 1
    "The time taken by shared_balance += 1 should always be the same" citation needed. Considering that integers in Python are variable length (and thus may be bigger than biggest machine register), that can't possibly be true even for the increment operation itself. – Dan Mašek Aug 20 '23 at 16:36
  • 1
    "Does it make sense to switch between threads constantly?" -- Preemptive multitasking. The OS needs to be able to switch even between threads that never block for IO. Being able to switch it sooner (before the whole timeslice is used up) is an optimization of that basic idea. – Dan Mašek Aug 20 '23 at 16:38
  • Re, "I thought a switch in threads would only happen when there's a block for IO." That's called [_cooperative multitasking_](https://en.wikipedia.org/wiki/Cooperative_multitasking). A thread in a cooperative multitasking system can only be swapped out when it makes a system call/library call that is documented to be a _yield point._ The alternative is a [_preemptive multitasking system_](https://en.wikipedia.org/wiki/Cooperative_multitasking) in which a thread can be swapped out at any time with no warning. Cooperative systems can still be found in very small embedded applications... – Solomon Slow Aug 20 '23 at 17:30
  • ...e.g., like the controller for your microwave oven. Otherwise, they have _mostly_ (but not entirely) been abandoned in favor of preemptive systems. – Solomon Slow Aug 20 '23 at 17:31
  • thanks everyone! I think I finally understand why this works the way it does. Need to brush up my OS knowledge :-) – dumbPotato21 Aug 20 '23 at 19:28

0 Answers0