асинхронный ответ в шаблоне реестра

#go

#Вперед

Вопрос:

Я изучаю go, и я хотел бы изучить некоторые шаблоны.

Я хотел бы создать компонент реестра, который поддерживает отображение некоторых элементов, и я хочу предоставить к нему сериализованный доступ:

В настоящее время я закончил с чем-то вроде этого:

 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 = amp;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
}
  

По сути, я инкапсулирую каждую операцию внутри структуры, которая несет
параметры и канал ответа.

Затем я создал вспомогательные методы для конечных пользователей:

 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
}
  

Я решил использовать канал для каждого типа запроса, чтобы быть безопасным для типов.


Проблема, которую я вижу при таком подходе, заключается в:

  • Много шаблонного кода и много мест для изменения при изменении некоторых параметров / возвращаемого типа

  • Приходится делать странные вещи, такие как создание еще одной структуры-оболочки, чтобы возвращать ошибки из подпрограммы обработки. (Если я правильно понял, кортежей нет, и нет способа отправить несколько значений в канале, например, многозначные возвраты)

Итак, мне интересно, имеет ли все это смысл, или, скорее, просто вернуться к старым добрым блокировкам.

Я уверен, что кто-нибудь найдет какой-нибудь умный выход, используя каналы.

Комментарии:

1. Почему вы считаете канал ошибок странным? AFAIK это довольно стандартный способ передачи ошибок по каналам. И вы можете обернуть это хорошей функцией.

Ответ №1:

Я не совсем уверен, что понимаю вас, но тем не менее я постараюсь ответить.

Вам нужен универсальный сервис, который выполняет отправленные ему задания. Вы также можете захотеть, чтобы задания были сериализуемыми.

Что нам нужно, так это интерфейс, который определял бы общее задание.

 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 := amp;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) {...}
  

Гораздо меньше кода и примерно столько же полезен.

Что касается сигнализации об ошибках с помощью подмышечного потока, вы всегда можете использовать вспомогательную функцию.

 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
}
  

Комментарии:

1. Привет, Элазар, спасибо за предложение. Однако мой сценарий касается не только отправки заданий, но и возможности получать другие виды команд в этой подпрограмме, отсюда и выбор. Я хочу, чтобы подпрограмма менеджера была единственной, отвечающей за доступ к карте заданий. Я понимаю, что это выполнимо, просто скрыв все с помощью функций-оболочек, просто интересно, есть ли способ реализовать это без такого большого количества шаблонного кода, поскольку я рассматривал возможность использования этого шаблона и в других случаях.

2. Как бы выглядела другая команда? У вас может быть общий interface{} канал и проверять его тип во время выполнения, если это то, что вы ищете. Однако, ИМХО, это не такое уж отличное решение. Смотрите, например, здесь golang.org/doc/go_spec.html#Type_switches

3. да, это та же проблема, с которой я столкнулся: я выбрал один канал для каждой команды (см. Мою структуру JobRegistry, в ней есть два канала, по одному для каждой операции в этом примере). Я использую шаблон переключения типов на других языках с помеченными объединениями типов, в go это нормально в подпрограмме сервера, но клиенту разрешено отправлять все, что угодно.

4. @ithkull, тебе нужно изменить свой дизайн. Попытайтесь подумать о том, что является общим для способа взаимодействия ваших команд с той, которая их выполняет, и создайте интерфейс, который позволяет это. Например, если все они выполняются и возвращают значение, все они должны подчиняться interface{Run() Value} .