Рефакторинг кода привел к проблемам с взаимодействием структур и скоростью выполнения поставленных задач

я начал переписывать свой код из-за того, что в функциональном виде он начал терять читаемость. Основная цель моего кода заключается в следующем:

Есть процессор, у него есть отправитель адресов страниц, воркеры этого процессора читают эти адреса и стягивают с них необходимую информацию.

Код:

import (
    "sync"
    "path"
    "github.com/PuerkitoBio/goquery"
    "fmt"
    "log"
)

type Shape struct {
    Name, PathToImage string 
}

type Champion struct {
    Name, PathToDefaultSkin, Complexity, Info, Role string
    Shapes                                          []Shape
}

func collectChampion(page *goquery.Document) Champion {
    var champion Champion

    champion.Name = page.Find(".iLTyui span").Text()

    return champion
}

type Worker struct {}

func (w *Worker) Work(tasks chan string, championsCh chan Champion) {
    for requestURL := range tasks {
        page, err := getGoqueryDocumentByURL(requestURL)
        
        if err != nil { log.Fatal(err) }
        
        championsCh <- collectChampion(page)
    }
}

type Handler struct {
    pendingChampions chan Champion
}

func (h *Handler) Handle() {
    for champion := range h.pendingChampions {
        fmt.Println(champion.Name)
    }
}

type Processor struct {
    Tasks   chan string
    Workers []Worker
    Handler Handler
    Sender  func()
    WG      *sync.WaitGroup
}

func (p *Processor) init() {
    p.Tasks = make(chan string)

    p.Sender = func() {
        document, err := getGoqueryDocumentByURL(MonitoringPage)
        if err != nil { log.Fatal(err) }
            
        document.Find(ChampionsSelector).Each(func(
            index int, item *goquery.Selection) {
                championPageURL, _ := item.Attr("href")
                p.Tasks <- MonitoringPage + path.Base(championPageURL)
            },
        )
        close(p.Tasks)
    }
    for i := 0; i < WORKERS; i++ {
        p.Workers = append(p.Workers, 
            Worker{})
    }
    p.Handler = Handler{ pendingChampions: make(chan Champion) }
}

func (p *Processor) InformationProcessor() {
    go p.Sender()

    for _, worker := range p.Workers {
        go worker.Work(p.Tasks,
             p.Handler.pendingChampions)
    }
    
    go p.Handler.Handle()
    
    for {}
}

На for {} в конце метода процессора обращать внимания не стоит, я знаю, что так поступать нельзя, просто еще WG не задействовал.

Вот чем меня смущает мое "нерукотворное произведение":

  1. Скорость получения данных уменьшилась несмотря на то, что читаемость слегка улучшилась.
  2. Структура воркера пуста из-за того, что если в ней объявить поле tasks, которое вместо процессора будет получать урлы, код будет работать некорректно.(вместо 150 необходимых объектов он может вернуть под 400, видимо это из-за того, что получение урлов происходит для каждого воркера. Скорость кода падает еще больше)

Вот о чем я:

import (
    "sync"
    "path"
    "github.com/PuerkitoBio/goquery"
    "fmt"
    "log"
)

type Shape struct {
    Name, PathToImage string 
}

type Champion struct {
    Name, PathToDefaultSkin, Complexity, Info, Role string
    Shapes                                          []Shape
}

func collectChampion(page *goquery.Document) Champion {
    var champion Champion

    champion.Name = page.Find(".iLTyui span").Text()

    return champion
}

type Worker struct {
    pendingTasks chan string // добавили поле
}

func (w *Worker) Work(championsCh chan Champion) {
    for requestURL := range w.pendingTasks { // проходка по полученным таскам ВОРКЕРА, а не ПРОЦЕССОРА
        page, err := getGoqueryDocumentByURL(requestURL)
        
        if err != nil { log.Fatal(err) }
        
        championsCh <- collectChampion(page)
    }
}

type Handler struct {
    pendingChampions chan Champion
}

func (h *Handler) Handle() {
    for champion := range h.pendingChampions {
        fmt.Println(champion.Name)
    }
}

type Processor struct {
    Workers []Worker
    Handler Handler
    Sender  func(chan string)
    WG      *sync.WaitGroup
}

func (p *Processor) init() {
    p.Sender = func(ch chan string) {
        document, err := getGoqueryDocumentByURL(MonitoringPage)
        if err != nil { log.Fatal(err) }
            
        document.Find(ChampionsSelector).Each(func(
            index int, item *goquery.Selection) {
                championPageURL, _ := item.Attr("href")
                ch <- MonitoringPage + path.Base(championPageURL) // возвращаем в канал полученный при вызове
            },
        )
        close(ch)
    }
    for i := 0; i < WORKERS; i++ {
        p.Workers = append(p.Workers, 
            Worker{ pendingTasks: make(chan string) })
    }
    p.Handler = Handler{ pendingChampions: make(chan Champion) }
}

func (p *Processor) InformationProcessor() {
    for _, worker := range p.Workers {
        go p.Sender(worker.pendingTasks)            // вот скорее всего основная проблема,
        go worker.Work(p.Handler.pendingChampions) // из-за которой падает корректность и производительность                                
    }
    // мы для каждого воркера повторно получаем адреса из сендера
    go p.Handler.Handle()
    
    for {}
}

Зная проблему я мог бы ее и сам пофиксить, но вот я не знаю как. Меня волнует, чтобы каждый воркер был задействован для скорости выполнения кода, но если так поступать то приходится для каждого вызывать сендер, а это приводит к некорректности. В другом случае структура воркера остается пустая, что тоже, как я полагаю, не круто, а таски записываются в процессор.


Ответы (0 шт):