35
Scuola Politecnica e delle Scienze di Base Corso di Laurea in Ingegneria Informatica Elaborato finale in Programmazione I Il linguaggio di programmazione Go di Google Anno Accademico 2016/17 Candidato: Domenico Iannucci matr. N46/2034

Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

Embed Size (px)

Citation preview

Page 1: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

Scuola Politecnica e delle Scienze di Base Corso di Laurea in Ingegneria Informatica

Elaborato finale in Programmazione I

Il linguaggio di programmazione Go di Google

Anno Accademico 2016/17

Candidato: Domenico Iannucci matr. N46/2034

Page 2: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

A Michele, mio padre.

Page 3: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

Indice

Indice .................................................................................................................................................. III

Introduzione al linguaggio Go ............................................................................................................. 4

I. Motivazioni nel panorama attuale dello sviluppo software ......................................................... 4

II. Obiettivi del linguaggio ............................................................................................................... 5

III. Caratteristiche fondamentali ...................................................................................................... 5

IV. Storia del linguaggio .................................................................................................................. 6

Capitolo 1: Sintassi del linguaggio ...................................................................................................... 8

1.1 Formattazione............................................................................................................................. 8

1.2 Nomi, dichiarazioni e assegnazioni ........................................................................................... 8

1.3 Tipi di dato ................................................................................................................................. 9

1.3.1 Allocazione con new e make ............................................................................................. 10

1.4 Strutture di controllo ................................................................................................................ 10

1.5 Funzioni e defer ....................................................................................................................... 12

1.6 Metodi e interfacce ................................................................................................................... 12

1.7 Goroutines e channels .............................................................................................................. 13

Capitolo 2: Compilazione ed esecuzione ........................................................................................... 15

2.1 Organizzazione in package e dipendenze ................................................................................ 15

2.2 Compilazione ed esecuzione .................................................................................................... 16

2.3 Errori, panic e recover ............................................................................................................. 17

Capitolo 3: Aspetti avanzati ............................................................................................................... 18

3.1 Modello di allocazione delle variabili e garbage collector ..................................................... 18

3.2 Polimorfismo ............................................................................................................................ 18

3.3 Gestione dell’I/O ...................................................................................................................... 19

3.4 Gestione della concorrenza e parallelizzazione ....................................................................... 20

3.5 Cenni a rete, socket e design patterns ...................................................................................... 21

Capitolo 4: Confronto con C++ e Java .............................................................................................. 22

Capitolo 5: Applicazioni esistenti ...................................................................................................... 23

Capitolo 6: Sviluppo di un programma in Go .................................................................................... 24

6.1 Installazione e ambienti di sviluppo ......................................................................................... 24

6.2 Esempio di programma in Go .................................................................................................. 24

Conclusioni ........................................................................................................................................ 29

Bibliografia ........................................................................................................................................ 30

Appendice .......................................................................................................................................... 32

Page 4: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

4

Introduzione al linguaggio Go

In questo elaborato verrà analizzato il linguaggio di programmazione Go sviluppato da

Google. Nell’introduzione saranno descritte le motivazioni che hanno portato allo sviluppo

del progetto Go, gli obiettivi che i creatori si sono posti, le caratteristiche fondamentali che

rendono questo linguaggio di programmazione peculiare e una breve storia di Go, con una

panoramica delle versioni. A seguire nel capitolo 1 sarà esposta la sintassi del linguaggio

mentre nel capitolo 2 la compilazione e l’esecuzione di un programma Go con attenzione

anche alla gestione dell’errore. Ancora, nel capitolo 3 verranno chiariti gli aspetti avanzati

del linguaggio, in particolare si farà riferimento alla gestione della concorrenza, e nel

capitolo 4 i linguaggi Go, C++ e Java saranno messi a confronto sulla base di alcune

caratteristiche. A conclusione, il capitolo 5 illustrerà l’impiego attuale del linguaggio Go

nello sviluppo di applicazioni industriali e il capitolo 6 presenterà un esempio di sviluppo

di un programma in Go attraverso un semplice caso di studio che comunque ha consentito

di applicare alcuni concetti di ingegneria del software, sistemi operativi e programmazione.

I. Motivazioni nel panorama attuale dello sviluppo software

L’esigenza di creare un nuovo linguaggio di programmazione sorge dai problemi incontrati

dagli ingegneri Google durante lo sviluppo delle infrastrutture software dell’azienda e dalla

loro analisi delle sfide oggi poste dallo sviluppo software. Nell’ultimo decennio infatti, al

progresso nello sviluppo di sistemi hardware, avutosi nello specifico con l’introduzione dei

processori multicore, non è corrisposto un analogo progresso nello sviluppo dei sistemi

software. Anzi, la gestione della dimensione degli attuali progetti è diventata critica, in

particolare la gestione delle dipendenze tra moduli nello sviluppo software determina

Page 5: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

5

sempre più la necessità di strumenti di compilazione veloce e di strumenti per una

efficiente analisi delle dipendenze. I programmatori stanno tendendo verso l’uso di

linguaggi tipizzati dinamicamente, che facilitano la programmazione, come Python o

Javascript, che supportano alcuni meccanismi in grado di ottimizzare le prestazioni delle

applicazioni da un lato e del sistema dall’altro, quali il garbage collector e i meccanismi

per la gestione della concorrenza, che consentono di sfruttare la potenza di calcolo dei

processori multicore. Tali meccanismi non sono ben supportati dai linguaggi di

programmazione tradizionali e la necessità di un nuovo linguaggio di programmazione

nasce quindi dall’esigenza di avere un linguaggio che risponda ai seguenti requisiti:

compilazione efficiente, esecuzione veloce e facilità di programmazione.

II. Obiettivi del linguaggio

Il principale obiettivo è creare un linguaggio multipurpose, adatto quindi a diversi scopi e

ambienti di sviluppo, che coniughi la velocità di compilazione all’esecuzione efficiente e

alla facilità di programmazione. Il proposito è combinare l’efficienza e la safety dei

linguaggi compilati e tipizzati staticamente con la facilità di programmazione dei linguaggi

interpretati e tipizzati dinamicamente. L’idea è stata di renderlo più produttivo rispetto a

linguaggi come C++, Java e Python, i quali sono stati sviluppati in anni in cui gli ambienti

e le problematiche di sviluppo erano totalmente diverse. Go, quindi, nativamente introduce

supporto ai processori multicore e alle reti, oltre alla costruzione di un programma

flessibile e modulare. Lo scopo, non ancora realizzato in forma matura, è ridurre il

problema della scalabilità: oggi i web server hanno decine di milioni di righe di codice, con

conseguente fase di compilazione della durata di minuti, se non di ore. Go, invece,

promette una compilazione efficiente e di breve durata, persino sulle macchine meno

performanti. Per raggiungere questo obiettivo, Go ha dovuto affrontare una serie di

questioni linguistiche, con conseguenti scelte non immediatamente intuitive.

