csharp-shared-memory

Compartiendo memoria en C# con Shared Memory

SharedMemory es una biblioteca de código abierto disponible en GitHub, creada por Justin Stenning, que ofrece clases de memoria compartida en C#.

La comunicación y el intercambio de datos entre procesos es una necesidad que a veces tenemos al desarrollar software. Existen distintas formas, más o menos eficientes y seguras, de compartir información entre procesos.

Uno de los mecanismos es la SharedMemory, un mecanismo que permite a múltiples procesos compartir segmentos de memoria en el espacio de direcciones de una computadora.

Estos procesos pueden acceder y modificar los datos almacenados en la memoria compartida de manera eficiente, lo que resulta útil para la comunicación y cooperación entre procesos independientes.

Sin embargo, es fundamental implementar una adecuada sincronización y control de acceso a la memoria compartida para evitar condiciones de carrera y garantizar la coherencia de los datos compartidos, lo que puede lograrse mediante el uso de semáforos, mutex u otros mecanismos de sincronización.

La biblioteca SharedMemory en C# ofrece clases de memoria compartida que facilitan el intercambio de datos eficiente y seguro entre procesos. Simplifica el proceso de compartir datos y se encarga de la sincronización y el acceso concurrente.

Estas clases permiten compartir datos entre procesos de manera eficiente y segura:

  • SharedBuffer: Una clase base abstracta que envuelve un archivo mapeado en memoria, exponiendo las operaciones de lectura/escritura e implementando un pequeño encabezado para permitir a los clientes abrir el búfer compartido sin conocer el tamaño de antemano.
  • BufferWithLocks: Una clase abstracta que extiende SharedBuffer para proporcionar soporte simple de bloqueo de lectura/escritura a través del uso de EventWaitHandles.
  • SharedArray: Una implementación simple de una matriz genérica que utiliza un búfer de memoria compartida. Hereda de BufferWithLocks para proporcionar soporte para sincronización de subprocesos.
  • BufferReadWrite: Proporciona acceso de lectura/escritura a un búfer de memoria compartida, con varias sobrecargas para admitir la lectura y escritura de estructuras, la copia hacia y desde IntPtr, y más. Hereda de SharedMemory.BufferWithLocks para proporcionar soporte para sincronización de subprocesos.
  • CircularBuffer: Implementación de un búfer circular FIFO sin bloqueos (también conocido como búfer de anillo). Con soporte para 2 o más nodos, esta implementación admite múltiples lectores y escritores. El enfoque sin bloqueos se implementa utilizando Interlocked.Exchange y EventWaitHandles.
  • RpcBuffer: Canal RPC bidireccional simple que utiliza CircularBuffer. Admite un par maestro/esclavo por canal. Solo disponible en .NET 4.5+ / .NET Standard 2.0.

Cómo usar SharedMemory

Podemos añadir la biblioteca a un proyecto de .NET fácilmente, a través del paquete Nuget correspondiente.

Install-Package SharedMemory

Ejemplo básico

Vamos a ver un ejemplo práctico y sencillo de cómo utilizar la clase SharedArray para compartir información enteros entre dos procesos en C#:

// Proceso 1: Escritor
using (var sharedArray = new SharedArray<int>(100))
{
    // Escribir datos en el arreglo compartido
    for (int i = 0; i < sharedArray.Length; i++)
    {
        sharedArray[i] = i;
    }
    
    // Esperar a que el proceso 2 complete la lectura
    while (sharedArray.ReadCount > 0)
    {
        Thread.Sleep(10);
    }
}

// Proceso 2: Lector
using (var sharedArray = new SharedArray<int>(100))
{
    // Leer datos del arreglo compartido
    for (int i = 0; i < sharedArray.Length; i++)
    {
        int value = sharedArray[i];
        Console.WriteLine(value);
    }
    
    // Marcar el arreglo como leído
    sharedArray.MarkRead();
}

En este ejemplo, el proceso 1 es el escritor y el proceso 2 es el lector. El proceso 1 escribe los números del 0 al 99 en el arreglo compartido, mientras que el proceso 2 lee y muestra los valores. La clase SharedArray se encarga automáticamente de la sincronización y el acceso concurrente a los datos compartidos.

Aquí tenéis algunos de cómo utilizar SharedMemory extraídos de la documentación de la librería

SharedArray

Console.WriteLine("SharedMemory.SharedArray:");
using (var producer = new SharedMemory.SharedArray<int>("MySharedArray", 10))
using (var consumer = new SharedMemory.SharedArray<int>("MySharedArray"))
{
    producer[0] = 123;
    producer[producer.Length - 1] = 456;
    
    Console.WriteLine(consumer[0]);
    Console.WriteLine(consumer[consumer.Length - 1]);
}

CircularBuffer

Console.WriteLine("SharedMemory.CircularBuffer:");
using (var producer = new SharedMemory.CircularBuffer(name: "MySharedMemory", nodeCount: 3, nodeBufferSize: 4))
using (var consumer = new SharedMemory.CircularBuffer(name: "MySharedMemory"))
{
    // nodeCount must be one larger than the number
    // of writes that must fit in the buffer at any one time
    producer.Write<int>(new int[] { 123 });
    producer.Write<int>(new int[] { 456 });
   
    int[] data = new int[1];
    consumer.Read<int>(data);
    Console.WriteLine(data[0]);
    consumer.Read<int>(data);
    Console.WriteLine(data[0]);
}

BufferReadWrite

Console.WriteLine("SharedMemory.BufferReadWrite:");
using (var producer = new SharedMemory.BufferReadWrite(name: "MySharedBuffer", bufferSize: 1024))
using (var consumer = new SharedMemory.BufferReadWrite(name: "MySharedBuffer"))
{
    int data = 123;
    producer.Write<int>(ref data);
    data = 456;
    producer.Write<int>(ref data, 1000);
    
    int readData;
    consumer.Read<int>(out readData);
    Console.WriteLine(readData);
    consumer.Read<int>(out readData, 1000);
    Console.WriteLine(readData);
}

RpcBuffer

Console.WriteLine("SharedMemory.RpcBuffer:");
// Ensure a unique channel name
var rpcName = "RpcTest" + Guid.NewGuid().ToString();
var rpcMaster = new RpcBuffer(rpcName);
var rpcSlave = new RpcBuffer(rpcName, (msgId, payload) =>
{
    // Add the two bytes together
    return BitConverter.GetBytes((payload[0] + payload[1]));
});

// Call the remote handler to add 123 and 10
var result = rpcMaster.RemoteRequest(new byte[] { 123, 10 });
Console.WriteLine(result); // outputs 133

SharedMemory es Open Source, y todo el código y documentación está disponible en el repositorio del proyecto en https://github.com/justinstenning/SharedMemory