Patrón de grupo de objetos para operaciones de zócalo asíncrono -- # campo con stack campo con queue camp codereview Relacionados El problema

Object pool pattern for asynchronous socket operations


5
vote

problema

Español

He escrito mi propia clase de grupo de objetos llamado ObjectPool<T> que usa SpinLock y Stack<T>2 :

  public class ObjectPool<T> where T : class {     private int _size;     private Func<T> _factory;     private SpinLock _spinLock;      // storage for the pool objects.     // the first item is expected to be most often case.     private T _firstItem;     private Stack<T> _cache; //Use Queue<T> for first-in-first-out (FIFO)      public ObjectPool(Func<T> factory) : this(factory, Environment.ProcessorCount * 2) { }      public ObjectPool(Func<T> factory, int size)     {         _factory = factory;         _size = size;         _cache = new Stack<T>();         _spinLock = new SpinLock(false);     }      /// <summary>     /// Produces an instance.     /// </summary>     public T Rent()     {         bool lockTaken = false;         _spinLock.Enter(ref lockTaken);          try         {             T instance = _firstItem;             if (instance == null || instance != Interlocked.CompareExchange(ref _firstItem, null, instance))             {                 instance = RentFromCache();             }              return instance;         }         finally         {             if (lockTaken)             {                 _spinLock.Exit(false);             }         }     }      private T RentFromCache()     {         var cache = _cache;          if (cache.Count > 0)         {             T instance = _cache.Pop();              if (instance != null)             {                 return instance;             }         }          return CreateInstance();     }      private T CreateInstance()     {         var instance = _factory();         return instance;     }      /// <summary>     /// Returns objects to the pool.     /// </summary>     public void Return(T obj)     {         bool lockTaken = false;         _spinLock.Enter(ref lockTaken);          try         {             if (_firstItem == null)             {                 // worst case scenario: two objects may be stored into same slot.                 _firstItem = obj;             }             else             {                 ReturnToCache(obj);             }         }         finally         {             if (lockTaken)             {                 _spinLock.Exit(false);             }         }     }      private void ReturnToCache(T obj)     {         var cache = _cache;          if (cache.Count >= _size)         {             // not a big fan of doing it this way             return;         }          // worst case scenario: two objects may be stored into same slot.         cache.Push(obj);     } }   

Actualmente estoy usando este grupo de objetos para obtener un alto rendimiento de un Socket haciendo un grupo de operaciones de socket asíncrono ( 9988777665544335 ).

Nota: Estoy recibiendo paquetes UDP, que podrían ayudar a ilustrar por qué existen algunas peculiaridades (que no funcionarán bien para algunos casos genéricos) en el diseño de este grupo de grupos, como usar una piscina LIFO. < / em>

Está funcionando bien hasta ahora. Sin embargo, apreciaría alguna revisión de código de expertos. Espero consejos sobre dónde se puede mejorar, optimizado adicionalmente, o si me he perdido algo importante.


Preguntas adicionales:

  • Me pregunto si ConcurrentStack<T> me proporcionará cualquier beneficio adicional?
Original en ingles

I have written my own object pool class called ObjectPool<T> which uses SpinLock and Stack<T>:

public class ObjectPool<T> where T : class {     private int _size;     private Func<T> _factory;     private SpinLock _spinLock;      // storage for the pool objects.     // the first item is expected to be most often case.     private T _firstItem;     private Stack<T> _cache; //Use Queue<T> for first-in-first-out (FIFO)      public ObjectPool(Func<T> factory) : this(factory, Environment.ProcessorCount * 2) { }      public ObjectPool(Func<T> factory, int size)     {         _factory = factory;         _size = size;         _cache = new Stack<T>();         _spinLock = new SpinLock(false);     }      /// <summary>     /// Produces an instance.     /// </summary>     public T Rent()     {         bool lockTaken = false;         _spinLock.Enter(ref lockTaken);          try         {             T instance = _firstItem;             if (instance == null || instance != Interlocked.CompareExchange(ref _firstItem, null, instance))             {                 instance = RentFromCache();             }              return instance;         }         finally         {             if (lockTaken)             {                 _spinLock.Exit(false);             }         }     }      private T RentFromCache()     {         var cache = _cache;          if (cache.Count > 0)         {             T instance = _cache.Pop();              if (instance != null)             {                 return instance;             }         }          return CreateInstance();     }      private T CreateInstance()     {         var instance = _factory();         return instance;     }      /// <summary>     /// Returns objects to the pool.     /// </summary>     public void Return(T obj)     {         bool lockTaken = false;         _spinLock.Enter(ref lockTaken);          try         {             if (_firstItem == null)             {                 // worst case scenario: two objects may be stored into same slot.                 _firstItem = obj;             }             else             {                 ReturnToCache(obj);             }         }         finally         {             if (lockTaken)             {                 _spinLock.Exit(false);             }         }     }      private void ReturnToCache(T obj)     {         var cache = _cache;          if (cache.Count >= _size)         {             // not a big fan of doing it this way             return;         }          // worst case scenario: two objects may be stored into same slot.         cache.Push(obj);     } } 

I'm currently using this object pool to get high performance out of a Socket by making a pool of asynchronous socket operations (ObjectPool<SocketAsyncEventArgs>).

Note: I'm receiving UDP packets, which might help illustrate why some quirks exist (that won't work well for some generic-cases) in this object pool's design, such as using a LIFO pool.

It's working great so far. However, I'd appreciate some expert code review. I am hoping for advice on where it can be improved, further optimized, or if I have missed something important.


Additional Questions:

  • I'm wondering if ConcurrentStack<T> would provide me with any added benefits?
        
     
     

Lista de respuestas

4
 
vote
vote
La mejor respuesta
 

porque no cambias

  $groups = $pdo->query("SELECT id, name FROM control_group")->fetchAll(PDO::FETCH_KEY_PAIR); $roles  = $pdo->query("SELECT id, roleName FROM user_role")->fetchAll(PDO::FETCH_KEY_PAIR);  $sql = "SELECT u.id, u.firstName, u.lastName, u.email, u.phoneNumber,      u.address, u.birthDate,group_concat(ur.roleName), roles group_concat(cg.name) groups      FROM users as u      LEFT OUTER JOIN user_role as ur ON u.id = ur.userId      LEFT OUTER JOIN user_group as ug on ug.userId = u.id      LEFT OUTER JOIN control_group as cg on cg.id = ug.groupId      GROUP BY u.id"; $res = $pdo->query($sql); $users = array(); while ($row = $res->fetch(PDO::FETCH_ASSOC)) {     if ($row['id'] == $_SESSION["id"]) {         continue;     }      $user_groups = array();     foreach(explode(",", $row['groups'] as $id) {         $user_groups[$id] = $groups[$id]     }     $row['groups'] = $user_groups;      $user_roles = array();     foreach(explode(",", $row['roles']) as $id) {         $user_roles[$id] = $roles[$id]     }      $row['roles'] = $user_roles;     $users[] = $row; } 2  

$groups = $pdo->query("SELECT id, name FROM control_group")->fetchAll(PDO::FETCH_KEY_PAIR); $roles = $pdo->query("SELECT id, roleName FROM user_role")->fetchAll(PDO::FETCH_KEY_PAIR); $sql = "SELECT u.id, u.firstName, u.lastName, u.email, u.phoneNumber, u.address, u.birthDate,group_concat(ur.roleName), roles group_concat(cg.name) groups FROM users as u LEFT OUTER JOIN user_role as ur ON u.id = ur.userId LEFT OUTER JOIN user_group as ug on ug.userId = u.id LEFT OUTER JOIN control_group as cg on cg.id = ug.groupId GROUP BY u.id"; $res = $pdo->query($sql); $users = array(); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { if ($row['id'] == $_SESSION["id"]) { continue; } $user_groups = array(); foreach(explode(",", $row['groups'] as $id) { $user_groups[$id] = $groups[$id] } $row['groups'] = $user_groups; $user_roles = array(); foreach(explode(",", $row['roles']) as $id) { $user_roles[$id] = $roles[$id] } $row['roles'] = $user_roles; $users[] = $row; } 3

Debe hacerlos $groups = $pdo->query("SELECT id, name FROM control_group")->fetchAll(PDO::FETCH_KEY_PAIR); $roles = $pdo->query("SELECT id, roleName FROM user_role")->fetchAll(PDO::FETCH_KEY_PAIR); $sql = "SELECT u.id, u.firstName, u.lastName, u.email, u.phoneNumber, u.address, u.birthDate,group_concat(ur.roleName), roles group_concat(cg.name) groups FROM users as u LEFT OUTER JOIN user_role as ur ON u.id = ur.userId LEFT OUTER JOIN user_group as ug on ug.userId = u.id LEFT OUTER JOIN control_group as cg on cg.id = ug.groupId GROUP BY u.id"; $res = $pdo->query($sql); $users = array(); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { if ($row['id'] == $_SESSION["id"]) { continue; } $user_groups = array(); foreach(explode(",", $row['groups'] as $id) { $user_groups[$id] = $groups[$id] } $row['groups'] = $user_groups; $user_roles = array(); foreach(explode(",", $row['roles']) as $id) { $user_roles[$id] = $roles[$id] } $row['roles'] = $user_roles; $users[] = $row; } 4 .

Corrección después del comentario de T3CHB0T:

No hagas el $groups = $pdo->query("SELECT id, name FROM control_group")->fetchAll(PDO::FETCH_KEY_PAIR); $roles = $pdo->query("SELECT id, roleName FROM user_role")->fetchAll(PDO::FETCH_KEY_PAIR); $sql = "SELECT u.id, u.firstName, u.lastName, u.email, u.phoneNumber, u.address, u.birthDate,group_concat(ur.roleName), roles group_concat(cg.name) groups FROM users as u LEFT OUTER JOIN user_role as ur ON u.id = ur.userId LEFT OUTER JOIN user_group as ug on ug.userId = u.id LEFT OUTER JOIN control_group as cg on cg.id = ug.groupId GROUP BY u.id"; $res = $pdo->query($sql); $users = array(); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { if ($row['id'] == $_SESSION["id"]) { continue; } $user_groups = array(); foreach(explode(",", $row['groups'] as $id) { $user_groups[$id] = $groups[$id] } $row['groups'] = $user_groups; $user_roles = array(); foreach(explode(",", $row['roles']) as $id) { $user_roles[$id] = $roles[$id] } $row['roles'] = $user_roles; $users[] = $row; } 5 readonly. Más aclaraciones: https://stackoverflow.com/a/9235028/2655508

El problema subyacente es que el compilador C # crea una copia de un campo de tipo de valor readonly cuando llame a un método no estático en él y ejecuta ese método en la copia, porque el método podría tener efectos secundarios que cambian el valor de La estructura, que no está permitida para los campos readonly.


  $groups = $pdo->query("SELECT id, name FROM control_group")->fetchAll(PDO::FETCH_KEY_PAIR); $roles  = $pdo->query("SELECT id, roleName FROM user_role")->fetchAll(PDO::FETCH_KEY_PAIR);  $sql = "SELECT u.id, u.firstName, u.lastName, u.email, u.phoneNumber,      u.address, u.birthDate,group_concat(ur.roleName), roles group_concat(cg.name) groups      FROM users as u      LEFT OUTER JOIN user_role as ur ON u.id = ur.userId      LEFT OUTER JOIN user_group as ug on ug.userId = u.id      LEFT OUTER JOIN control_group as cg on cg.id = ug.groupId      GROUP BY u.id"; $res = $pdo->query($sql); $users = array(); while ($row = $res->fetch(PDO::FETCH_ASSOC)) {     if ($row['id'] == $_SESSION["id"]) {         continue;     }      $user_groups = array();     foreach(explode(",", $row['groups'] as $id) {         $user_groups[$id] = $groups[$id]     }     $row['groups'] = $user_groups;      $user_roles = array();     foreach(explode(",", $row['roles']) as $id) {         $user_roles[$id] = $roles[$id]     }      $row['roles'] = $user_roles;     $users[] = $row; } 6  

Aquí está usando $groups = $pdo->query("SELECT id, name FROM control_group")->fetchAll(PDO::FETCH_KEY_PAIR); $roles = $pdo->query("SELECT id, roleName FROM user_role")->fetchAll(PDO::FETCH_KEY_PAIR); $sql = "SELECT u.id, u.firstName, u.lastName, u.email, u.phoneNumber, u.address, u.birthDate,group_concat(ur.roleName), roles group_concat(cg.name) groups FROM users as u LEFT OUTER JOIN user_role as ur ON u.id = ur.userId LEFT OUTER JOIN user_group as ug on ug.userId = u.id LEFT OUTER JOIN control_group as cg on cg.id = ug.groupId GROUP BY u.id"; $res = $pdo->query($sql); $users = array(); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { if ($row['id'] == $_SESSION["id"]) { continue; } $user_groups = array(); foreach(explode(",", $row['groups'] as $id) { $user_groups[$id] = $groups[$id] } $row['groups'] = $user_groups; $user_roles = array(); foreach(explode(",", $row['roles']) as $id) { $user_roles[$id] = $roles[$id] } $row['roles'] = $user_roles; $users[] = $row; } 7 Solo para verificar su propiedad $groups = $pdo->query("SELECT id, name FROM control_group")->fetchAll(PDO::FETCH_KEY_PAIR); $roles = $pdo->query("SELECT id, roleName FROM user_role")->fetchAll(PDO::FETCH_KEY_PAIR); $sql = "SELECT u.id, u.firstName, u.lastName, u.email, u.phoneNumber, u.address, u.birthDate,group_concat(ur.roleName), roles group_concat(cg.name) groups FROM users as u LEFT OUTER JOIN user_role as ur ON u.id = ur.userId LEFT OUTER JOIN user_group as ug on ug.userId = u.id LEFT OUTER JOIN control_group as cg on cg.id = ug.groupId GROUP BY u.id"; $res = $pdo->query($sql); $users = array(); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { if ($row['id'] == $_SESSION["id"]) { continue; } $user_groups = array(); foreach(explode(",", $row['groups'] as $id) { $user_groups[$id] = $groups[$id] } $row['groups'] = $user_groups; $user_roles = array(); foreach(explode(",", $row['roles']) as $id) { $user_roles[$id] = $roles[$id] } $row['roles'] = $user_roles; $users[] = $row; } 8 . No veo una razón para no usar el nivel de clase $groups = $pdo->query("SELECT id, name FROM control_group")->fetchAll(PDO::FETCH_KEY_PAIR); $roles = $pdo->query("SELECT id, roleName FROM user_role")->fetchAll(PDO::FETCH_KEY_PAIR); $sql = "SELECT u.id, u.firstName, u.lastName, u.email, u.phoneNumber, u.address, u.birthDate,group_concat(ur.roleName), roles group_concat(cg.name) groups FROM users as u LEFT OUTER JOIN user_role as ur ON u.id = ur.userId LEFT OUTER JOIN user_group as ug on ug.userId = u.id LEFT OUTER JOIN control_group as cg on cg.id = ug.groupId GROUP BY u.id"; $res = $pdo->query($sql); $users = array(); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { if ($row['id'] == $_SESSION["id"]) { continue; } $user_groups = array(); foreach(explode(",", $row['groups'] as $id) { $user_groups[$id] = $groups[$id] } $row['groups'] = $user_groups; $user_roles = array(); foreach(explode(",", $row['roles']) as $id) { $user_roles[$id] = $roles[$id] } $row['roles'] = $user_roles; $users[] = $row; } 9 directamente. El uso de la variable de nivel de clase directamente no confundirá al lector del código. Lo mismo se aplica a CancellationTokenSource0 también.


 

Because you don't change

private int _size; private Func<T> _factory; private Stack<T> _cache; 

private SpinLock _spinLock; //Use Queue<T> for first-in-first-out (FIFO)

you should make them readonly.

Correction after the comment of t3chb0t:

Don't make the SpinLock readonly. Further clarification: https://stackoverflow.com/a/9235028/2655508

The underlying problem is that the C# compiler creates a copy of a readonly value type field when you call a non-static method on it and executes that method on the copy - because the method could have side effects that change the value of the struct - which is not allowed for readonly fields.


private T RentFromCache() {     var cache = _cache;      if (cache.Count > 0)     {         T instance = _cache.Pop();          if (instance != null)         {             return instance;         }     }      return CreateInstance(); }   

Here you are using var cache only for checking its Count property. I don't see a reason to not use the class level _cache directly. Using the class level variable directly won't confuse the reader of the code. The same applies to ReturnToCache() as well.


 
 
 
 
2
 
vote

Me gustaría agregar mis 5 centavos:

  1. primero, creo que deberías considerar cuidadosamente si realmente necesita esas optimizaciones. La depuración y el mantenimiento de un código multi-roscado es mucho trabajo, y no tiene que hacerlo más duro a menos que haya una buena razón para ello. Si necesita una piscina, ¿tiene que usar SpinLock y Interlocked.CompareExchange ? Es simple lock ¿Demasiado lento? Es ConcurrentStack ¿Demasiado lento? Debe comenzar intentando la opción más sencilla primero. Solo cuando tenga una prueba objetiva de que las opciones simples no cumplan con sus requisitos de desempeño, debe considerar soluciones más complejas. Parece que lo estás haciendo al revés.
  2. Problema de diseño potencial: para liberar un objeto agrupado, tengo que tener una referencia a la piscina que vino. En algunos casos, esta referencia podría no ser tan fácil de obtener.
  3. Problema de diseño potencial: su grupo no cuenta las referencias. Si ClassA y ClassB siempre obtén una referencia al mismo objeto agrupado, será difícil para ClassA para liberarlo sin arriesgarlo todavía podría estar en uso en ClassB .
 

I'd like to add my 5 cents:

  1. First, I think you should carefully consider whether you actually need those optimizations. Debugging and maintaining multi-threaded code is a lot of work, and you don't have to make it harder unless there is a good reason for it. If you need a pool, does it have to use SpinLock and Interlocked.CompareExchange? Is simple lock too slow? Is ConcurrentStack too slow? You should start by trying the simplest option first. Only when you have objective proof that simple options do not meet your performance requirements you should consider more complex solutions. It looks like you are doing it the other way around.
  2. Potential design issue: in order to release a pooled object I have to have a reference to the pool it came from. In some cases this reference might not be that easy to get.
  3. Potential design issue: your pool does not count references. If ClassA and ClassB ever get a reference to the same pooled object, it is going to be hard for ClassA to release it without risking that it might still be in use in ClassB.
 
 
     
     

Relacionados problema

4  Linkedlist para ser utilizado en una pila o cola  ( Linkedlist to be used in a stack or queue ) 
Todo esto funciona. Solo quería asegurarse de que no me perdí nada. Viniendo de C ++ y trabajando en mi camino a través de algoritmos, 4ta ed. Aquí está mi cl...

5  Cola de bloqueo con la exactitud de la lista doblemente vinculada  ( Lock free queue with doubly linked list correctness ) 
Necesito una cola de bloqueo que se implementa mediante la lista doblemente vinculada. es mi código correcto? ¿Hay algún error? ¿Hay alguna mejoras a realiz...

2  Montón Binomial en Java  ( Binomial heap in java ) 
Tengo esta implementación de un montón de binomio que proporciona inserto, disminución de la tecla y el extracto en el tiempo logarítmico. minpriorityqueue...

2  Cola de bloqueo delimitada  ( Bounded blocking queue ) 
¿Puede alguien por favor revise este código para mí? No he implementado todos los métodos para la simplicidad. NSUSerDefaults1 Preguntas abiertas: l...

8  Reader de cola sin bloqueo Seclador de Singler-Writer en C ++  ( Lockless queue multiple reader singler writer in c ) 
Escribí una cola sin encimera para enviar objetos pequeños desde un solo hilo a un hilo de trabajador aleatorio. suposiciones: x86 / x86_64 compilado con...

3  Cola MultiPhread Generic Simple  ( Simple generic multithreaded queue ) 
Esta es una cola de hilo simple que se usa en un modelo de consumidor productor. public class ThreadedQueue<TType> { private Queue<TType> _queue; p...

1  DEQUEUE () en la implementación de la cola que utiliza una lista circular vinculada  ( Dequeue in queue implememtation that uses a circular linked list ) 
Utilizo una lista de enlaces circulares para implementar un queue , y solo sostiene un 99887776665544332 (Tenga en cuenta que 99887776655443333 enlaces a...

2  Implementación de la lista de la cola prioritaria  ( Priority queue linked list implementation ) 
Soy nuevo en Python y quería asegurarme de que mi código responde a la pregunta de mi tarea debido. Mi tarea: Una cola de prioridad es una cola en la que...

3  Cola de JavaScript para las funciones de ASYNC  ( Javascript queue for async functions ) 
Basado en la respuesta a Mi pregunta anterior sobre el desbordamiento de pila , armé la siguiente clase de cola. Me doy cuenta de que ya hay bibliotecas para...

19  Una clase de hilo-piscina / cola personalizada  ( A custom thread pool queue class ) 
Quería una clase que ejecuta cualquier cantidad de tareas, pero solo cierta cantidad al mismo tiempo (por ejemplo, para descargar varios contenidos de Interne...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos