respuesta asincrónica en el patrón de registro

Estoy aprendiendo a ir y me gustaría explorar algunos patrones.

Me gustaría crear un componente de registro que mantenga un mapa de algunas cosas, y quiero proporcionar un acceso serializado a él:

Actualmente terminé con algo como esto:

type JobRegistry struct {
  submission chan JobRegistrySubmitRequest
  listing chan JobRegistryListRequest
}

type JobRegistrySubmitRequest struct {
  request JobSubmissionRequest
  response chan Job
}

type JobRegistryListRequest struct {
  response chan []Job
}

func NewJobRegistry() (this *JobRegistry) {
  this = &JobRegistry{make(chan JobRegistrySubmitRequest, 10), make(chan JobRegistryListRequest, 10)}

  go func() {
    jobMap := make(map[string] Job)

    for {
        select {
        case sub := <- this.submission:
            job := MakeJob(sub.request) // ....

            jobMap[job.Id] = job
            sub.response <- job.Id

        case list := <- this.listing:

            res := make([]Job, 0, 100)
            for _, v := range jobMap {
                res = append(res, v)
            }
            list.response <- res

        }

        /// case somechannel....
     }
   }()

   return
}

Básicamente, encapsulo cada operación dentro de una estructura, que lleva los parámetros y un canal de respuesta.

Luego creé métodos de ayuda para usuarios finales:

func (this *JobRegistry) List() ([]Job, os.Error) {
    res := make(chan []Job, 1)
    req := JobRegistryListRequest{res}
    this.listing <- req
    return <-res, nil // todo: handle errors like timeouts
}

Decidí usar un canal para cada tipo de solicitud con el fin de estar seguro de tipos.


El problema que veo con este enfoque es:

  • Mucho código repetitivo y muchos lugares para modificar cuando cambia algún tipo de parámetro / retorno

  • Tengo que hacer cosas raras como crear otra estructura contenedora para devolver errores desde dentro del controlador goroutine. (Si entendí correctamente, no hay tuplas y no hay forma de enviar múltiples valores en un canal, como devoluciones de valores múltiples)

Entonces, me pregunto si todo esto tiene sentido, o más bien, simplemente volvamos a las buenas cerraduras.

Estoy seguro de que alguien encontrará una salida inteligente utilizando los canales.

preguntado el 16 de mayo de 11 a las 18:05

¿Por qué consideras que un canal de error es extraño? AFAIK, esta es una forma bastante estándar de pasar errores a través de los canales. Y puedes envolverlo con una función agradable. -

1 Respuestas

No estoy del todo seguro de entenderte, pero intentaré responderte de todos modos.

Quieres un servicio genérico que ejecute los trabajos que se le envíen. También es posible que desee que los trabajos sean serializables.

Lo que necesitamos es una interfaz que defina un trabajo genérico.

type Job interface {
    Run()
    Serialize(io.Writer)
}

func ReadJob(r io.Reader) {...}

type JobManager struct {
    jobs map[int] Job
    jobs_c chan Job      
}

func NewJobManager (mgr *JobManager) {
    mgr := &JobManager{make(map[int]Job),make(chan Job,JOB_QUEUE_SIZE)}
    for {
        j,ok := <- jobs_c
        if !ok {break}
        go j.Run()
    }
}

type IntJob struct{...}
func (job *IntJob) GetOutChan() chan int {...}
func (job *IntJob) Run() {...}
func (job *IntJob) Serialize(o io.Writer) {...}

Mucho menos código y aproximadamente igual de útil.

Sobre la señalización de errores con un flujo axilar, siempre puede utilizar una función auxiliar.

type IntChanWithErr struct {
    c chan int
    errc chan os.Error
}
func (ch *IntChanWithErr) Next() (v int,err os.Error) {
    select {
        case v := <- ch.c // not handling closed channel
        case err := <- ch.errc
    }
    return
}

contestado el 30 de mayo de 11 a las 19:05

Hola Elazar, gracias por la sugerencia. Sin embargo, mi escenario no se trata solo de enviar trabajos, sino también de poder recibir otros tipos de comandos en esa goroutine, de ahí el select. Quiero que el gerente goroutine sea el único encargado de acceder al mapa laboral. Entiendo que es factible simplemente ocultando todo con las funciones de envoltura, solo me preguntaba si había una manera de implementar eso sin tanto código repetitivo, ya que estaba considerando usar este patrón en otros casos también. - mkm

¿Cómo se vería el otro comando? Puedes tener un genérico interface{} canal y pruebe su tipo en tiempo de ejecución si eso es lo que está buscando. Sin embargo, no es una gran solución en mi humilde opinión. Ver por ejemplo aquí golang.org/doc/go_spec.html#Type_switches - Elazar Leibovich

sí, este es el mismo problema que estaba sintiendo: opté por tener un canal para cada comando (vea mi estructura JobRegistry, tiene dos canales, uno para cada operación en este ejemplo). Utilizo un patrón de cambio de tipo en otros idiomas con uniones de tipo etiquetadas, en adelante está bien en el servidor goroutine, pero el cliente puede enviar lo que sea. - mkm

@ithkull, tienes que cambiar tu diseño. Intente pensar en las cosas que son comunes a la forma en que sus comandos interactúan con el que los ejecuta, y cree una interfaz que lo permita. Por ejemplo, si todos se ejecutan y devuelven un valor, todos deben obedecer interface{Run() Value}. - Elazar Leibovich

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.