III. Caratteristiche fondamentali

Il Go è un progetto open-source in licenza BSD. Esistono quindi dei repository pubblici

importati da Google. È un linguaggio compilato, caratterizzato da un’alta velocità di

Page 6: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

6

esecuzione e di compilazione. Quest’ultima è eccellente sia su macchina singola che per

grossi file sorgente. Può essere definito semplice, perché è simile a livello sintattico al C

ma molto ottimizzato. Mantiene quindi le caratteristiche del C, aggiungendone altre tipiche

dei linguaggi dinamici. Implementa inoltre a livello nativo la concorrenza tramite le

goroutines, cioè funzioni eseguite concorrentemente anche su processori multicore.

Avendo un sistema di tipizzazione di tipo non gerarchico, garantisce la sicurezza. Il

linguaggio è strettamente controllato nell’uso della memoria e nella tipizzazione, a

differenza del C. In Go, sempre per garantire la sicurezza, è stata esclusa l’aritmetica dei

puntatori. Il linguaggio è corredato di garbage collector, che lo rende più semplice, poiché

il programmatore è sollevato dalla gestione della memoria. In sintesi, gli ingegneri di

Mountain View hanno preso ispirazione dai problemi dei principali linguaggi di

programmazione: la difficoltà per il C, la lentezza dei linguaggi interpretati e dai margini

legati alle performance delle macchine virtuali.

IV. Storia del linguaggio

L’idea nasce nel 2007 da Robert Griesemer, Rob Pike, e Ken Thompson per cercare di

risolvere alcuni problemi che si presentavano durante lo sviluppo delle infrastrutture

software di Google. “Go è un linguaggio di programmazione sviluppato da Google per

aiutare a risolvere i problemi di Google, e Google ha

grandi problemi.” [1] Il nome Go è stato deciso dopo una

lunga fase di sviluppo, e non è stato esente da

controversie. Nel panorama mondiale, esisteva già un

linguaggio dal nome Go!, ma nonostante la richiesta del

suo creatore Francis McCabe, il nome del linguaggio non

è stato modificato poiché, secondo Google, i due

linguaggi non possono essere confusi. La prima versione

stabile del linguaggio è del marzo 2011 e solo un anno dopo è stata rilasciata la versione

1.0 che ne ha sancito ufficialmente la nascita. Il linguaggio, negli anni, grazie all’influenza

di BigG è riuscito a ottenere sempre più popolarità, aggiudicandosi il titolo di “Linguaggio

di programmazione dell’anno” di TIOBE nell’anno 2009 e nell’anno 2016. [2]

Gopher: la mascotte di Go

Page 7: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

7

Nel seguito verrà indicata brevemente una panoramica delle major releases con le novità

introdotte. [3]

La novità della versione 1.0 è l’introduzione dell’interfaccia error contenente il metodo

Error(). Questa è fondamentale poiché apre il linguaggio alla gestione degli errori,

inizialmente assente. Altra introduzione sono gli operatori == e != per le struct e gli array.

Tali operatori vengono ora definiti, non in base ai puntatori, ma ai campi e agli elementi,

come è più solito l’utilizzo da parte degli utenti.

Con la versione 1.1, del maggio 2013, viene trattata la divisione intera per zero come un

errore a tempo di compilazione e non più a run-time.

La versione 1.2, di dicembre 2013, vede l’introduzione di un limite configurabile del

numero di threads che può avere un programma per evitare la starvation delle risorse.

Inoltre, viene incrementato lo stack a 8KB delle goroutines.

Lo stack delle goroutines è stato modificato nella versione 1.3 del giugno 2014, ove si è

passato da un modello di memoria segmentato a uno contiguo. Se una goroutine necessita

di più memoria stack di quella effettivamente disponibile, l’intero stack viene trasferito su

una porzione di memoria maggiore.

Nel dicembre 2014 è rilasciata la versione 1.4, con la quale viene modificata la sintassi del

ciclo for per evitare di definire un indice se non è effettivamente usato. Viene inoltre

aggiunto il supporto ai processori ARM per compilare file binari per il sistema operativo

Android.

Caratteristica fondamentale della versione 1.5 di agosto 2015 è la totale assenza del

linguaggio C. Da questa implementazione il compilatore è scritto totalmente in Go e in

assembler. Il garbage collector ora è concorrente con le goroutines.

Dalla versione 1.6 di febbraio 2016 alla versione 1.9 di agosto 2017, vengono incrementate

le performace del garbage collector, viene migliorata la gestione della concorrenza e

vengono aggiunti vari tools di supporto.

Nel luglio 2017 si è iniziato a parlare di Go 2 che porterà futuri miglioramenti e il cui

obiettivo sarà risolvere i problemi di scalabilità incontrati da Go 1.

Page 8: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

8

Capitolo 1: Sintassi del linguaggio

In questo capitolo verrà presentata la sintassi principale del linguaggio di programmazione

Go, consultabile sulla documentazione ufficiale, in relazione al C/C++. [4][5]

1.1 Formattazione

Il programma gofmt analizza il codice sorgente e lo riformatta nella maniera corretta. Nel

particolare segue un insieme di regole, quali l’indentazione e il giusto allineamento delle

colonne e dei commenti. Ciò permette una facile leggibilità dei sorgenti da parte di diversi

programmatori, poiché vengono adottate regole standard.

I punti e virgole non appaiono come in C/C++ alla fine di ogni riga di codice.

L’analizzatore lessicale del linguaggio Go provvede automaticamente all’inserimento di

questi dove necessario, cioè al seguito di alcuni simboli e parole chiave.

1.2 Nomi, dichiarazioni e assegnazioni

Per utilizzare i contenuti di un package importato nel sorgente, si usa la dot notation. Ad

esempio, per stampare a video si usa la funzione Println del package fmt:

fmt.Println(“Hello world!”)

Il linguaggio Go è case-sensitive, e in aggiunta attribuisce un particolare significato alle

lettere maiuscole. Nel particolare, i campi di una struttura hanno visibilità pubblica solo se

il nome inizia per lettera maiuscola, in caso contrario la visibilità è privata. La convenzione

utilizzata stabilisce una regola anche per i metodi set e get. Per il primo viene usato

SetVariabile(), invece per le get si conviene usare Variabile() in luogo di GetVariabile().

Page 9: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

9

Per il nome delle interfacce, nel caso contengano un solo metodo, è prevista l’aggiunta del

suffisso -er al nome del metodo contenuto. Ad esempio, un’interfaccia con un metodo

Write() sarà nominata Writer().

Per definire le variabili, il linguaggio offre le seguenti parole chiave: var per le variabili, il

cui valore può essere modificato nel corso del programma, e const per le costanti.

Go supporta l’inferenza di tipo, cioè la capacità del compilatore di dedurre il tipo di

variabile o costante in base al valore. Sono ammissibili, quindi, dichiarazioni sulla base di

