Mutable vs Immutable Types in Python and Their Use in Multithreaded Programming
This article explains the memory‑management differences between mutable and immutable Python objects, demonstrates how each type behaves with code examples, and shows how immutability improves thread safety while providing practical synchronization techniques for mutable data in concurrent programs.
In Python, mutable and immutable types differ in how memory is managed: mutable objects (e.g., lists) have a single in‑memory instance that can be altered in place, while immutable objects (e.g., tuples) require a new allocation for any change, making them safer for copying and multithreaded use.
Example of a mutable list shows that the object's id remains constant after modifying elements or appending new items:
list1 = [1, 2, 3] print(id(list1)) # view list id list1[0] = 'one' print(id(list1)) # same id list1.append(4) print(id(list1)) # same idImmutable tuple example demonstrates that attempting to modify raises an error and that creating a new tuple yields a different id :
tup1 = ('a', 'b', 'c') print(id(tup1)) # tup1[0] = 'A' # TypeError tup2 = ('d', 'e', 'f') print(id(tup2))Mutable dictionary behaves like a list: its id stays the same after updating values or adding new keys:
dict1 = {"key": "value"} print(id(dict1)) dict1["key"] = 'another value' print(id(dict1)) dict1["new_key"] = 'new value' print(id(dict1))Immutable strings cannot be altered; creating a new string produces a new object:
str1 = "Hello" print(id(str1)) # str1[0] = 'X' # TypeError str2 = "Goodbye" print(id(str2))Characters are just one‑character strings; concatenating creates a new string object, while trying to reassign a character raises an error:
char1 = 'H' char2 = 'h' concat_char = char1 + char2 print(id(concat_char))Immutable data offers strong advantages in multithreaded environments because it cannot change, eliminating race conditions and allowing safe sharing and caching for performance gains.
A naïve counter updated by multiple threads without synchronization produces an incorrect result:
import threading counter = 0 def increment_counter():
global counter
for _ in range(10_000):
counter += 1 # create and start threads... print(counter) # not the expected valueUsing a lock (or other atomic primitive) fixes the problem:
counter_lock = threading.Lock() def increment_counter():
global counter, counter_lock
for _ in range(10_000):
with counter_lock:
counter += 1 # start threads and join print(counter) # expected resultA simple reader/writer example shows concurrent access to a shared dictionary, illustrating the need for proper synchronization.
import threading data = {} def reader():
while True:
print(f"Reader: {data.get('key')}") def writer():
while True:
data['key'] = len(data)
print(f"Writer: {len(data)}")Cache usage with a lock ensures that expensive computations are performed only once and shared safely across threads:
import threading cache = {} lock = threading.Lock() def get_cached_data(key):
if key in cache:
return cache[key]
with lock:
if key in cache:
return cache[key]
result = expensive_computation()
cache[key] = result
return resultAtomic integer (or similar atomic primitive) provides lock‑free thread‑safe increments:
import threading with atomic_int = AtomicInteger(0):
def increment():
atomic_int.increment()
# start multiple threads
print(atomic_int.value()) # expected resultExplicit lock usage with mutex.acquire() / release() demonstrates another way to protect mutable shared state:
import threading mutex = threading.Lock() count = 0 def increment():
global count
mutex.acquire()
try:
count += 1
finally:
mutex.release()When mutable data is accessed concurrently without synchronization, race conditions occur, as shown by a shared number being incorrectly updated; applying a lock resolves the issue and yields the correct final value.
import threading shared_number = 0 def change_number():
global shared_number
shared_number += 1 # without lock, final value is unexpected # with mutex = threading.Lock()
# acquire, modify, release print(shared_number) # expected result after lockingTest Development Learning Exchange
Test Development Learning Exchange
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.