47

I'm working on a project in Python using the "thread" module.

How can I make a global variable (in my case I need it to be True or False) that all the threads in my project (about 4-6) can access?

Shay
  • 1,375
  • 5
  • 16
  • 26

4 Answers4

45

We can define the variable outside the thread classes and declare it global inside the methods of the classes.

Please see below trivial example which prints AB alternatively. Two variables flag and val are shared between two threads Thread_A and Thread_B. Thread_A prints val=20 and then sets val to 30. Thread_B prints val=30, since val is modified in Thread_A. Thread_B then sets val to 20 which is again used in Thread_A. This demonstrates that variable val is shared between two threads. Similarly variable flag is also shared between two threads.

import threading
import time
c = threading.Condition()
flag = 0      #shared between Thread_A and Thread_B
val = 20

class Thread_A(threading.Thread):
    def __init__(self, name):
        threading.Thread.__init__(self)
        self.name = name

    def run(self):
        global flag
        global val     #made global here
        while True:
            c.acquire()
            if flag == 0:
                print "A: val=" + str(val)
                time.sleep(0.1)
                flag = 1
                val = 30
                c.notify_all()
            else:
                c.wait()
            c.release()


class Thread_B(threading.Thread):
    def __init__(self, name):
        threading.Thread.__init__(self)
        self.name = name

    def run(self):
        global flag
        global val    #made global here
        while True:
            c.acquire()
            if flag == 1:
                print "B: val=" + str(val)
                time.sleep(0.5)
                flag = 0
                val = 20
                c.notify_all()
            else:
                c.wait()
            c.release()


a = Thread_A("myThread_name_A")
b = Thread_B("myThread_name_B")

b.start()
a.start()

a.join()
b.join()

Output looks like

A: val=20
B: val=30
A: val=20
B: val=30
A: val=20
B: val=30
A: val=20
B: val=30

Each thread prints the value which was modified in another thread.

Mr.Zeus
  • 424
  • 8
  • 25
user3343166
  • 594
  • 5
  • 4
  • 5
    why isn't `c` (the `threading.Condition()`) defined as `global`? – Shai Jul 13 '17 at 08:01
  • 1
    @Shai because it's not being reassigned. It does not need to be. Flag and val on the other hand are reassigned, so should that assignment create a local variable, or modify the global one? it's ambiguous without the global keyword. But `c` is not reassigned, so it's not ambiguous. – alkasm May 11 '20 at 06:30
  • Well this assumes both threads are created in the same source file, so they both have (closure-style) access to the global names doesn't it? – matanster Jun 06 '20 at 10:29
  • 2
    @matanster yes, both threads should be created in the same source file. That's not an optimal solution don't know why it's the accepted one ! I am trying to find a way to do it with queues.. – BAKYAC Jan 05 '22 at 16:10
  • @BAKYAC https://stackoverflow.com/a/15461553/2340939 – user2340939 Sep 16 '22 at 10:48
11

With no clue as to what you are really trying to do, either go with nio's approach and use locks, or consider condition variables:

From the docs

# Consume one item
cv.acquire()
while not an_item_is_available():
    cv.wait()
get_an_available_item()
cv.release()

# Produce one item
cv.acquire()
make_an_item_available()
cv.notify()
cv.release()

You can use this to let one thread tell another a condition has been met, without having to think about the locks explicitly. This example uses cv to signify that an item is available.

doctorlove
  • 18,872
  • 2
  • 46
  • 62
2

How about using a threading.Event object per this description?

For example in the script below, worker1 and worker2 share an Event, and when worker2 changes its value this is seen by worker1:

import time
from threading import Thread, Event


shared_bool = Event()


def worker1(shared_bool):
    while True:
        if shared_bool.is_set():
            print("value is True, quitting")
            return
        else:
            print("value is False")
        time.sleep(1)


def worker2(shared_bool):
    time.sleep(2.5)
    shared_bool.set()


t1 = Thread(target=worker1, args=(shared_bool, ))
t2 = Thread(target=worker2, args=(shared_bool, ))
t1.start()
t2.start()
t1.join()
t2.join()

Prints out:

value is False
value is False
value is False
value is True, quitting
0

I was working on a tiny Pomodoro Timer project using ttkbootstrap and wanted to make a timing thread separate from the main thread of the program because time.sleep froze the main program when called.

I ended up creating a Queue that would send data to the main thread. Here is an example of a simple counter.

from time import sleep
from queue import Queue
from threading import Thread, Event
import ttkbootstrap as ttk


# A "tick" will be represented as `1` and is put on the queue every second.
ticking_queue = Queue()
timer_running = Event()

# Set up the app, as well as variables that we will modify
app = ttk.Window(themename="darkly", size=(240, 240))
int_var = ttk.IntVar(app, 0)


def timer_job():
    while True:
        sleep(1)
        ticking_queue.put(1)


def start_timer():
    if not timer_running.is_set():
        thread = Thread(target=timer_job, daemon=True)
        thread.start()
        timer_running.set()


def event_loop():
    while not ticking_queue.empty():
        int_var.set(int_var.get() + ticking_queue.get_nowait())
    
    # Repeat the event loop every 100ms and try to update.
    app.after(100, event_loop)


# Set up UI
label = ttk.Label(app, textvariable=int_var)
button = ttk.Button(app, text="Click me!", command=start_timer)
label.pack()
button.pack()


# Start the loops
event_loop()
app.mainloop()

So, how (and why) does this work?

Tkinter uses a main loop to run (notice the app.mainloop() at the end.) Any code after the call to app.mainloop() will not run until the app is closed. To get past this, we start the event loop beforehand. The function event_loop will be called after every 100 milliseconds.

In the event loop, we try and loop through the queue to see if there are any new ticks. If there are ticks, we update the int_var variable which is used to update the label of the app. One thing I like about queues is that say the app lagged and missed the previous tick. Well, there's no need to worry because the queue might have all the ticks that were missed.

When we click the button, we spawn a daemon thread that runs timer_job. A daemon thread is a thread that exits when the program closes. If we didn't set daemon=True, that thread would've run in an infinite loop. This function also sets the Event timer_running. This prevents any future button clicks from spawning more threads.

The timer_job function sleeps for every 1 second. After waking up, it puts the value of 1 onto the queue.

Ed The ''Pro''
  • 875
  • 10
  • 22