assegnazioni, come ad esempio:

var a := 10

var b int

c := 5

1.3 Tipi di dato

Nel linguaggio Go esistono i seguenti tipi base di dato:

- numeri: int8, int16, int32, int64, e le corrispondenti versioni uint, float32, float34,

complex64, complex128

- byte, è un alias per uint8

- bool per i valori booleani

- per le stringhe string

Su questi tipi di dato sono definiti i consueti operatori.

I tipi di dati strutturati nel linguaggio Go, invece, sono le struct, gli array, le slices e le

maps.

Le strutture sono tipi valore che contengono uno o più campi di diverso tipo. Questi campi

possono essere acceduti dall’esterno del package di appartenenza se hanno nomi inizianti

per lettera maiuscola, in caso contrario sono visibili solo all’interno del medesimo

package.

Gli array in Go sono sequenze di elementi, dello stesso tipo base, di lunghezza fissa. La

principale differenza rispetto al C/C++ è che questi sono valori, cioè assegnare un array ad

un altro significa copiare tutti gli elementi. E ancora, passare un array a una funzione

significa che questa ne riceverà una copia.

Page 10: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

10

Le slices sono un reference type, nel particolare un riferimento a una porzione di array, che

può anche coincidere con l’array stesso. La lunghezza delle slices è modificabile

dinamicamente, però questa non può mai eccedere la propria capacità, che è data dal limite

dell’array sottostante.

Un tipo map è una sequenza di coppie chiave-valore, aventi il vincolo dell’unicità della

chiave.

1.3.1 Allocazione con new e make

Le primitive di allocazione in Go sono new e make, le quali presentano alcune differenze.

La funzione new alloca la memoria ma non la inizializza, semplicemente la azzera, e

ritorna il puntatore al tipo di dato allocato. Questo tipo di allocazione è utile quando non si

ha bisogno di ulteriori inizializzazioni. L’allocazione mediante make crea solo slices,

mappe e channels, e ritorna un valore di tipo inizializzato, non un puntatore. Il supporto a

solo tre tipi di dato è dovuto al fatto che rappresentano riferimenti a strutture dati che

devono essere inizializzate prima dell’uso.

1.4 Strutture di controllo

In Go non esistono cicli while-do e do-while presenti in altri linguaggi di programmazione.

A differenza di C/C++ non è previsto l’utilizzo delle parentesi tonde per la condizione da

verificare nella struttura di selezione if e if/else. Una novità introdotta in Go è la possibilità

di dichiarare e assegnare una variabile dopo la parola chiave if e controllare su questa la

condizione. Ad esempio:

if err := file.Chmod(0664) ; err != nil {

//altre istruzioni

}

La struttura di selezione switch-case, in Go, è stata notevolmente ampliata. Nella sua

struttura standard, la variabile da valutare può essere di qualsiasi tipo. È permesso inserire

più istruzioni nel medesimo case anche senza inserire parentesi graffe, e prevedere più

alternative per ogni case separando i valori con una virgola. Interessante è la totale assenza

della parola chiave break, poiché il fallthrough è possibile solo esplicitamente con

Page 11: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

11

l’istruzione fallthrough, in modo da evitare l’errore di far eseguire più branch dello switch

in sequenza.

Sono state introdotti anche due utilizzi dello switch del tutto assenti in linguaggi come

C/C++. Il primo prevede che a livello dell’istruzione switch non sia presente alcuna

istruzione, lasciando ai vari case la valutazione di diverse condizioni.

switch {

case condizione1:

//istruzione

case condizione2:

//istruzione

default:

//istruzione

}

La seconda prevede una inizializzazione nella riga dello switch, conclusa da un punto e

virgola, e una serie di case con i valori da confrontare.

switch inizializzazione; {

case valore1:

istruzione

case valore2:

istruzione

default:

istruzione

}

In Go la struttura for comprende anche la struttura iterativa while, ma non il do-while,

poiché non previsto dal linguaggio.

La sintassi per il for è uguale al C/C++, ma con l’assenza delle parentesi tonde. È possibile

dichiarare la variabile indice nel for stesso, e prevedere più variabili indice, evitando, però,

l’uso degli operatori unari di incremento e decremento, poiché in Go non sono espressioni

ma istruzioni. Differisce, invece dal C/C++, nella possibilità di non dichiarare la variabile

indice: supponendo di voler scorrere un array mioarray per effettuare una somma degli

elementi, si può scrivere:

for _, valore := range mioarray {

somma+ = valore

}

Per quanto riguarda il ciclo while, invece, la sintassi è la seguente:

for condizione {

//istruzioni

}

Page 12: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

12

1.5 Funzioni e defer

Un elemento distintivo del Go è la possibilità delle funzioni, ma anche dei metodi, di avere

valori di ritorno multipli. Ciò nasce per evitare il comportamento di alcune funzioni in C,

cioè far ritornare alla funzione un valore negativo per segnalare un errore. Ad esempio, la

funzione Read del package os restituisce due valori, come si legge dalla seguente firma:

func (f *File) Read(b []byte) (n int, err error)

In aggiunta, all’interno di una funzione è possibile utilizzare un parametro di ritorno senza

dichiararlo. Questo sarà automaticamente inizializzato a zero e, al termine della funzione,

restituito senza ulteriori specificazioni, tramite solo un return.

Utilizzando la dichiarazione defer prima di una chiamata a funzione, questa viene eseguita

immediatamente prima del return della funzione chiamante. Il classico esempio di utilizzo

è il rilascio di un mutex o la chiusura di un file. L’utilizzo del defer, nel caso del file, ha il

vantaggio di non far dimenticare ai programmatori la chiusura, e far apparire la chiusura

dopo l’apertura, che rende il codice più chiaro rispetto a chiudere il file alla fine della

funzione. Gli eventuali argomenti della funzione chiamata sono valutati all’esecuzione di

defer e non alla chiamata. In presenza di più defer, le funzioni vengono eseguite in ordine

LIFO prima del return.

1.6 Metodi e interfacce

Il linguaggio Go consente di definire metodi per qualsiasi tipo di dato, quindi non

necessariamente le strutture, ad eccezione di puntatori e interfacce. I metodi possono avere

come ricevente, cioè come tipo di dato su cui sono dichiarati, anche un puntatore al

ricevente. Nel caso del puntatore al ricevente verrà modificato il dato originale, in caso

contrario il metodo utilizza una copia del dato originale.

Le interfacce definiscono un comportamento di un certo tipo, essendo composte da un

insieme di metodi. Le interfacce sono come dei contratti fra l’aspettativa di

implementazione e la realizzazione effettiva. Il concetto di interfaccia, unitamente a quello

di struct, avvicina il linguaggio Go al paradigma di programmazione ad oggetti, poiché dal

Page 13: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

13

punto di vista morfologico le classi sono delle strutture con dei metodi associati. Nella

sintassi del linguaggio, un tipo di dato non ha bisogno di esplicitare con parole chiave

