Lock

To make threading programming thread safe, we can use locking mechanism python provides. Python provides mutex locking system with threading.Lock.

lock = threading.Lock()
 
lock.acquier()
change_value()
update_value()
lock.release()

It can be done using with context manager.

with lock:
    change_value()
    update_value()

Example

class Atom():
    def __init__(self, value) -> None:
        self.value = value
        self._lock = threading.Lock()
 
    def inc(self, name) -> int:
        logging.info("Starting by thread %d", name)
        logging.info("Acquire Lock by thread %d", name)
        # with self._lock:
        self._lock.acquire()
        logging.info("Acquired Lock by thread %d", name)
        copy = self.value
        copy += 1
        time.sleep(0.1)
        logging.info("Update by thread %d", name)
        self.value = copy
        self._lock.release()
        logging.info("Release Lock by thread %d", name)
 
 
if __name__ == "__main__":
    format = "%(asctime)s: %(message)s"
    logging.basicConfig(format=format, level=logging.INFO,
                        datefmt="%H:%M:%S")
 
    atom = Atom(1)
    with ThreadPoolExecutor(max_workers=2) as executor:
        executor.map(atom.inc, list(range(1, 3)))
 
    logging.info("MAIN : atom value is %d", atom.value)

Output

13:48:02: Starting by thread 1
13:48:02: Acquire Lock by thread 1
13:48:02: Acquired Lock by thread 1
13:48:02: Starting by thread 2
13:48:02: Acquire Lock by thread 2
13:48:02: Update by thread 1
13:48:02: Release Lock by thread 1
13:48:02: Acquired Lock by thread 2
13:48:02: Update by thread 2
13:48:02: Release Lock by thread 2
13:48:02: MAIN : atom value is 3