Patrón canónico para la manipulación estatal de la biblioteca transaccional -- mparative-review campo con concurrency campo con go campo con locking camp codereview Relacionados El problema

canonical pattern for transactional library state manipulation


1
vote

problema

Español

En Golang, encuentro que existe una presión definitiva en el idioma en sí y las bibliotecas incorporadas para hacer las cosas de una manera canónica. Puede que alguien pueda ver críticamente estas dos formas en que estoy considerando usar para proporcionar sincronización transaccional de la manipulación estatal dentro de una biblioteca y proporcionarme algunos comentarios.

Todas las funciones de manipulación estatal de la biblioteca (es decir, todas las funciones de la ayuda de una pareja) usarían este patrón y habría más de una docena de funciones no triviales que requieren una operación transaccional. El método 1 es lo que generalmente habría hecho en C #, pero el blog oficial de Golang casi parece estar implicando que esto es un anti-patrón.

Método 1 - C # bloqueo de bloqueo https://play.golang.org/p/xs14fm_xug

gobyexample.com/mutexes

  package main  import (     "fmt"     "sync" )  type ILibraryState interface {     DoSomething(arg int) error }  type LibraryState struct {     lock *sync.Mutex     // Some private state }  func (l *LibraryState) DoSomething(arg int) error {     // Do some arg check      // Transactional synchronisation     l.lock.Lock()     defer l.lock.Unlock()      // Do something     fmt.Println("Hello, playground")     return nil; }  func NewLibrary() ILibraryState {     lib := LibraryState{}     lib.lock = &sync.Mutex{}     return &lib }  func main() {     l := NewLibrary()     l.DoSomething(5) }   

Método 2 - Hidden Goroutine y una sola cola de comandos https://play.golang.org/p/ 3VWQJDSWJM

  • http://gobyexample.com/stateful-goroutines
  • http://blog.golang.org/share-memory-by-communicating
  • http://blog.golang.org/advanzed-go-concurrency-patterns
  package main  import (     "fmt" )  type ILibraryState interface {     DoSomething(arg int) error }  type LibraryState struct {     command  chan (interface{})     response chan (interface{})     // Some private state }  type commandDoSomething struct {     arg int }  func (l *LibraryState) DoSomething(arg int) error {     // Do some arg check      // Perform operation transactionally     l.command <- commandDoSomething{arg}     res := <-l.response     if res == nil {         return nil     }     return res.(error) }  func (l *LibraryState) reallyDoSomething(arg int) {     // Do something     fmt.Println("Hello, playground")     var err error = nil     l.response <- err }  func (l *LibraryState) processCommands() {     for {         switch c := (<-l.command).(type) {          case commandDoSomething:             l.reallyDoSomething(c.arg)         }     } }  func NewLibrary() ILibraryState {     lib := LibraryState{}     lib.command = make(chan (interface{}))     lib.response = make(chan (interface{}))     go lib.processCommands()     return &lib }  func main() {     l := NewLibrary()     l.DoSomething(5) }   
Original en ingles

In golang I am finding that there is a definite pressure in the language itself and built-in libraries to do things in a canonical way. Please can someone critically look at these two ways I am considering using to provide transactional synchronisation of state manipulation within a library and provide me with some feedback.

All state manipulation functions of the library (i.e. all but a couple helper functions) would use this pattern and there would be over a dozen non-trivial functions that require transactional operation. Method 1 is what I would usually have done in C# but the official golang blog almost seems to be implying this is an anti-pattern.

Method 1 - C# lock pattern https://play.golang.org/p/XS14fM_XUg

gobyexample.com/mutexes

package main  import (     "fmt"     "sync" )  type ILibraryState interface {     DoSomething(arg int) error }  type LibraryState struct {     lock *sync.Mutex     // Some private state }  func (l *LibraryState) DoSomething(arg int) error {     // Do some arg check      // Transactional synchronisation     l.lock.Lock()     defer l.lock.Unlock()      // Do something     fmt.Println("Hello, playground")     return nil; }  func NewLibrary() ILibraryState {     lib := LibraryState{}     lib.lock = &sync.Mutex{}     return &lib }  func main() {     l := NewLibrary()     l.DoSomething(5) } 

Method 2 - Hidden goroutine and single command queue https://play.golang.org/p/3vWQJDswJm

  • http://gobyexample.com/stateful-goroutines
  • http://blog.golang.org/share-memory-by-communicating
  • http://blog.golang.org/advanced-go-concurrency-patterns
package main  import (     "fmt" )  type ILibraryState interface {     DoSomething(arg int) error }  type LibraryState struct {     command  chan (interface{})     response chan (interface{})     // Some private state }  type commandDoSomething struct {     arg int }  func (l *LibraryState) DoSomething(arg int) error {     // Do some arg check      // Perform operation transactionally     l.command <- commandDoSomething{arg}     res := <-l.response     if res == nil {         return nil     }     return res.(error) }  func (l *LibraryState) reallyDoSomething(arg int) {     // Do something     fmt.Println("Hello, playground")     var err error = nil     l.response <- err }  func (l *LibraryState) processCommands() {     for {         switch c := (<-l.command).(type) {          case commandDoSomething:             l.reallyDoSomething(c.arg)         }     } }  func NewLibrary() ILibraryState {     lib := LibraryState{}     lib.command = make(chan (interface{}))     lib.response = make(chan (interface{}))     go lib.processCommands()     return &lib }  func main() {     l := NewLibrary()     l.DoSomething(5) } 
           

Lista de respuestas

3
 
vote
vote
La mejor respuesta
 

Go tiene una opinión sólida sobre el uso de Mutexes vs. Canales. De hecho, el paquete sync estados: "que no sean los tipos de una vez y de esperanza, la mayoría está diseñada para su uso por rutinas de biblioteca de bajo nivel. La sincronización de nivel superior se realiza mejor a través de canales y comunicación. . " (consulte https://golang.org/pkg/sync/ )

Esta opinión claramente implica que su primera implementación es subóptima. Estoy de acuerdo, así que no voy a revisar esa implementación.

La segunda implementación es más intrigante, utiliza canales, pero tiene problemas ... y no es un ejemplo "canónico" de lo que esperaría con una implementación de "comunicación" para la concurrencia en Go.

La rutina de "estado" es una que solo accede a la rutina individual, o modifica el estado. Toda la interacción con el estado se realiza comunicándose con esa rutina, y la comunicación ocurre a través de los canales.

Tiene un canal para comunicar la solicitud de acceder al estado, y otro para devolver la respuesta. Este es un problema ... El canal de solicitud command1 está bien, pero el canal 99887776555443332 está roto. Si tiene varios comandos concurrentes emitidos, no podrá enviar la respuesta correcta al comando correcto, y las cosas se mezclarán.

La solución a esto es dedicar un canal para cada comando para obtener las respuestas. Agregue el canal al commandDoSomething Struct, para que su código se vea algo así:

  func (l *LibraryState) DoSomething(arg int) error {      // dedicated response channel for communication (of error type, not interface{})     resp := make(chan error, 1)      // Perform operation transactionally     l.command <- commandDoSomething{arg, resp}     res := <-response     return res }   

Ahora se vería su manejador de estado:

  func (l *LibraryState) reallyDoSomething(arg int, respondto chan<- error) {      defer close(respondto)      // Do something     fmt.Println("Hello, playground")     var err error = nil     respondto <- err }   

y ser llamado como:

  for {     switch c := (<-l.command).(type) {      case commandDoSomething:         l.reallyDoSomething(c.arg, c.respondto)     } }   

Tenga en cuenta que puede eliminar completamente el canal 99887766654433765544337 .

Editar : Tenga en cuenta que al dedicar un canal de respuesta para cada comando, puede hacer cosas como devolver el canal real desde la función DoSomething , como:

  func (l *LibraryState) DoSomething(arg int) <-chan error {      // dedicated response channel for communication (of error type, not interface{})     resp := make(chan error, 1)      // Perform operation transactionally     l.command <- commandDoSomething{arg, resp}     return resp }   

y como consecuencia, los "clientes" pueden llamar al método 998877666555443310 y luego potencialmente hacer otro trabajo mientras el estado está cambiando, y obtenga la posible condición de error más tarde ... como:

  command1  
 

Go does have a strong opinion on the use of mutexes vs. channels. In fact, the sync package states: "Other than the Once and WaitGroup types, most are intended for use by low-level library routines. Higher-level synchronization is better done via channels and communication." (See https://golang.org/pkg/sync/ )

This opinion clearly implies your first implementation is suboptimal. I agree, so I'm not going to even review that implementation.

The second implementation is more intriguing, it uses channels, but it's got issues.... and it's not a "canonical" example of what I would expect with a "communication" implementation for concurrency in Go.

The "Stateful" go-routine is one which only that single go-routine accesses, or modifies the state. All interaction with the state is done by communicating with that routine, and communication happens through channels.

You have one channel to communicate the request for accessing the state, and another for returning the response. This is a problem... The request channel command is OK, but the response channel is broken. If you have multiple concurrent commands issued, you won't be able to send the right response to the right command, and things will get mixed up.

The solution to this is to dedicate a channel for each command to get the responses on. Add the channel to the commandDoSomething struct, so that your code looks something like:

func (l *LibraryState) DoSomething(arg int) error {      // dedicated response channel for communication (of error type, not interface{})     resp := make(chan error, 1)      // Perform operation transactionally     l.command <- commandDoSomething{arg, resp}     res := <-response     return res } 

Now your state handler would look like:

func (l *LibraryState) reallyDoSomething(arg int, respondto chan<- error) {      defer close(respondto)      // Do something     fmt.Println("Hello, playground")     var err error = nil     respondto <- err } 

and be called like:

for {     switch c := (<-l.command).(type) {      case commandDoSomething:         l.reallyDoSomething(c.arg, c.respondto)     } } 

Note that you can remove the l.response channel completely.

Edit: Note that by dedicating a response channel for each command, you can do things like return the actual channel from the DoSomething function, like:

func (l *LibraryState) DoSomething(arg int) <-chan error {      // dedicated response channel for communication (of error type, not interface{})     resp := make(chan error, 1)      // Perform operation transactionally     l.command <- commandDoSomething{arg, resp}     return resp } 

and as a consequence, "clients" can call the DoSomething method and then potentially do other work while the state is changing, and get the possible error condition later... like:

resp := DoSomething(input) //... work on other activities if err := <-resp; err != nil {     // handle error - perhaps undo some previous optimistic code.... } 
 
 
         
         

Relacionados problema

3  Simulando Memcachache Obtén y establece la condición de carrera y resolviéndola con Agregar  ( Simulating memcache get and set race condition and solving it with add ) 
Memcache get y set puede llegar a una condición de carrera si los usa juntos para lograr algo como el bloqueo porque no es atómico . Un simple memcache ...

2  STD :: Lock Implementación en C con PTHEADS  ( Stdlock implementation in c with pthreads ) 
Me estropeé un poco con PTHEADS y necesitaba una alternativa a la función C ++ 11 std::lock (2 args son suficientes), y esto es lo que vino: void lock(pt...

2  Función para bloquear un archivo usando Memcache, versión 2  ( Function to lock a file using memcache version 2 ) 
Basado en comentarios sugeridos para Función para bloquear un archivo usando Memcache < / a>, he modificado el código a import os import shutil import mem...

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

48  Safe-Safe y Lock-Free - Implementación de la cola  ( Thread safe and lock free queue implementation ) 
Estaba tratando de crear una implementación de colas de bloqueo en Java, principalmente para el aprendizaje personal. La cola debe ser general, lo que permite...

8  C ++ Sección crítica con tiempo de espera  ( C critical section with timeout ) 
Nota: Mi implementación se basa en CodeProject Artículo de Vladislav Gelfer. Basado en valdok 's CÓDIGOS DE VALDOK , reescribí la clase de sección crí...

6  Usando el temporizador con el trabajador de fondo para asegurarse de que el método Dowork se llame  ( Using timer with backgroundworker to ensure the dowork method is called ) 
Tengo una aplicación de formularios de Windows en la que un trabajador de fondo se llama una y otra vez. Necesito evitar el acceso concurrente del código en D...

7  Esperando la conexión del servidor de juegos  ( Waiting for game server connection ) 
public boolean connectedOnGameServer = false; public final Object conGameServerMonitor = new Object(); public void connectedToGameServer() { synchronize...

2  Método de actualización de bloqueo de reentrantreadwritelock  ( Reentrantreadwritelock lock upgrade method ) 
Tengo una pregunta sobre la actualización de bloqueo. Específicamente lo que me molesta está entre readlock.unlock () y siguiendo a writelock.lock () ... Esto...

1  Función para bloquear un archivo usando Memcache, versión 1  ( Function to lock a file using memcache version 1 ) 
Tengo una aplicación web donde enumera los archivos en la UI a muchos usuarios al mismo tiempo. Hay muchas personas que monitorean los archivos y los procesan...




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