l’implementazione di un’interfaccia ma deve solo implementare i metodi previsti da

questa. Naturalmente, più tipi possono implementare la stessa interfaccia e più interfacce

possono essere implementate da un solo tipo.

1.7 Goroutines e channels

Le goroutines sono il meccanismo adottato dal linguaggio Go per gestire il multithreading

nativamente. Il nome è stato scelto dai creatori poiché a loro parere genera meno

confusione di parole come threads e processi. Una goroutine è semplicemente una

funzione eseguita in concorrenza con altre goroutines nello stesso spazio di indirizzamento.

Le goroutines sono leggere e costano poco più dello spazio di allocazione di uno stack. Lo

stack in partenza è molto piccolo, 8KB, ma all’occorrenza viene ampliato liberando la

porzione di memoria e allocando un segmento di lunghezza maggiore. Le goroutines

vengono affidate a diversi threads del sistema operativo, cosicché, se una è in stato

bloccato in attesa ad esempio di un’operazione di I/O, le altre possono continuare

l’esecuzione. Per lanciare una goroutine basta anteporre la parola go alla chiamata di

funzione, ma bisogna notare che se il chiamante termina l’esecuzione allora vengono

terminate anche le goroutines chiamate.

I canali offrono un meccanismo per la comunicazione, inviando e ricevendo valori di un

tipo determinato in ordine FIFO, fra funzioni in esecuzione concorrente. Nella

dichiarazione di un canale, tramite la parola chan, è possibile specificare la direzione, cioè

invio o ricezione, con l’operatore <-. Ad esempio:

chan int // canale bidirezionale per valori interi

chan<- int // canale per l’invio di int

<-chan int // canale a sola ricezione di int

Un canale può essere creato con la funzione di allocazione make, che ha come parametri il

tipo di canale e opzionalmente la capacità:

make(chan float, 5)

Page 14: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

14

La capacità rappresenta la lunghezza del buffer del canale. Se è zero o non specificata, il

canale è non bufferizzato, quindi il sender si blocca fino a quando il receiver non ha

ricevuto il valore; in caso contrario il canale è bufferizzato e il sender si blocca solo per

copiare il valore sul buffer, ma se questo è pieno significa aspettare la ricezione di un

receiver. Il canale può essere chiuso tramite la funzione close.

Page 15: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

15

Capitolo 2: Compilazione ed esecuzione

In questo capitolo è illustrata l’organizzazione in packages, l’esecuzione di un programma

Go e la gestione dell’errore.

2.1 Organizzazione in package e dipendenze

La proprietà di modularità di un programma Go è garantita dall’organizzazione in

packages. Questi sono chiaramente un riferimento al linguaggio Java. Infatti, come prima

riga di un file .go è dichiarato il package di appartenenza, seguito poi dai vari import dei

package necessari per il programma in questione. Una proprietà importante è che

nell’import può essere inserita una URL, quindi c’è la possibilità di avere dei packages

remoti. Tramite il comando go get si ottiene il package dall’URL e lo si installa, se ne fa

l’import e lo si usa come un qualsiasi package. Il comando go get scarica le dipendenze

ricorsivamente, il naming dei package è decentralizzato e scalabile, essendo delegato alle

URL l’allocazione dello spazio dei path degli import. [1]

Il problema delle dipendenze è stato affrontato con particolare attenzione dagli ingegneri di

Mountain View. Nel particolare è stato messo in discussione tutto il sistema degli #include

e degli #ifndef contenuti negli header files del C/C++, in quanto sofferenti di un grave

problema di scalabilità. Tutte queste istruzioni prevedono, infatti, che il precompilatore

controlli molteplici volte gli stessi files, con conseguente incremento delle operazioni di

I/O. Seguendo lo standard ANSI, devono essere dichiarate le librerie utilizzate all’inizio di

ogni file sorgente, con il conseguente inserimento multiplo delle stesse librerie oltre alla

naturale dipendenza concatenata fra queste, cosa che richiede un tempo di compilazione

molto elevato. I tecnici Google hanno stimato che, a fronte un file binario di circa 4MB,

Page 16: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

16

espandendo tutti #include, al compilatore giungano circa 8GB di files da analizzare. In

aggiunta a ciò, per i files binari di Mountain View sono necessari grandi sistemi di

compilazione distribuiti, e un tempo medio di 30-45 minuti. [1] Le dipendenze nel

linguaggio Go sono sintatticamente e semanticamente definite dal linguaggio, vengono

dichiarate con la parola chiave import seguita da una stringa costante. Per garantire la

scalabilità ed evitare di compilare codice non necessario, le dipendenze non usate sono

segnalate come errore di compilazione. L’efficienza è invece garantita dal compilatore

poiché all’atto di un import legge il file oggetto della libreria e non il codice sorgente.

Supponendo di avere un programma A che importa B che a sua volta importa C, il

compilatore compila prima C, poi compila B generando il file oggetto, e quando si trova

all’import di B nel programma A semplicemente legge il file oggetto di B, invece di

leggere il file sorgente e ritornare a compilare C. Con questo meccanismo, non originale

ma presente anche in linguaggi come Java, la compilazione risulta quaranta volte più

veloce del linguaggio C, secondo il parere di Rob Pike. [1]

2.2 Compilazione ed esecuzione

Il processo di compilazione è del tutto analogo al C/C++, i file sorgenti .go vengono

compilati generando i file oggetto .o, per poi essere passati al linker che li assembla. [6]

Mediante il comando go build viene lanciato il compilatore, il quale dalla versione 1.5 è

scritto interamente in Go.

Per quanto concerne l’esecuzione di un programma Go, tramite la variabile d’ambiente

GOMAXPROCS si stabilisce il limite dei threads simultanei del sistema operativo che

possono eseguire il codice Go. [7] L’esecuzione di un programma comincia con la

funzione main, la quale non ha parametri di ingresso e di ritorno, ed è necessariamente

dichiarata all’interno del package main. [4]

Anche Go prevede la possibilità di passare argomenti a riga di comando alla funzione

main. Si utilizza la slice Args del package os, ponendo attenzione al fatto che ad os.Args[0]

corrisponde il nome del programma, e solo dall’indice 1 a seguire ci sono gli argomenti

passati. [8]

Page 17: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

17

2.3 Errori, panic e recover

La gestione degli errori, inizialmente del tutto assente, è stata introdotta con un approccio

più leggero, e sacrifica la brevità del codice a vantaggio di una maggiore leggibilità.

Questo tipo di gestione viene sconsigliata dagli autori, i quali premono sull’utilizzo dei

valori multipli di ritorno delle funzioni per segnalare gli errori. Il motivo di questo

atteggiamento è la tendenza dei programmatori ad ignorare gli eventi eccezionali, come in

Java, invece che gestirli appropriatamente. [1] Con la versione 1.0, il linguaggio si

arricchisce dell’interfaccia error così dichiarata: [4]

type error interface {

Error() string

}

Per gli eventi eccezionali, il linguaggio mette a disposizione le funzioni panic e recover,

una sorta di equivalenti al costrutto try-catch del C++, che hanno la seguente firma: [4]

func panic(interface{})

func recover() interface{}

La chiamata alla funzione panic fa terminare immediatamente la funzione che la contiene,

ma vengono eseguite normalmente le funzioni differite dichiarate. Al termine delle

funzioni deferred, il controllo viene restituito al chiamante della funzione contenente il

panic, e così via, cioè ripercorrendo a ritroso la catena dei chiamanti fino a giungere alla

funzione main, la quale provoca la terminazione del programma (stack unwinding). Questa

catena di rilancio dello stato di panico può essere interrotta solo dalla funzione recover,

che ha senso ovviamente solo all’interno di una funzione differita. La funzione recover,

che ha quindi il compito di recuperare lo stato di un programma per evitarne la

terminazione anticipata e riprenderne il controllo, deve gestire la specifica situazione di

errore. Recover restituisce valore nil se l’argomento del panic è nil, cioè se la routine non è

in stato di panico e se essa stessa non è inserita in una funzione differita.

Page 18: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

18

Capitolo 3: Aspetti avanzati

In questo capitolo saranno illustrati gli aspetti avanzati del linguaggio Go.

3.1 Modello di allocazione delle variabili e garbage collector

Il compilatore Go adotta diverse strategie riguardo all’allocazione delle variabili. Nel caso

di variabili locali a una funzione, il compilatore le alloca all’interno dello stack frame della

funzione stessa. Invece, vengono allocate nella zona heap del garbage collector se il

compilatore non può verificare l’esistenza di riferimenti dopo la fine della funzione.

Vengono allocate, poi, nella zona heap se sono di grandi dimensioni. [9]

Se con la gestione degli errori gli ingegneri di Google hanno avuto delle remore, riguardo

al garbage collector non c’è stato alcun dubbio. [1] Il linguaggio Go è dotato quindi di un

garbage collector, che è l’unico modo per liberare la memoria allocata. Nei linguaggi ove

questo non è previsto, come C/C++, i programmatori spendono molte righe di codice, che

andrebbero tenute nascoste, per allocare e liberare la memoria, esponendoli ad errori

comuni. Essendo un linguaggio concorrente, il garbage collector è fondamentale per

evitare comportamenti malevoli fra le esecuzioni concorrenti. Il garbage collector porta

con sé un aumento dell’overhead di sistema, latenza, oltre che una complessità di

implementazione. L’implementazione alla versione corrente, 1.9, è di tipo mark-and-sweep

parallelo. [10]

3.2 Polimorfismo

Il polimorfismo, cioè la capacità di tipi diversi di rispondere in maniera diversa a uno

stesso messaggio, in Go è realizzato grazie al concetto di interfaccia. Riprendendo il

Page 19: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

19

classico esempio del calcolo dell’area per un rettangolo e un cerchio, si può definire

l’interfaccia Shape che contiene il metodo Area() e le struct Rettangolo e Cerchio che

implementano il metodo della Shape. A questo punto basta dichiarare una slice di Shape,

contenente vari rettangoli e cerchi, e chiamare su ogni elemento il metodo Area: così si

assiste al comportamento polimorfico a tempo di esecuzione.

3.3 Gestione dell’I/O

La gestione dell’I/O di Go è, in prima analisi affidata al package fmt. [11] Per la lettura

dell’input da tastiera esistono le seguenti funzioni:

func Scan(a …interface{}) (n int, err error)

func Scanf(format string, a …interface{}) (n int, err error)

func Scanln(a …interface{}) (n int, err error)

che restituiscono il numero di byte letti ed eventuali errori. Nel particolare le funzioni Scan

e Scanln memorizzano in argomenti successivi gli elementi separati dallo spazio, ma la

Scanln si arresta all’arrivo di newline. Queste tre funzioni sono anche disponibili con il

prefisso “F”, cioè Fscan, Fscanf e Fscanln, che hanno bisogno di uno specifico io.Reader.

Per le operazioni di output a video, invece, sono a disposizione le seguenti funzioni:

func Printf(format string, a …interface{}) (n int, err error)

func Println(a …interface{}) (n int, err error)

di cui la prima stampa e va a capo, la seconda segue una formattazione. In realtà, queste

operazioni di scrittura e lettura, si basano sulle variabili Stdin e Stdout del package os. [8]

L’alternativa a tutte queste funzioni è rappresentata dal package bufio, [12] che

implementa l’I/O bufferizzato, con dei wrapper per io.Reader e io.Writer.

La gestione dell’I/O su file è delegata al package os, [8] tramite le consuete funzioni:

func Open(name string) (*File, error)

func (f *File) Close() error

func (f *File) Read(b []byte) (n int, err error)

func (f *File) Write(b []byte) (n int, err error)

Per una gestione avanzata delle operazioni di I/O su file è possibile utilizzare i package

bufio [12] e ioutil. [13]

Page 20: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

20

3.4 Gestione della concorrenza e parallelizzazione

La gestione della concorrenza è uno dei nodi principali del linguaggio di Google. Per

quanto concerne le primitive per la definizione di attività concorrenti, è stato illustrato nel

paragrafo 1.7 l’utilizzo delle goroutines. Per la sincronizzazione e la comunicazione fra le

goroutines, il linguaggio Go prevede, oltre alla classica gestione tramite mutex, l’utilizzo

dei canali.

Questo tipo di approccio è basato su un motto creato in casa Google: “Non comunicare

condividendo la memoria; ma, condividere la memoria comunicando”. [5] Il problema

della comunicazione è stato affrontato dagli ingegneri di Mountain View in base al loro

dominio del problema, principalmente web servers, e si basa sul modello communicating

sequential processes (CSP) di Hoare. [1] Per l’utilizzo dei canali, è necessario distinguere

la casistica di canali bufferizzati o non bufferizzati. Nel caso di un canale bufferizzato,

l’operazione di send avviene prima del completamento della corrispondente receive.

Quindi, un tipo di sincronizzazione fra due goroutines potrebbe essere: la prima modifica

una variabile ed effettua una send sul channel, la seconda usa la stessa variabile dopo la

receive sul canale, così è garantito che la seconda goroutine usi la variabile già modificata

dalla prima. Nel caso, invece, di un canale non bufferizzato è la receive che avviene prima

che la send completi. Riprendendo l’esempio precedente, la prima goroutine modifica una

variabile ed effettua una receive sul canale, e la seconda usa la variabile dopo una send

sullo stesso canale. Più in generale, è possibile modellare un semaforo con un canale

bufferizzato, il numero di elementi del canale rappresenta gli utenti attivi, la capacità del

canale i permessi del semaforo, le operazioni di send e receive rappresentano

rispettivamente l’acquisizione e il rilascio di un permesso sul semaforo. [14]

L’utilizzo del package sync è scoraggiato dai creatori di Go, che premono per l’utilizzo dei

canali. Nel package, comunque, vengono messe a disposizione le primitive per i mutex:

[15]

func (m *Mutex) Lock()

func (m *Mutex) Unlock()

a cui vengono aggiunte le condizioni per il costrutto monitor:

func NewCond(l Locker) *Cond

Page 21: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

21

func (c *Cond) Wait()

func (c *Cond) Signal()

Il linguaggio Go, essendo concorrente, non soddisfa tutti i problemi del modello della

parallelizzazione. Un meccanismo per parallelizzare il codice può essere: dividere un

problema in diverse parti, lanciare tante funzioni quante sono i core della CPU, ricavabili

tramite runtime.NumCPU() [7], e sincronizzare il tutto attraverso un channel che attende

che tutti i tasks abbiano terminato. [5]

3.5 Cenni a rete, socket e design patterns

Nel package net, il linguaggio Go mette a disposizione le funzionalità per il supporto alla

rete, fra le quali UDP e TCP. [16] Fra le funzioni di maggior rilevanza ed uso, possono

essere citate:

func Dial(network, address string) (Conn, error)

func Close() error

func Read(b []byte) (n int, err error)

func Write(b []byte) (n int, err error)

ove come parametro network è lecito indicare “tcp” o “udp”, ad esempio.

Lato server, invece, sono disponibili le seguenti:

func Listen(network, address string) (Listener, error)

func (l *Listener) Accept() (Conn, error)

func (l *Listener) Close() error

Nella programmazione Go possono essere usati anche i design patterns, cioè soluzioni

generali a problemi ricorrenti, definiti dalla Gang of Four. Per lo sviluppo dei singoli

pattern, avendo ben presente l’impossibilità di realizzarli se è prevista l’ereditarietà, si

rimanda a GitHub, al progetto go patterns, che espone le implementazioni specifiche per

delega in Go. [17]

Page 22: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

22

Capitolo 4: Confronto con C++ e Java

In questo capitolo verranno confrontati Go, C++ 2011, e Java in base alle caratteristiche.

C+

+ 2

011

Java

Go

Paradigma di

programmazione

Pro

cedura

le

Fun

zional

e S

ì S

ì S

ì

Gen

eric

a S

ì S

ì N

o, m

a non è

esc

lusa

in

futu

ro [

9]

Object Orientation

Cost

rutt

o

clas

se

No, st

ruct

+ m

etodi

Inca

psu

lam

ento

S

ì, c

on s

pec

ific

atori

di

acce

sso

Sì,

con s

pec

ific

atori

di

acce

sso

Sì,

tra

mit

e up

per

e l

ow

er

case

Ere

dit

arie

No, so

lo c

om

posi

zione

Poli

morf

ism

o

Sì,

tra

mit

e in

terf

acce

Gestione

della

memoria

Punta

tori

S

ì N

o

Sì,

ma

senza

ari

tmet

ica

Gar

bage

coll

ecto

r N

o

Gestione

dell'errore

E

ccez

ioni

+ c

ost

rutt

o

try/c

atch

Ecc

ezio

ni

+ c

ost

rutt

o

try/c

atch

Err

ori

com

e val

ori

di

rito

rno m

ult

ipli

del

le

funzi

oni

+ p

anic

e r

ecover

Concorrenza

Thre

ads

Sì,

le

goro

uti

nes

Mute

x

Sì,

ma

si p

rivil

egia

no i

canal

i

Monit

or

Sì,

ma

si p

rivil

egia

no i

canal

i

Page 23: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

23

Capitolo 5: Applicazioni esistenti

La crescita del linguaggio Go è dovuta, oltre alle caratteristiche peculiari e la semplicità,

sicuramente all’influenza del colosso Google. L’uso del linguaggio nell’azienda di

Mountain View è riservato, ma per alcuni scopi è di dominio pubblico. L’intero web server

del sito ufficiale di Go è scritto nel linguaggio Go; inoltre, nello sviluppo di Google

Fuchsia, cioè un nuovo sistema operativo che dovrebbe essere capace di funzionare su ogni

tipo di dispositivo, è stato adoperato il linguaggio Go in sinergia a C, C++, Rust, Dart,

Python e Swift. Nel panorama mondiale, il linguaggio si sta diffondendo non solo in

aziende che si occupano esplicitamente di software e hardware. Fra le aziende di tipo

informatico sono da citare come esempio: Adobe, Dell, General Electric Software, IBM,

Intel, Docker, DropBox MongoDB, Mozilla, Oracle e VMware, le quali più o meno

esplicitamente hanno dichiarato l’utilizzo del linguaggio di Google. Accanto a queste sono

da menzionare i social network come Facebook, Pinterest, Reddit, Tumblr e Twitter,

nonché nel settore dell’informazione la BBC, The Economist, The New York Times. Il

linguaggio Go ha anche spaziato nel settore entertainment con Mattel, Netflix, Riot Games

e Carbon Games, oltre che nell’e-commerce con Ebay e Zalando. L’impiego di Go è

giunto, inoltre, nella sfera della medicina e dei trasporti con Novartis, Ryanair e la

contestatissima Uber. Queste sono solo alcune delle aziende di portata internazionale che

utilizzano il linguaggio Go, ma esistono anche altre migliaia di piccole aziende locali che

ne fanno impiego.

Page 24: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

24

Capitolo 6: Sviluppo di un programma in Go

Nel seguente capitolo viene sviluppato un programma Go a titolo esemplificativo, per

esporre in maniera ancora più chiara le funzionalità del linguaggio.

6.1 Installazione e ambienti di sviluppo

Nella sezione download del sito ufficiale del linguaggio Go è possibile scaricare i file

binari dell’ultima versione stabile, 1.9.3, per Microsoft Windows, Apple macOS e Linux.

Analogamente, dal sito ufficiale, è possibile scaricare i plugin che sono ora disponibili per

molteplici IDE, quali Visual Studio Code, vim, Atom. Per lo sviluppo del programma del

successivo paragrafo è stato adoperato il plugin GoClipse per Eclipse in ambiente

Windows.

6.2 Esempio di programma in Go

La scelta di un esempio da sviluppare è stata presa considerando la volontà di voler coprire

più caratteristiche possibili trattate del linguaggio Go in chiave puramente didattica. Il

programma è basato sul design pattern Proxy-Skeleton, quindi con una parte client e una

parte server che comunicano attraverso le socket, con l’aggiunta di scrittura su file e

goroutines e channels. In linguaggio pseudo-UML può essere così visto il class diagram

lato server:

Page 25: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

25

Dal lato client, invece, sempre in pseudo-UML:

ove gli Stub e lo Skeleton si occupano della comunicazione su rete.

Nel seguito verranno presentate le righe di codice salienti dei file sorgenti necessari per

questo programma.

L’interfaccia è comune a entrambe le parti, ed è la seguente

