简体   繁体   中英

How to synchronize threads with shared global variable in python?

i'm currently trying to unterstand threading in python and i wrote a program that ideally would have 2 threads alternating between incrementing and decrementing a global variable but no matter how i spread out the lock it inevitably becomes out of sync.

number = 0
lock = threading.Lock()
def func1():
    global number
    global lock
    while True:
        try:
            lock.acquire()
            number += 1
        finally:
            lock.release()
        print(f"number 1 is: {number}")
        time.sleep(0.1)

def func2():
    global number
    global lock
    while True:
        try:
            lock.acquire()
            number -= 1
        finally:
            lock.release()
        print(f"number 2 is: {number}")
        time.sleep(0.1)

t1 = threading.Thread(target=func1)
t1.start()

t2 = threading.Thread(target=func2)
t2.start()

t1.join()
t2.join()

the output should look something like this:

number 1 is: 1
number 2 is: 0
number 1 is: 1
number 2 is: 0
number 1 is: 1
number 2 is: 0
number 1 is: 1
number 2 is: 0

but right now it looks like this:

number 1 is: 1
number 2 is: 0
number 1 is: 1
number 2 is: 0
number 2 is: -1number 1 is: 0

number 2 is: -1number 1 is: 0

number 1 is: 1number 2 is: 0

any idea how to do this without falling out of sync?

First, avoid using global variables with threads in python. Use a queue to share the variables instead.

Second, the lock acquisition in non-deterministic. At the moment a lock is released, you have no guarantee that the other thread will grab it. There is always a certain probability that the thread that just released the lock can grab it again before the other thread.

But in your case, you can avoid problems because you know the state that the variable needs to be to accept modifications by one thread or the other. So, you can enforce the protection for modification by verifying if the variable is in the right state to accept a modification.

Something like:

from threading import Thread
import time
from queue import Queue

def func1(threadname, q):
    while True:
        number = q.get()
        
        if number == 0:
            number += 1
            print(f"number 1 is: {number}")

        q.put(number)
        time.sleep(0.1)

def func2(threadname, q):
    while True:
        number = q.get()

        if number == 1:
            number -= 1
            print(f"number 2 is: {number}")

        q.put(number)
        time.sleep(0.1)

queue = Queue()
queue.put(0)
t1 = Thread(target=func1, args=("Thread-1", queue))
t2 = Thread(target=func2, args=("Thread-2", queue))

t1.start()
t2.start()
t1.join()
t2.join()

For starters, time.sleep is not exactly accurate . And depending on the python-implementation you're using (most likely cpython) multithreading might not quite work the way you're expecting it to . These two factors allow the initially correct timing of your threads to get out of sync within fairly short time.

There solution for this problem is to enforce alternate operation on the variable by the two threads via two locks:

import time
import threading

var = 0


def runner(op, waitfor, release):
    global var

    while True:
        try:
            # wait for resource to free up
            waitfor.acquire()

            # operation
            var = op(var)
            print(f"var={var}")
        finally:
            # notify other thread
            release.release()

        time.sleep(0.1)


# init locks for thread-synchronization
lock_a = threading.Lock()
lock_b = threading.Lock()
lock_a.acquire()
lock_b.acquire()

# create and start threads (they'll wait for their lock to be freed)
thread_a = threading.Thread(target=runner, args=(lambda v: v - 1, lock_a, lock_b))
thread_b = threading.Thread(target=runner, args=(lambda v: v + 1, lock_b, lock_a))
thread_a.start()
thread_b.start()

# let thread_b start the first operation by releasing the lock
lock_b.release()

In the above code, each thread has a lock that can be used to notify it, that the resource may be used by it. Thus threads can hand control over the global variable to each other.

thanks for all your answers, i remember seing someone in the comments mentioned using events or something about event based threading and that solved my issue. here's the code:

number = 0
event_number = threading.Event()
event_number.clear()

def func1():
    global number
    global event_number
    while True:
        if not event_number.is_set():
            number += 1
            print(f"func 1 is {number}")
            event_number.set()
        else:
            pass
        time.sleep(2)

def func2():
    global number
    global event_number
    while True:
        if event_number.is_set():
            number -= 1
            print(f"func 2 is {number}")
            event_number.clear()
        else:
            pass
        time.sleep(2)

t1 = threading.Thread(target=func1)
t2 = threading.Thread(target=func2)

t1.start()
t2.start()

t1.join()
t2.join()

now i notice that sometimes one of the loops will either not wait it's alloted time and print right away or wait double the time but at least the number only stays within those 2 values.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM