Inter-Process Communication (IPC) allows different processes in a program to share information and work together. When processes run separately, sharing data quickly and simply is important for smooth communication. One way to do this in Python is by using shared memory.
RawValue
is a tool in Python’s multiprocessing.sharedctypes
module that lets you share basic data types like integers or floats directly between processes. Unlike other shared memory types, RawValue
works without locks, making it simple for low-level data sharing when you don’t need automatic synchronization.
In this article, you will learn how to create RawValue
objects, pass them to child processes, and modify their contents. You’ll see how processes can work together using this lightweight shared memory option for basic data.
Setting Up: Importing and Creating RawValue
To get started, first import the necessary modules: multiprocessing
for creating processes, and ctypes
to specify data types for shared memory.
Next, create a RawValue
to hold a simple piece of data—let’s say a temperature reading represented as a floating-point number. This shared value can be accessed and modified by multiple processes.
Here’s a small example showing how to create a RawValue
for temperature and print its initial value:
from multiprocessing import Process
from multiprocessing.sharedctypes import RawValue
import ctypes
# Create a shared RawValue for temperature (float)
temperature = RawValue(ctypes.c_double, 25.0)
print(f"Initial temperature: {temperature.value}°C")
In this code, RawValue
creates a shared floating-point number initialized to 25.0 degrees Celsius. Any process that receives temperature
can read or change this value directly in shared memory.
Passing RawValue to Child Processes
To share data with child processes, you pass the RawValue
object as an argument to the process function. Inside the child process, you can read or modify the shared value directly using the .value
attribute.
For example, imagine a sensor updating the temperature reading over time. The child process will increase the temperature several times, simulating changing sensor data. Meanwhile, the parent process waits for the child to finish and then prints the updated temperature.
Here’s how this works in code:
from multiprocessing import Process
from multiprocessing.sharedctypes import RawValue
import ctypes
import time
def sensor_update(temp):
for _ in range(5):
temp.value += 1.5 # Simulate temperature rising
print(f"Sensor updated temperature to: {temp.value}°C")
time.sleep(0.5)
if __name__ == "__main__":
temperature = RawValue(ctypes.c_double, 20.0)
print(f"Starting temperature: {temperature.value}°C")
p = Process(target=sensor_update, args=(temperature,))
p.start()
p.join()
print(f"Final temperature: {temperature.value}°C")
In this example, the child process sensor_update
modifies the shared RawValue
by increasing it five times. The parent process waits for the child to complete, then prints the final temperature, showing the shared memory’s updated state.
Accessing and Modifying RawValue
You can read and write the data stored in a RawValue
using its .value
attribute. This makes it easy for multiple processes to access and change the same shared data.
For example, imagine two processes both updating a shared counter. Each process increases the counter several times independently. After both finish, the parent process reads the final value from the shared RawValue
.
Here’s how it looks in code:
from multiprocessing import Process
from multiprocessing.sharedctypes import RawValue
import ctypes
import time
def increment_counter(counter, amount, times):
for _ in range(times):
counter.value += amount
print(f"Counter updated to: {counter.value}")
time.sleep(0.3)
if __name__ == "__main__":
# Create a shared RawValue integer initialized to 0
counter = RawValue(ctypes.c_int, 0)
p1 = Process(target=increment_counter, args=(counter, 1, 5))
p2 = Process(target=increment_counter, args=(counter, 2, 5))
p1.start()
p2.start()
p1.join()
p2.join()
print(f"\nFinal counter value: {counter.value}")
In this example, two processes update the same shared counter by adding 1 and 2 repeatedly. Both modify counter.value
directly. When both finish, the parent prints the final value, showing how RawValue
holds the combined updates from both processes.
RawValue vs Value: Demonstrating No Locking
In Python’s multiprocessing.sharedctypes
, both Value
and RawValue
let you share simple data types between processes. The key difference is that Value
uses an internal lock to control access automatically, while RawValue
does not use any locking.
This means Value
ensures only one process changes the data at a time, preventing conflicts. On the other hand, RawValue
gives faster access but does not protect against simultaneous changes, so you have to be aware of that when using it.
Here’s a simple example comparing how you create and use each:
from multiprocessing import Process
from multiprocessing.sharedctypes import Value, RawValue
import ctypes
import time
def increment(shared_var, times):
for _ in range(times):
shared_var.value += 1
print(f"Updated value: {shared_var.value}")
time.sleep(0.1)
if __name__ == "__main__":
# Using Value (with locking)
locked_value = Value(ctypes.c_int, 0)
p1 = Process(target=increment, args=(locked_value, 5))
p2 = Process(target=increment, args=(locked_value, 5))
p1.start()
p2.start()
p1.join()
p2.join()
print(f"Final locked Value: {locked_value.value}")
# Using RawValue (no locking)
raw_value = RawValue(ctypes.c_int, 0)
p3 = Process(target=increment, args=(raw_value, 5))
p4 = Process(target=increment, args=(raw_value, 5))
p3.start()
p4.start()
p3.join()
p4.join()
print(f"Final RawValue: {raw_value.value}")
In this code, both Value
and RawValue
are updated by two processes in the same way. The Value
object has a built-in lock that prevents both processes from changing it at the same time. The RawValue
does not, so the updates happen without synchronization.
Note: This example does not show race conditions explicitly, but it highlights that RawValue
skips locking, meaning you handle synchronization yourself if needed.
Fun Example: Shared Score Tracker for a Multiplayer Game
Let’s create a simple multiplayer game score tracker using RawValue
. Imagine several player processes all adding points to a shared score. Since RawValue
does not use locks, these players update the score directly in shared memory.
Here’s how it works: each player process increments the shared score several times, simulating points earned during the game. The main process waits for all players to finish, then prints the final total score.
from multiprocessing import Process
from multiprocessing.sharedctypes import RawValue
import ctypes
import time
import random
def player(name, score, rounds):
for _ in range(rounds):
points = random.randint(1, 10)
score.value += points
print(f"{name} scored {points} points, total: {score.value}")
time.sleep(random.uniform(0.1, 0.3))
if __name__ == "__main__":
# Shared RawValue to hold the total score
total_score = RawValue(ctypes.c_int, 0)
players = []
for player_name in ['Harry', 'Hermione', 'Ron']:
p = Process(target=player, args=(player_name, total_score, 5))
players.append(p)
p.start()
for p in players:
p.join()
print(f"\nFinal total score: {total_score.value}")
In this example, each player randomly adds points to the shared score. The final printed score shows the combined effort. Since RawValue
doesn’t lock access, the score updates happen without coordination—perfect for a simple, fast shared counter!
Cleaning Up: Properly Ending Processes
After using RawValue
in multiple processes, it’s important to properly end those processes to keep things clean and avoid resource leaks. You do this by calling .join()
on each process. This makes the main program wait until the child processes finish their work.
Here’s a full example showing how to start several processes that update a shared RawValue
, then wait for them all to complete using .join()
:
from multiprocessing import Process
from multiprocessing.sharedctypes import RawValue
import ctypes
import time
import random
def worker(name, counter):
for _ in range(3):
counter.value += 1
print(f"{name} incremented counter to {counter.value}")
time.sleep(random.uniform(0.1, 0.3))
if __name__ == "__main__":
shared_counter = RawValue(ctypes.c_int, 0)
processes = []
for name in ['Amber', 'Stewie', 'Brian']:
p = Process(target=worker, args=(name, shared_counter))
processes.append(p)
p.start()
# Wait for all child processes to finish
for p in processes:
p.join()
print(f"\nFinal counter value: {shared_counter.value}")
This pattern ensures that the main program waits patiently for all child processes to finish their updates to the shared RawValue
before moving on or exiting. It’s a clean way to manage processes after sharing data.
Conclusion
RawValue
provides a simple way to share low-level data like numbers between processes without any locking mechanism. This makes it perfect for cases where you want straightforward shared memory access and don’t need synchronization. You’ve seen how to create, pass, and modify RawValue
instances across processes, and how to cleanly end those processes. Give RawValue
a try when your Python programs need easy, no-fuss sharing of basic data between parallel tasks.