type InterfacciaServizi interface {

Invia(num int)

Ricevi() int

}

Iniziando dal lato client nel file client.go è presente la funzione main

func main() {

done := make(chan bool, 10)

for i := 0; i < 10; i++ {

go clientRoutine(i, done)

}

for i := 0; i < 10; i++ {

<-done

}

fmt.Println("Finito!")

}

che genera 10 goroutines e ne attende la terminazione tramite il canale done. La funzione

clientRoutine

func clientRoutine(n int, done chan bool) {

var st Stub

if n%2 == 0 {

val := st.Ricevi()

fmt.Printf("Sono la routine %d e ho ricevuto %d\n", n, val)

} else {

val := rand.Intn(100)

st.Invia(val)

fmt.Printf("Sono la routine %d e ho inviato %d\n", n, val)

}

done <- true

}

se n è pari si comporta da consumatore chiamando la funzione Ricevi dello Stub, altrimenti

genera un numero casuale e chiama la funzione Invia. In entrambi i casi notifica la

terminazione al canale done.

Page 26: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

26

Lo Stub, che implementa le funzioni dell’interfaccia, rende la comunicazione su rete

trasparente al client con l’istanziazione di una socket tcp sul porto scelto casualmente 5423

s.conn, _ = net.Dial("tcp", "127.0.0.1:5423")

defer s.conn.Close()

con l’utilizzo della parola chiave defer per assicurarsi di chiudere la connessione al termine

della funzione chiamante. Interessante, sempre in stub.go, è questo frammento di codice

func (s *Stub) Ricevi() int {

//omissis

val, err := strconv.Atoi(message)

defer postpanico()

if err != nil {

panic(err)

}

return val

}

