Python RawValue

Python IPC: When to Use RawValue for Low-Level Shared Data

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.

Scroll to Top