2016-05-25 37 views
5

Ho tentato di dare una svolta alla concorrenza in Golang rifattorizzando uno dei miei programmi di utilità da linea di comando negli ultimi giorni, ma sono bloccato.Perché il mio canale Golang scrive blocco per sempre?

Here's codice originale (ramo principale).

Here's il ramo con concomitanza (x_concorrente).

Quando eseguo il codice in concomitanza con go run jira_open_comment_emailer.go, il defer wg.Done() non esegue se il problema JIRA viene aggiunto al canale here, che fa sì che il mio wg.Wait() da appendere per sempre.

L'idea è che ho una grande quantità di problemi JIRA e voglio eseguire una goroutine per ciascuno per vedere se ha un commento a cui devo rispondere. Se lo fa, voglio aggiungerlo ad una struttura (ho scelto un canale dopo qualche ricerca) che posso leggere da una coda in seguito per creare un promemoria via email.

Ecco la relativa sezione del codice:

// Given an issue, determine if it has an open comment 
// Returns true if there is an open comment on the issue, otherwise false 
func getAndProcessComments(issue Issue, channel chan<- Issue, wg *sync.WaitGroup) { 
    // Decrement the wait counter when the function returns 
    defer wg.Done() 

    needsReply := false 

    // Loop over the comments in the issue 
    for _, comment := range issue.Fields.Comment.Comments { 
     commentMatched, err := regexp.MatchString("~"+config.JIRAUsername, comment.Body) 
     checkError("Failed to regex match against comment body", err) 

     if commentMatched { 
      needsReply = true 
     } 

     if comment.Author.Name == config.JIRAUsername { 
      needsReply = false 
     } 
    } 

    // Only add the issue to the channel if it needs a reply 
    if needsReply == true { 
     // This never allows the defered wg.Done() to execute? 
     channel <- issue 
    } 
} 

func main() { 
    start := time.Now() 

    // This retrieves all issues in a search from JIRA 
    allIssues := getFullIssueList() 

    // Initialize a wait group 
    var wg sync.WaitGroup 

    // Set the number of waits to the number of issues to process 
    wg.Add(len(allIssues)) 

    // Create a channel to store issues that need a reply 
    channel := make(chan Issue) 

    for _, issue := range allIssues { 
     go getAndProcessComments(issue, channel, &wg) 
    } 

    // Block until all of my goroutines have processed their issues. 
    wg.Wait() 

    // Only send an email if the channel has one or more issues 
    if len(channel) > 0 { 
     sendEmail(channel) 
    } 

    fmt.Printf("Script ran in %s", time.Since(start)) 
} 
+3

Hai 'len (canale)' dappertutto, ma quel canale non ha lunghezza perché è senza buffer. È necessario ricevere dal canale ogni invio per completare (e in generale, prendere decisioni in base alla lunghezza di un canale memorizzato nel buffer è un errore, dal momento che le operazioni concorrenti possono correre per modificare quel valore) – JimB

+0

Quindi, se sto facendo tutto delle mie scritture sul canale, in attesa che vengano completate, e quindi leggendo dal canale ... ciò non può mai accadere perché le mandate non verranno mai effettivamente completate e innescare il 'defer wg.Done()'? Come affronteresti l'implementazione di questa concorrenza, in generale? Inoltre, non sono sicuro che tu sia corretto su 'len (canale)', poiché i godocs affermano che restituisce il numero corrente di elementi nel canale, non la capacità come 'cap (canale)' . https://golang.org/pkg/builtin/#len –

+0

'len (canale)' restituisce il numero corrente di elementi in un canale "bufferizzato", ma poiché i canali vengono solitamente utilizzati contemporaneamente, il risultato di 'len' è "stantio" non appena lo leggi. Di solito si hanno goroutine inverse e inviate dal canale. Vorrei consigliare di passare di nuovo attraverso la sezione [Concurrency] (https://tour.golang.org/concurrency/1) nel Tour Of Go per capire meglio come funzionano i canali. – JimB

risposta

8

I goroutines blocco sulla invio al canale senza buffer. Un cambiamento minimo sblocca i goroutines è creare un canale tamponata con capacità per tutti i problemi:

channel := make(chan Issue, len(allIssues)) 

e chiudere il canale dopo la chiamata a wg.Wait().

+2

Ma questo tipo di sconfigge lo scopo di un canale come un tubo tra blocchi simultanei .... – RickyA

+0

@RickyA Non c'è niente di sbagliato nell'usare un canale è una coda di elementi. –

+0

vero, ti risparmia l'overhead di passare una fetta mutex intorno. – RickyA