Published on

I miei primi passi su Go

Authors

In questo periodo di quarantena, quale miglior passatempo se non quello di iniziare ad esplorare un nuovo linguaggio? Era una cosa che volevo fare già da un pezzo, ma che continuavo a procrastinare sempre. Ora ci siamo!

La mia scelta è ricaduta su Go (detto anche golang), un linguaggio creato da mamma Google nel 2012 con un'interessantissima prerogativa: essere un linguaggio formalmente quanto più vicino possibile a C/C++ in termini di performance, ma più semplice da usare. Si tratta di un linguaggio progettato per i backend, quindi chissà... un giorno potrebbe anche sostituire NodeJS+TS nella mia stack.

Come ho iniziato l'esplorazione?

Prima di tutto mi sono diretto subito sul bellissimo tour interattivo del linguaggio, dove ho imparato le basi e alcune sue features interessanti in meno di 2 ore... altro che corsi a pagamento! Una feature in particolare che mi ha colpito molto durante questo primo impatto con Go sono le goroutines, ossia come Go gestisce l'asincronicità. Ne discuteremo meglio in seguito. Fatto sta che mi sono messo in testa di voler creare un applicativo dove poter sfruttare al 100% le goroutine, per tastare con mano la loro potenza e performance. Ma quale? Ci ho riflettuto un po'...

Che cosa creare adesso?

Oltre ai classici "Hello world" e stronzate del genere non c'è molto che puoi fare senza avere una conoscenza molto avanzata del linguaggio. Non potevo certo mettermi a scrivere subito un backend! (Anche se l'idea c'è stata inizialmente...)

Allora mi sono ricordato che uno dei migliori programmi che puoi scrivere per imparare meglio un linguaggio sotto ogni suo aspetto è in realtà uno dei più semplici: un web scraper!

Cos'è un web scraper?

Un web scraper è un programma che prende in input una pagina web (un link, per intenderci), opera direttamente sul suo HTML per raccogliere dati, o effettuare azioni automaticamente. Io ho scritto uno scraper praticamente per qualunque cosa, ma di solito li scrivevo sempre in NodeJS o in Python. Allora mi sono detto: "Perché non provarne a scrivere uno in Go?". Adesso mi serviva solo un obiettivo chiaro...

Cyberdrop!

Chiaramente, senza un preciso utilizzo, uno scraper è inutile... mi serviva un sito dove mi tornerebbe effettivamente comodo. Non vi andrò a dire come, ma la mia scelta è ricaduta su Cyberdrop. Cyberdrop è un sito che si occupa di file upload completamente anonimo. Una sua feature sono gli album: raccolta di file sotto un unico link condivisibile agli altri. Cyberdrop è completamente anonimo, dunque torna utile quando si vogliono condividere file (soprattutto immagini) senza possibilità di risalire all'autore in alcun modo (non andrò a spiegare i dettagli).

C'è un problema però: Cyberdrop non eccelle in user experience. Non è presente un pulsante per scaricare tutto un album in un colpo solo, devi scaricare immagine per immagine. Allora perché farlo fare a una persona, quando i computer sono molto più pazienti di noi?

File streaming in parallelo mediante goroutines

E così, in una piovosa serata di Aprile, ascoltando soundtrack di Danganronpa e Hacknet a palla, ho creato questo programma:

https://github.com/giovanni-orciuolo/cyberdrop-downloader

Non entrerò nei dettagli di tutto il suo funzionamento (è un semplice applicativo CLI), ma su una cosa vorrei soffermarmi: il mio uso delle goroutine per parallelizzare i download.

Gli altri scraper di Cyberdrop scritti in NodeJS o in Python (esatto, esistono già!) hanno un difetto: non parallelizzano i download. Significa che il programma scarica immagine per immagine, sequenzialmente, e questo porta a una lentezza indescrivibile. Sebbene ci siano altri thread disponibili a svolgere questo lavoro (che per altro non richiede risorse condivise, quindi nessuna possibilità di deadlock, grazie Ruta) gli altri programmi semplicemente si rifiutano di usarli.

Go invece nasce asincrono. Mediante le goroutines, il programma diventa multi-threading senza praticamente alcuno sforzo, solo un pochino di sintassi in più. Assurdo!

Ecco che quindi il codice diventa semplicissimo e facile da seguire:

chDoneDownload := make(chan bool)
for _, image := range images {
        go downloadImage(directory, image, chDoneDownload)
}

for c := range images {
        select {
        case <-chDoneDownload:
                c++
        }
}

O ancora:

var title string
var images []string
go crawlAlbumImages(url, chTitle, chImages, chUrlsCrawlDone)

crawl:
for {
        select {
        case tit := <-chTitle:
                title = tit
        case img := <-chImages:
                images = append(images, img)
        case <-chUrlsCrawlDone:
                break crawl
        }
}

Lascerò che siano i numeri a parlare:

Timing download di un album da 40 immagini (di elevata qualità, formato JPEG), velocità 100MBit/s:

  • NodeJS (no multi-threading) ~ 17 minuti
  • Python (no multi-threading) ~ 20 minuti
  • Go (con goroutines) ~ 36 secondi

Potete esplorare in autonomia il codice per approfondire il funzionamento dello scraping in se', ma anche quello è molto semplice utilizzando le funzionalità HTTP già incluse nel linguaggio! E' tutto così bello... non vedo l'ora di usare questo linguaggio per finalità più complesse.

Go: consigliato!

Vi consiglio vivamente questo linguaggio se siete alla ricerca di elevate performance (paragonabili a C/C++) ma siete stufi di star dietro a ogni singola stranezza o particolarità di questi ultimi.

That's all folks! Alla prossima!