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 un ordenador.
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