Por Jorge Fuertes Alfranca AKA Queru.
Libre distribución con licencia GPL v3 o superior.
- Beneficios de usar Go frente a otros lenguajes
- ¿Qué tipos de datos usa Go?
- ¿Qué tipo de conversión soporta Go?
- ¿Qué es una gorutina y como la puedes detener?
- ¿Cómo podemos comprobar el tipo de una variable en tiempo de ejecución?
- Concatenar strings
- ¿Qué son las interfaces y cómo funcionan?
- ¿Se pueden devolver múltiples valores desde una función?
- Formatear una cadena sin imprimirla
- Diferencias entre concurrencia y paralelismo en Go
- ¿Go soporta excepciones?
- ¿Qué es un puntero?
- Diferencias entre un canal sin buffer y otro con buffer
- Valor por defecto de un boleano
- ¿Qué es el type assertion o aserción de tipos?
- ¿Cuales de los siguientes son tipos derivados en Go?
- ¿Qué es cierto acerca de un bucle for en Go?
- ¿Existe un bucle while en Go?
- ¿Cual de las siguientes variables es una cadena en Go?
- ¿Cual de las siguientes funciones retorna la capacidad de un slice y cuantos elementos puede alojar?
- Afirmaciones correctas sobre interfaces
- ¿Cual es el zero value de un
interface
? - Afirmaciones correctas sobre estructuras
- Qué tipos de datos permiten agrupar o combinar posibles diferentes tipos en uno solo
- Todas las gorutinas terminan cuando termina la función
main
- Cómo se declara una variable anónima en Go
- Valor por defecto de
error
- Método usado para escribir datos a un fichero
- Método para crear un fichero
- Go se diseño de forma pragmática, no es un experimento académico. Cada característica y decisión sobre su sintáxis se pensó para hacer más fácil la vida del programador.
- Es más amigable para el programador que C++ o Java.
- Go está optimizado para concurrencia y escala muy bien.
- Se lee mejor que otros lenguajes gracias a que tiene un formato estándar de código.
- El recolector de basura es mucho más eficiente, por diseño y porque funciona concurrente con el resto del programa.
- Method (método)
- Boolean (boleano)
- Numeric (numérico)
- String (cadena)
- Array (matríz)
- Slice (matríz dinámica)
- Pointer (puntero)
- Function (función)
- Interface (interfaz)
- Channel (canal)
Conversión explícita para satisfacer los requisitos del tipado estricto o tipado duro (strong typing).
i := 55 // int
j := 60.1 // float64
sum1 := float(i) + j
Es una función o un método que se ejecuta concurrentemente con otras gorutinas usando un hilo especial para gorutina. Los hilos de gorutinas son mucho más ligeros que un hilo estándar, y muchos programas Go utilizan cientos de gorutinas.
channel
+------------+ send receive +------------+
| | ----------------> | |
| gorutina 1 | receive send | gorutina 2 |
| | <---------------- | |
+------------+ +------------+
Para crear una gorutina usamos la palabra reservada go
antes de la llamada la función:
package main
import (
"fmt"
"time"
)
func main() {
start := time.Now()
go func() {
for i := 0; i < 3; i++ {
## ¿Qué es una gorutina y como la puedes detener?
Es una función o un método que se ejecuta concurrentemente con otras _gorutinas_ usando un hilo especial para _gorutina_. Los hilos de _gorutinas_ son mucho más ligeros que un hilo estándar, y muchos programas __Go__ utilizan cientos de _gorutinas_.
```text
channel
+------------+ send receive +------------+
| | ----------------> | |
| gorutina 1 | receive send | gorutina 2 |
| | <---------------- | |
+------------+ +------------+
Para crear una gorutina usamos la palabra reservada go
antes de la llamada la función:
package main
import (
"fmt"
"time"
)
func main() {
start := time.Now()
go func() {
for i := 0; i < 3; i++ {
## ¿Qué es una gorutina y como la puedes detener?
Es una función o un método que se ejecuta concurrentemente con otras _gorutinas_ usando un hilo especial para _gorutina_. Los hilos de _gorutinas_ son mucho más ligeros que un hilo estándar, y muchos programas __Go__ utilizan cientos de _gorutinas_.
```text
channel
+------------+ send receive +------------+
| | ----------------> | |
| gorutina 1 | receive send | gorutina 2 |
| | <---------------- | |
+------------+ +------------+
Para crear una gorutina usamos la palabra reservada go
antes de la llamada la función:
package main
import (
"fmt"
"time"
)
func main() {
start := time.Now()
go func() {
for i := 0; i < 3; i++ {
## ¿Qué es una gorutina y como la puedes detener?
Es una función o un método que se ejecuta concurrentemente con otras _gorutinas_ usando un hilo especial para _gorutina_. Los hilos de _gorutinas_ son mucho más ligeros que un hilo estándar, y muchos programas __Go__ utilizan cientos de _gorutinas_.
```text
channel
+------------+ send receive +------------+
| | ----------------> | |
| gorutina 1 | receive send | gorutina 2 |
| | <---------------- | |
+------------+ +------------+
Para crear una gorutina usamos la palabra reservada go
antes de la llamada la función:
package main
import (
"fmt"
"time"
)
func main() {
start := time.Now()
go func() {
for i := 0; i < 3; i++ {
fmt.Println(i)
}
}()
}
Podemos detener una gorutina enviando una señal a su canal. Las gorutinas sólo responden a señales si están programadas para comprobarlas, así que tenemos que incluir comprobaciones en los puntos correctos, como en la parte superior del bucle for
:
package main
import (
"fmt"
"time"
)
func main() {
start := time.Now()
quit := make(chan bool)
go func() {
for i := 0; i < 3; i++ {
select {
case <- quit:
return
}
fmt.Println(i)
}
}()
// cerrar la gorutina
quit <- true
}
Por otro lado esta gorutina se detendrá al terminar el bucle for
o bien cuando termine todo el programa, ya que todas las gorutinas serán detenidas.
Otro ejemplo:
package main
import "sync"
func main() {
var wg sync.WaitGroup
wg.Add(1)
ch := make(chan int)
go func() {
for {
foo, ok := <- ch
if !ok {
println("done")
wg.Done()
return
}
println(foo)
}
}()
ch <- 1
ch <- 2
ch <- 3
close(ch)
wg.Wait()
}
Otro, con struct
:
package main
import "fmt"
import "time"
func do_stuff() int {
return 1
}
func main() {
ch := make(chan int, 100)
done := make(chan struct{})
go func() {
for {
select {
case ch <- do_stuff():
case <-done:
close(ch)
return
}
time.Sleep(100 * time.Millisecond)
}
}()
go func() {
time.Sleep(3 * time.Second)
done <- struct{}{}
}()
for i := range ch {
fmt.Println("receive value: ", i)
}
fmt.Println("finish")
}
Por último podríamos detener la gorutina utilizando un contexto:
package main
import (
"context"
"fmt"
"time"
)
func main() {
forever := make(chan struct{})
ctx, cancel := context.WithCancel(context.Background())
go func(ctx context.Context) {
for {
select {
case <-ctx.Done(): // if cancel() execute
forever <- struct{}{}
return
default:
fmt.Println("for loop")
}
time.Sleep(500 * time.Millisecond)
}
}(ctx)
go func() {
time.Sleep(3 * time.Second)
cancel()
}()
<-forever
fmt.Println("finish")
}
El mejor sistema es el type switching, que evalúa la variable en tiempo de ejecución:
func (e *Easy) SetCode(param interface{}) {
switch v := param.(type) {
case int:
e.code = param
case string:
...
}
}
Se pueden concatenar con +
:
v := "uno" + ", " + "dos"
Con join
si vienen en un array
:
strs := []string{"uno", "dos", "tres"}
fmt.Println(strings.Join(strs, ", "))
O con fmt
:
fmt.Printf("%s, %s", "uno", "dos")
Son un tipo especial en Go que define un conjunto de métodos pero que no provee de su implementación, es decir, describimos la firma de los métodos. Los valores de tipo interface
pueden sustituirse por cualquier valor que implemente ese conjunto de métodos.
Básicamente son huecos a medida que pueden ser cubiertos por múltiples implementaciones de objetos que encajen en él, que tengan implementados todos los métodos que están requeridos en el interface, pueden tener más métodos, pero no menos.
Sí. Se pueden devolver múltiples valores separados por comas en la declaración de retorno return
.
name := "Jorge"
finalStr := fmt.Sprintf("Hola, %s", name)
La concurrencia consiste en manejar el tiempo de cada proceso para ir dándoles salida "a la vez", esto puede suceder en uno o más cores de procesador, pero es Go quién se ocupa de ellos, y el scheduler
va pausando y retomando cada gorutina concurrente cuando procede.
La concurrencia es una propiedad que permite tener múltiples tareas en progreso pero no necesariamente ejecutándose al mismo tiempo.
La clave de la concurrencia en Go son las gorutinas y los canales.
El paralelismo consiste en ejecutar a la vez y eso siempre debe suceder en distintos procesadores o distintos cores, utilizando hardware diferente para cada proceso. Habrá dos o más tareas ejecutándose al mismo tiempo al utilizar hardware diferente. Go provee de un mecanismo de concurrencia no de paralelismo, sin embargo es capaz de manejar los cores de CPU disponibles y utilizar una especie paralelismo de forma transparente, siempre que eso sea posible dentro de nuestro programa. En cualquier caso, en Go, siempre hablaremos de concurrencia.
Hay que tener en cuenta que aunque un procesador disponga de varios cores, estos no son procesadores completos, sino que comparten algunos recursos como caché de nivel dos y otros espacios compartidos.
Hay un muy buen vídeo explicativo de Manuel y Antonio Rubio titulado No, tu PC no será más rapido. En este mismo vídeo nos recomiendan una charla de Rob Pike titulada Concurrencia no es paralelismo.
No. Go utiliza una aproximación diferente, aprovechando el retorno multivalor es fácil comunicar un error sin sobrecargar el valor de retorno, así que se utiliza el valor de error para señalar un estado anormal.
En una declaración de retorno se puede devolver un valor tipo error
además de el valor que se espera y si este error es nulo se sabrá que la rutina se ha ejecutado sin errores y el valor devuelto es correcto y usable:
func formatHello(name string) (string, error) {
if len(name) < 2 {
return "", errors.New("too short name")
}
return fmt.Sprintf("Hello, %s.", name), nil
}
Por convención el error
se devuelve en último lugar.
Un puntero o apuntador es una variable que contiene la dirección del valor de otra variable.
a := 1
b := &a // b es un puntero de a
Si hacemos modificaciones sobre el valor de un puntero, en realidad estamos haciendo modificaciones sobre el valor de la variable a cuya dirección apunta. Por tanto si cambiamos b
estaremos cambiando a
.
No siempre, debido al recolector de basura. En general se pueden copiar objetos por el stack si están en el mismo ámbito, la misma gorutina en definitiva, pero si pasamos la referencia hacemos que dicho objeto escape de ese ámbito, y por tanto tendrá que ir al heap para que el recolector de basura haga su magia más tarde.
En general, si mantenemos variables de ámbito local, y las pasamos por copia, el compilador puede hacer un buen trabajo determinando el ámbito de vida de dichas variables, asignando y destruyendo el espacio necesario para cada una de ellas.
Sim embargo, si las variables escapan de su ámbito, escapan también de la pila (stack), y el compilador no puede determinar cuando destruirlas, por tanto pasan a ser responsabilidad del recolector de basura, que determinará su destrucción en tiempo de ejecución.
go build -gcflags=-m=3 [package]
Un grafo complejo de objetos y punteros limita el paralelismo y genera más trabajo al recolector de basura. El recolector contiene algunas optimizaciones para las estructuras más comunes. Desde el punto de vista de la efectividad debemos tener en cuenta:
- Los valores libres de punteros son segregados de los otros valores.
- Puede ser ventajoso eliminar los punteros de las estructuras de datos que no los necesiten, esto reduce la presión sobre el caché del recolector del programa. Las estructuras de datos que se basen en índices sobre valores de punteros, aunque no tengan un tipado tan correcto, pueden funcionar mejor.
- El recolector detiene el escaneo al llegar al último puntero en el valor.
- Podría ser ventajoso agrupar los campos de puntero al principio del valor.
- En teoría esto lo hará el compilador de forma automática en algún momento, aunque no está implementado a la hora de escribir esto.
No es necesario meterse en este tipo de optimizaciones si el grafo de objetos no es complejo y si el recolector no está empleando mucho tiempo en marcar y escanear estos objetos.
Si no se tiene un problema de desempeño, y no se observa una sobrecarga del recolector de basura, no es necesario complicarse con este tipo de optimizaciones y deberíamos centrarnos en la claridad del código escrito y en su efectividad. Mayor claridad y menos líneas de código son un beneficio directo para el proyecto.
El emisor queda bloqueado al enviar hasta que el receptor reciba el dato por el canal, mientras el receptor también queda bloqueado hasta que el emisor envíe algo al canal.
El emisor sólo queda bloqueado si no hay espacio libre en el canal, mientras que el receptor se bloquea en la recepción si el canal está vacío.
El valor por defecto siempre es false
.
Un type assertion toma el valor de una interface y recupera el valor del tipo explícito especificado. Se utiliza type conversion para convertir tipos diferentes en Go.
Tipo | Respuesta |
---|---|
Interface types | 🔳 |
Map types | 🔳 |
Channel types | 🔳 |
Todos los anteriores | ✅ |
Afirmación | Respuesta |
---|---|
Si se ha definido una condición, el bucle se ejecuta mientras se siga cumpliendo dicha condición | 🔳 |
Si se ha definido un range , el bucle se ejecuta por cada objeto del rango. |
🔳 |
Ambas | ✅ |
Ninguna | 🔳 |
NO existe como tal, aunque se puede simular facilmente con un for
.
Definición | Respuesta |
---|---|
x := 10 |
🔳 |
x := "10" |
✅ |
Ninguna de las anteriores | 🔳 |
Todas las anteriores | 🔳 |
¿Cual de las siguientes funciones retorna la capacidad de un slice y cuantos elementos puede alojar?
Función | Respuesta |
---|---|
size() |
🔳 |
len() |
🔳 |
cap() |
✅ |
Ninguna | 🔳 |
Afirmación | Respuesta |
---|---|
Hay un tipo conocido como interface que representa un conjunto de firmas de métodos. |
🔳 |
El tipo struct implementa estos interfaces al tener las definiciones de los métodos de la firma de esos interfaces. |
🔳 |
Ambas | ✅ |
Ninguna | 🔳 |
Valor | Respuesta |
---|---|
0 | 🔳 |
1 | 🔳 |
Nil | ✅ |
Ninguno | 🔳 |
Afirmación | Respuesta |
---|---|
Para acceder a un miembro de la estructura, usamos el operador de acceso (.) . |
🔳 |
Se usa la palabra reservada struct para definir variables de tipo estructura. |
🔳 |
Se puede pasar una estructura como un argumento a una función de forma similar a como pasarías cualquier otra variable o puntero. | 🔳 |
Todas | ✅ |
Tipo | Respuesta |
---|---|
interface |
🔳 |
channel |
🔳 |
struct |
✅ |
Ninguno | 🔳 |
Todos | 🔳 |
✅ Correcto.
De hecho la función main
también es conocida como main gorutine
, y cuando esta termina, terminan todas las demás ya que dependen de ella.
Declaración | Respuesta |
---|---|
@ |
🔳 |
_ |
✅ |
* |
🔳 |
# |
🔳 |
El valor por defecto de un tipo error
es nil
.
ioutil.WriteFile()
os.Create()