func postpanico() {

if r := recover(); r != nil {

fmt.Println("Provo a recuperare lo stato")

}

che mostra il comportamento di panic e recover a tempo di esecuzione. Nel caso in cui la

funzione Atoi generi un errore poiché impossibilitata a convertire la stringa message in

intero, la variabile err assume valore diverso da nil e viene lanciato il panico. A tempo di

esecuzione non viene stampato lo stack trace dell’errore, ma viene eseguita la funzione

postpanico in maniera differita, la quale contiene la funzione recover che restituisce un

valore diverso da nil solo se si è verificata una chiamata a panic.

Lato server, invece, è presente il file server.go che contiene semplicemente la funzione

main

func main() {

var s Skeleton

s.RunSkeleton()

}

che istanzia uno Skeleton e ne chiama la funzione RunSkeleton.

Lo Skeleton, al pari dello Stub, si occupa della comunicazione su rete e implementa

InterfacciaServizi. Possiede una variabile privata di serverReale, inoltre si pone in attesa di

connessioni tcp sul medesimo porto 5423 e chiama una differente goroutine ad ogni

richiesta di connessione.

Page 27: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

27

func (s *Skeleton) RunSkeleton() {

//omissis

ln, _ := net.Listen("tcp", "127.0.0.1:5423")

for {

conn, _ := ln.Accept()

go skeletonroutines(s, conn)

}

}

Le goroutines elaborano i messaggi di richiesta chiamando le funzioni Invia e Ricevi

implementate dallo Skeleton e nel secondo caso si occupano anche di costruire ed inviare il

messaggio di risposta. L’implementazione delle funzioni dell’interfaccia prevede solo la

delega alle rispettive funzioni del serverReale.

func (s *Skeleton) Invia(num int) {

s.ser.Invia(num)

}

Nel file serverReale.go è definito il seguente tipo

type serverReale struct {

valore int

prod chan bool

cons chan bool

miofile *os.File

}

che ha come attributi un buffer unitario di tipo int, un puntatore a un file e due canali

bidirezionali utili per accedere in mutua esclusione al buffer. La funzione Inizializza

func (s *serverReale) Inizializza() {

s.valore = 0

s.miofile, _ = os.Create("miofile.txt")

s.prod = make(chan bool, 1)

s.cons = make(chan bool, 1)

s.prod <- true

}

crea un file chiamato miofile.txt, istanzia i due canali bufferizzati di capacità 1 ed invia un

valore true sul canale prod poiché il buffer è inizialmente vuoto e pronto alla produzione.

La funzione Invia si occupa della produzione del valore:

func (s *serverReale) Invia(num int) {

<-s.prod

s1 := "Produco: " + strconv.Itoa(num)

fmt.Println(s1)

s.valore = num

s.miofile.WriteString(s1 + "\r\n")

time.Sleep(2 * time.Second)

s.cons <- true

}

Page 28: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

28

Nel particolare aspetta la ricezione dal canale prod, che indica che il buffer è vuoto,

effettua la produzione, scrive su file e si pone in attesa di 2 secondi. Al termine segnala

tramite invio di true sul canale cons che un valore è stato scritto ed è pronto per essere

consumato.

La funzione Ricevi, invece, si occupa del consumo dal buffer:

func (s *serverReale) Ricevi() int {

<-s.cons

s1 := "Consumo: " + strconv.Itoa(s.valore)

fmt.Println(s1)

val := s.valore

s.miofile.WriteString(s1 + "\r\n")

s.prod <- true

return val

}

La funzione si pone in attesa tramite la ricezione sul canale cons, consuma il valore, scrive

sul file e al termine invia true al canale prod per segnalare che il buffer è vuoto. Per

realizzare la mutua esclusione è stato scelto l’utilizzo dei canali per seguire lo stile di

programmazione Go, così come fortemente spinto dai creatori, a discapito dei mutex del

package sync.

Il codice sorgente integrale è presente in appendice a questo elaborato.

Page 29: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

29

Conclusioni

Le critiche mosse al linguaggio Go corrispondono proprio alle sue caratteristiche, come per

esempio, la mancanza dei tipi generici, l’assenza dell’overloading che lo rende più

prolisso, l’assenza dell’ereditarietà e la presenza del garbage collector che genera pause ed

overhead del sistema.

Gli sviluppatori del linguaggio hanno dichiarato che nessun nuovo linguaggio è nato e si è

affermato nel nuovo millennio. Neppure il quasi decennale Go, però, è riuscito a

pareggiare linguaggi come Java e C++. Google, probabilmente, in un primo momento l’ha

trattato come esperimento, dedicando poco tempo e risorse. Invece avrebbe potuto

investire molto e imporlo, grazie alla propria influenza, a livello mondiale come il

corrispondente Swift per Apple.

Con lo standard di C++ del 2011 poi, il Go, nonostante sia nettamente antecedente, ha

perso il proprio punto di forza, la gestione della concorrenza.

Il linguaggio Go è sicuramente stimolante e genera molta curiosità al riguardo. Ha delle

caratteristiche molto interessanti, frutto di una volontà di fare davvero innovazione da parte

degli sviluppatori. Queste caratteristiche spingono i programmatori a ripensare a come

sviluppare il proprio codice per allinearsi allo stile di Go. Se da un lato questo implica

impegno, poiché non immediato da assimilare, dall’altro Go ha dalla propria parte la

semplicità della sintassi e l’estrema leggibilità del codice.

La scelta di studiare ed usare il linguaggio Go potrà essere utile per il futuro, se Google

deciderà di investire più tempo e risorse, ma viste le centinaia di linguaggi esistenti,

l’impegno da parte di BigG dovrà essere sicuramente maggiore affinché sia davvero

conveniente migrare a questo nuovo linguaggio.

Page 30: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

30

Bibliografia

[1] Go at Google, https://talks.golang.org/2012/splash.article, 11 gennaio 2018

[2] TIOBE Index – The software quality company, https://www.tiobe.com/tiobe-index/,

12 gennaio 2018

[3] The Go Programming Language – Release History,

https://golang.org/doc/devel/release.html, 11 gennaio 2018

[4] The Go Programming Language Specification, https://golang.org/ref/spec, 13

gennaio 2018

[5] The Go Programming Language – Effective Go,

https://golang.org/doc/effective_go.html, 13 gennaio 2018

[6] The Go Programming Language – Command compile,

https://golang.org/cmd/compile/, 18 gennaio 2018

[7] The Go Programming Language – Package Runtime, https://golang.org/pkg/runtime/,

18 gennaio 2018

[8] The Go Programming Language – Package os, https://golang.org/pkg/os/, 18 gennaio

2018

[9] The Go Programming Language – Frequently Asked Questions (FAQ),

https://golang.org/doc/faq, 27 gennaio 2018

[10] Go 1.4+ GC Plan and Roadmap,

https://docs.google.com/document/d/16Y4IsnNRCN43Mx0NZc5YXZLovrHvvLhK_h0K

N8woTO4/edit, 21 gennaio 2018

[11] The Go Programming Language – Package fmt, https://golang.org/pkg/fmt/, 24

gennaio 2018

Page 31: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

31

[12] The Go Programming Language – Package bufio, https://golang.org/pkg/bufio/, 24

gennaio 2018

[13] The Go Programming Language – Package ioutil, https://golang.org/pkg/io/ioutil/, 24

gennaio 2018

[14] The Go Programming Language – The Go Memory Model,

https://golang.org/ref/mem, 27 gennaio 2018

[15] The Go Programming Language – Package sync, https://golang.org/pkg/sync/, 27

gennaio 2018

[16] The Go Programming Language – Package net, https://golang.org/pkg/net/, 29

gennaio 2018

[17] GitHub tmrts/go-patterns, https://github.com/tmrts/go-patterns, 29 gennaio 2018

Page 32: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

32

Appendice

In questa appendice è riportato integralmente il codice sorgente sviluppato come esempio

nel paragrafo 6.2.

File client.go

package main

import (

"fmt"

"math/rand"

)

func main() {

done := make(chan bool, 10)

for i := 0; i < 10; i++ {

go clientRoutine(i, done)

}

for i := 0; i < 10; i++ {

<-done

}

fmt.Println("Finito!")

}

func clientRoutine(n int, done chan bool) {

var st Stub

if n%2 == 0 {

val := st.Ricevi()

fmt.Printf("Sono la routine %d e ho ricevuto %d\n", n, val)

} else {

val := rand.Intn(100)

st.Invia(val)

fmt.Printf("Sono la routine %d e ho inviato %d\n", n, val)

}

done <- true

}

Page 33: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

33

File stub.go

package main

import (

"fmt"

"net"

"strconv"

"strings"

)

type InterfacciaServizi interface {

Invia(num int)

Ricevi() int

}

type Stub struct {

conn net.Conn

}

func (s *Stub) Invia(num int) {

s.conn, _ = net.Dial("tcp", "127.0.0.1:5423")

defer s.conn.Close()

s1 := "PUT_" + strconv.Itoa(num)

s.conn.Write([]byte(s1 + "\n"))

}

func (s *Stub) Ricevi() int {

s.conn, _ = net.Dial("tcp", "127.0.0.1:5423")

defer s.conn.Close()

s1 := "GET"

s.conn.Write([]byte(s1 + "\n"))

b := make([]byte, 20)

n, _ := s.conn.Read(b)

message := string(b[:n])

message = strings.TrimSuffix(message, "\n")

val, err := strconv.Atoi(message)

defer postpanico()

if err != nil {

panic(err)

}

return val

}

func postpanico() {

if r := recover(); r != nil {

fmt.Println("Provo a recuperare lo stato")

}

}

File server.go

package main

func main() {

var s Skeleton

s.RunSkeleton()

}

Page 34: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

34

File skeleton.go

package main

import (

"net"

"strconv"

"strings"

)

type InterfacciaServizi interface {

Invia(num int)

Ricevi() int

}

type Skeleton struct {

ser serverReale

}

func (s *Skeleton) RunSkeleton() {

s.ser.Inizializza()

ln, _ := net.Listen("tcp", "127.0.0.1:5423")

for {

conn, _ := ln.Accept()

go skeletonroutines(s, conn)

}

}

func (s *Skeleton) Invia(num int) {

s.ser.Invia(num)

}

func (s *Skeleton) Ricevi() int {

return s.ser.Ricevi()

}

func skeletonroutines(s *Skeleton, conn net.Conn) {

b := make([]byte, 20)

n, _ := conn.Read(b)

message := string(b[:n])

message = strings.TrimSuffix(message, "\n")

if message == "GET" {

val := s.Ricevi()

risposta := strconv.Itoa(val)

conn.Write([]byte(risposta + "\n"))

} else if strings.Contains(message, "PUT_") {

s1 := strings.Replace(message, "PUT_", "", 1)

num, _ := strconv.Atoi(s1)

s.Invia(num)

}

}

Page 35: Il linguaggio di programmazione Go di Google · ... 12 1.6 Metodi e interfacce ... sistemi operativi e programmazione. ... Altra introduzione sono gli operatori == e != per le struct

35

File serverReale.go

package main

import (

"fmt"

"os"

"strconv"

"time"

)

type serverReale struct {

valore int

prod chan bool

cons chan bool

miofile *os.File

}

func (s *serverReale) Inizializza() {

s.valore = 0

s.miofile, _ = os.Create("miofile.txt")

s.prod = make(chan bool, 1)

s.cons = make(chan bool, 1)

s.prod <- true

}

func (s *serverReale) Invia(num int) {

<-s.prod

s1 := "Produco: " + strconv.Itoa(num)

fmt.Println(s1)

s.valore = num

s.miofile.WriteString(s1 + "\r\n")

time.Sleep(2 * time.Second)

s.cons <- true

}

func (s *serverReale) Ricevi() int {

<-s.cons

s1 := "Consumo: " + strconv.Itoa(s.valore)

fmt.Println(s1)

val := s.valore

s.miofile.WriteString(s1 + "\r\n")

s.prod <- true

return val

}