Notes on the Go Programming Langugae
Some notes on the Go programming language that I took by watching youtube videos, reading / skimming through a textbook, and reading a README on github. Just an introduction to the language. I want to learn Go because the things that I want to implement in future projects cannot be implemented with the quality that I want with a Node.js backend. As my first project with Go, I plan to try to convert the backend for this website to Go.
Go Notes
Go Notes Using Youtube
I'm using this youtube video to lean about GO.
Go Introduction
- Statically types language
- Strongly typed (type of operation you can perform on a variable depends on its type)
- GO is Compiled (much faster)
- Compilation time itself is very fast
- Go has built in concurrency (Goroutines)
- Simplicity
- Has garbage collection
Setting Up Project
- Modules
- collection of packages
- Initialize go project with go mod init [GITHUB REPOSITORY NAME]
- packages
- folder that contains go files
- go build [PATH_TO_FILE] => compile the go file
- go run [PATH_TO_FILE] => compile and run the files
Constants Variables and Data Types
- int, int8, int16, int32, int64, uint (only saves positive integers)
- int will default to 32 or 64 buts
- float32, float64
- You cant perform operations with mixed types (but you can cast types)
- Integer division is floor division
- rune = C char
- bool = True|False
- Default values for all number is 0, for strings it is an empty string, for booleans it is False
- variables get the default value when value is not assigned to them on declaration
- can't change the value of constants after they are created
- can't initialize constants without a value
- error: default value is nil
Functions and Control Structures
- funcs, if statements, switch statements
- Something to Note: many functions may return an error from the errors module
Arrays, Slices, Maps, Loops
- Arrays: fixed length of data of the same type, indexable, contiguous in memory
- Default values of arrays in the default value o the array type
- Slices wrap arrays to give a more general, powerful, and convenient interface to sequences of data
- The cap() function gives you the capacity of the array
- The len() function gives you the length of the array
- You can use the make() function to make an array with a given type, length, and capacity
- The map type is like a hashmap. If you reference a key that does not exist in the map type, you get back the default type for that map
- When referencing a map, the map also returns a second value for whether or not that value exists in the map
- To delete a value from a map, use Go's built in delete function, whether the first value is the map and the second value is the key
- Iterate over values in a map and array using the range function
- When iterating over arrays, you get the index and the value for each index/value in the array
- When iterating over maps, you get the key and value for each key/value in the map
- while loops in GO use the for keyword
var i int = 0
for i < 10 {
fmt.Println(i)
i = i+1
}
- You can also use this familiar syntax for for loops:
for i:=0; i <10; i++ {
fmt.Println(i)
}
- You can use decrement / increment syntax (i++, i--, i+=, i-=, i*=, i/=)
- Not allocating capacity for arrays can greatly increase the times that operations occur
Strings
- When you're dealing with strings in GO, you are dealing with a value whose underlying representation is an array of bytes
- In easier way to index and iterating over strings is to cast them to an array of runes rather than dealing with the underlying byte array of a string
- Runes are just unicode point numbers that represent the character. Runes are just an alias for int32. RUnes are denoted by single characters inside single quotes (') as well.
- Strings are immutable in GO
- Can use the built in strings package in GO
Structs and Interfaces
- Structs are like defining your own type
- Interfaces are useful when you don't need a specific type for a function or operation, but only need a type with a certain property / method
Pointers and How They are Used in GO
- Pointers are a special type that store memory locations rather than values
- The new() function returns a free memory location
- Pointers can be useful in functions so that you can use less data or if you want to change the values of the data passed into the function
Go Routines
- A way to have multiple functions execute concurrently
- Concurrency means that you have multiple tasks in progress at the same time
- Can improve code speed
- Use the go keyword before a function call to run a go routine
- go routines spawn a process but don't wait for the goRoutines to finish
- You can use wait groups
- Writing to the same memory location in go routines can cause problems
- You can use var mutex = sync.Mutex()|sync.RWMutex() to solve this problem with mutex.Lock() and mutex.Unlock()
- GoRoutines depend on the number of CPU cores
- If your function is computationally expensive, then the performance improvement with go routines is a function of the cores in the CPU
- If the function is waiting for a dbResponse, then the goRoutines will take about the same time as any single request
Channels And Go Routines
- Channels are a way for go routines to pass around information
- Channels:
- hold data
- are thread safe
- can listen for data
- Make channel with the chan keyword
Generics
- Allow you to define functions that take a variety of types
func genericSumSlice[T int | float32 | float64](slice []T) T {
var sum T
for _, v := range slice {
sum+=v
}
return sum
}
- You can use the any type in certain situations as well
General
- You have to use all imports and variables that you declare
- The built in len() function counts the number of utf-8 characters, not the number of characters
- GO represents strings with utf-8 encoding
- Adding the types when you can is good practice
- Run go mod tidy to install external packages
The Go Programming Language, Alan Donovan, Brian W. Kernighan
- Go is an open source programming language that makes it easy to build simple, reliable, and efficient software
- Go has automatic memory management or garbage collection
Go is especially well suited for building infrastructure like networked servers, and tools and systems for programmers, but it is a general purpose language and finds use in domains as diverse as graphics, mobile applications, and machine learning
- Communicating Sequential Processing (CSP): In CSP, a program is a parallel composition of processes that have no shared state; the processes communicate and synchronize using channels.
- Go values its Simplicity
- The variable size stacks of Go's lightweight threads or goroutines are initially small enough that creating one goroutine is cheap and creating a million is practical.
- Best Way to Find Information on Go is the Website
- Go Playground
Chapter 1: Tutorial
- go run compiles the source code from one or more source files whose name end in .go, links it with libraries, then runs the resulting executable file
- go build: compiles it once and saves the compiled result for later use
- Go code is organized into packages, which are similar to libraries or modules in other languages. A package consists of one or more .go source files in a single directory that define what the package does.
- Each source file begins with a package declaration, that states which package the file belongs to, followed by a list of other packages that it imports, and then the declarations of the program are stored in that file.
- Go standard library has over 100 packages
- The main package is special, and the func main inside the main package is also special, this function runs when the program begins
- A program will not compile if there are missing or unused imports
- The import declaration must follow the package declaration
- A function declaration consists of the keyword func followed by the name of the function, a parameter list, a result list, and the body of the function enclosed in curly braces.
- The gofmt tool rewrites code into the standard format, and the go tool's fmt subcommand applies gofmt to all the files in the specified package, or the ones in the current directory by default
- A related tool goimports additionally manages the insertion and removal of import declarations as needed.
- Command line arguments => os package, os.Args
- Comments begin with //. By convention, we describe the package after the package declaration (unless it is main package - then we describe whole program)
- Short Variable declaration - a statement that declares one or more variables an gives them appropriate types based on the initializer values.
- for loop is th only loop statement in go. With the form:
for initialization; condition; post {
// zero or more statements
}
- The initialization statement is executed before the loop starts. If it is present it must be a simple statement - a short variable declaration, an increment or asignment statement, or a function call.
- The condition is evaluated at the beginning of each loop. Id it evaluated to true, the statements controlled by the lop ate executed.
- The post statement is executed after the body of the loop, and the condition is evaluated again.
- Go does not permit unused local variables, so a solution to a problem where the syntax required a variable name but program logic does not is to use the blank identifier _
- Short variable declaration can only be used within functions.
- In practice, use one of the following forms to declare variables:
s := ""
var s string
- Parentheses are never used around the condition in an if statement
- A map holds a set of key/value pairs and provides constant-time operations to store, retrieve, or test for an item in the set
- strings package for string operations (such as join, split)
- bufio package for reading input and output
- os package to get stdin, stdout, read files
- A map is a reference to a data structure created by make. When a map is passed to a function, the function receives a copy of the reference, so any changes the called function makes to the underlying data stricture will be visible through the caller's map reference too.
- After importing a package whose path has multiple components, like image/color, we refer to the package with a name that comes from the last component
- Composite Literals: a compact notation for instantiating any of Go's composite types from a sequence of element values
- A struct is a group of values called fields, often of different types, that are collected together in a single object that can be treated as a unit.
- A goroutine is a concurrent function execution. A channel is communication mechanism that allows one goroutine to pass values opf a specified type to another goroutine.
Behind the scenes, the server runs the handler for each incoming request in a seperate goroutine so that it can serve multiple requests simultaneously
- Control Flow
- Switch Statements:
- Cases do not fall through like in other languages
- A switch does not need an operand; it can just list the cases, each of which is a boolean expression
- break and continue are included in go
- Names Types
- A type declaration makes it possible to give a name to an existing type. Structs are nearly always named.
- Pointers - Go provides pointers, values that contain the address of a variable. Pointers are explicitly visible in go (use the & operator to get the address of a variable and the * operator to retrieve the variable that the pointer refers to), but there is no pointer arithmetic
- Methods and Interfaces
- A method is a function associated with a named type. Methods can be attached to almost any type. Interfaces are abstract types that let us treat different concrete types in the same way based on what methods they have
- Packages
- Search packages on the go website
- Comments
- Good behavior to write a comment before the declaration of each function. go has single and multi line comments
Chapter 2: Program Structure
- The names of Go functions, variables, constants, types, statement labels, and packages follow a simple rule: a name begins with a letter or an underscore and may have additional letters, digits, and underscores
- Go has 25 *keywords that may be used only where syntax permits:
- break,case,chan``const,continue,default,defer,else,fallthrough,for,func,go,goto,if,import,interface,map,package,range,return,select,struct,switch,type,var
- There are three dozed predeclared names like int and true for built in constants, types, and functions:
- Constants: true,false,iota,nil
- Types: int,int8,int16,int32,int64,uint,unit8,uint16,unit32,uint64,uintptr,float32,float64,complex128,complex64,bool,byte,rune,string,error
- Functions: make,len,cap,new,append,copy,close,delete,complex,real,imag,panic,recover
- These names are not reserved, so you may use them in declarations
- If an entity is declared within a function, it is local to that dunction. If declared outside of a function, however, it is visible in all files of the package to which it belongs
- If a name negins with an upper-case letter, it is exported, which means that it is visible and accessible outside of its own package and may be referred to by other parts of the program (as with Printf of the fmt package). Package names themselves are always lower case.
- use camel case when naming variables
Declarations
- A declaration is a program entity and specifies some or all of its properties
- Four major types of declarations: var, const, type, and func
- Each file starts with a package declaration that says which package the file is a part of
- Package declaration followed by import declarations, and then a sequence of package-level declarations of types, variables, constants, and functions
- The name of each package level entity is visible not only throughout the source file that contains the declaration, but throughout all the files of the package.
- A function declaration has a name, a list of parameters, an optional list of results (skip if doesn't return anything), and the function body
Variables
- A var declaration creates a variable of a particular type, attaches a name to it, and sets its initial value. Each declaration has the general form:
var name = type expression
- Either the type or the = expression part may be omitted, but not both. If the type is omitted, it is determined by the initializer expression. If th eexpression is omitted, the onitial value is the zero value for the type, which is 0 for numbers, false for booleans, "" for strings, and nil for interfaces and reference types (slice, pointer, map, chanel, function). The zero type for an aggregate type like array or struct has the zero value of all its elements or fields.
Short Variable Declarations
- Can be used inside Functions
- Used to declare and initialize the majority of local variables
- Keep in mind that := is a declaration, where as = is an assignment
- A short variable declaration does not necessarily declare all the varaibles on its left hand side. If some of them were already declared in the same lexical block, then the short variable assignment acts like an assignment to those variables.
- A short variable declaration must declare at least one new variable
Pointers
- A variable is a piece of storage containing a value. A pointer value is the address of a variable. A pointer is thus the location at which a value is stored. Not every value has an address, but every variable does. With a variable, we can read or update the value of a variable indirectly without using or even knowing the name of the variable.
x := 1
p := &x // p, of type *int, points to x
fmt.Println(*p) // "l"
*p = 2 // equivalent to x = 2
fmt.Println(x) // "2"
- The zero value for a pointer of any type is nil. Two points are qual if and only if they point to the same variable or both are nil.
- Pointers are key to the flag package, which uses a program's command line arguments to set the values of certain variables distributed throughout the program
- The expression new(T) creates an unnamed variable f type T, initializes it to the zero value of T, and returns its address, which is a value of type *T
- The new function is rarely used
Lifetime of Variables
- The lifetime of a variable is the interval of time during which it exists as the program executes. The lifetime of a package level variable is the entire execution of the program. Local variables have a dynamic lifetime - they are created each time the declaration statement is executed and the variable lives on until it becomes unreachable.
Assignments
- The value held by the variable is updated by an assignment statement, which in its simplest form has a variable on the left of the equal sign, and the expression of the right
- Tuple Assignment - several variables can be declared at once
func example() {
x := 1
y := 2
k := 3
// Tuple Assignment
x, y, k = y, x, 5
}
Assignability
- Assignment, explicitly or implicit, is always legal if the left side and the right side have the same type.
Type Declarations
- The type of a variable or expression defines the characteristics of the values it may take on, such as the size, how they are represented internally, the intrinsic operations that can be performed on them, and the methods associated with them
- A type declaration defined a new named type that has the same underlying type as an existing type. The named type provides a way to separate different and perhaps incompatible uses of the underling type so that they can't be mixed unintentionally.
- For every type T, there is a corresponding conversion operation T(x) that converts the value x to type T.
Packages and Files
- Packages in Go serve the same purpose as libraries or modules in other languages, supporting modularity, encapsulation, separate compilation, and reuse.
- Each package serves as a separate name space for its declarations.
- Identifiers in a package that start with an upper case letter are exported
- A packages name matches the last segment of its import path
- strconv package for converting strings
- Any file may contain an init() function that is automatically executed when the program starts, but they cant be called or referenced
Scope
- The scope of a declaration is the part of the source code where a use of the declared name refers to that declaration
- A syntactic block is a sequence of statements enclosed in braces like those that surround the body of a function or loop. The block encloses its declarations and determines their scope
- There is a lexical block for the entire source code, called the universe block
- For each package
- For each file
- For each for, if, switch, for each case in a switch or select statement
- SHort variable declarations demand awareness of scope.
var cwd string;
func init() {
// Short variable declaration declares local variable cwd, does not assign to package level variable cwd
cwd, err := os.Getwd() // Compile error: unused: cwd
if err != nil {
log.Fatalf("od.Getwd failed: %v",err)
}
}
Chapter 3: Basic Data Types
- Computers operate fundamentally on fixed-size numbers called words
- Go's types fall into four categories basic types, aggregate types, reference types, and interface types
- Basic types include numbers, strings, and booleans
- Aggregate types - form more complicated data types by combining values of several simpler ones - include arrays and structs
- Reference types - refer to program variables or state indirectly- include pointers, slices, maps, functions, and channels
- The type rune is a synonym for int32 and conventionally indicates that a value is a unicode code point
- If the result of an arithmetic operation has more bits than can be resulted in the result type, it is said to overflow
- Although Go provides unsigned numbers and arithmetic, we tend to use the signed int form even for quantities that can't be negative, such as the length of an array, through uint might seem like a more obvious choice.
- unit types are rarely used
- The math package is usefule for math operations. Any comparison with NaN yields false
- There are two sizes of complex numbers: complex64 and complex128, whose components are float32 and float64 respectively
. The built in function complex creates a complex number from its real and imaginary parts, and the built in real and imag functions extract those components
Strings
- A string is a sequence of bytes
- The built in len function returns the number of bytes in a string, and the index operator s[i] retrieves the i-th byte (not necessarily character) of string s
- A raw string literal is written with backticks. There is no processing of escape sequences in raw string literals.
- Raw string literals are useful for regular expressions, HTML templates, JSON literals
Unicode collects all of the characters in all of the world’s writing systems, plus accents and other diacritical marks, control codes like tab and carriage return, and plenty of esoterica, and assigns each one a standard number called a Unicode code point or, in Go terminology, a rune.
- Raw string literals are useful for regular expressions, HTML templates, JSON literals
- Go's range loop, when applied to a string, performs UTF-8 decoding implicitly
- Useful packages for strings:
- bytes
- function sor manipulating slices of bytes, of type []byte, which share properties with strings. Because strings are immutable, building up strings incrementally can involve a lot of allocation and copying. In such cases, uts more efficient to use the bytes.Buffer type, which we'll show in a moment
- strings
- provides functions for searching, replacing, comparing, trimming, splitting, and joining strings
- strconv
- provides functions for converting boolean, integer, and floating point values to and from their string representations, and functions for quoting and unquoting strings
- unicode
- Provides functions like IsDigit, IsLetter, IsUpper, and IsLower for classifying runes.
- bytes
- A const declaration may use the constant generator iota, which is used to create a sequence of related values without spelling out each one explicitly. In a const declaration, the value of iota begins at zero and increments by one for each item in the sequence.
// Enumeration / enum / iota Example (Can use iota in more complex ecpressions)
type Weekday int
const (
Sunday Weekday = iota // 0
Monday // 1
Tuesday // 2
Wednesday
Thursday
Friday
Saturday
)
- Untyped constants allow for greater arithmetic precision. Only constants may be untyped. When an untyped constant is applied to a variable, the type of the variable is inferred.
Chapter 4: Composite Types
Arrays and structs are aggregate types; their values are concatenations of other values in memory. Arrays are homogeneous - their elements have the same type - whereas structs are heterogeneous. Both arrays and structs are fixed size. In contrast, slices and maps are dynamic data structures that grow as values are added.
Arrays
- Built in len function returns the number of elements in an array
- We can use an array literal to initialize an array with a list of values:
var q = [3]int{1,2,3}
var r = [3]int{1,2} //
fmt.Printf("%v",r[2]) // 0 -> Uninitialized array elements are initialized to the zero value for the type of each element of the array
var arr = [...]int{99:-1} // each value in this 100 length array has a value of the zero value for int (0) except for the last element, which is -1
- An arrays length is part of its type. If two arrays are comparable, then the == operator between two arrays check whether the elements at each index are equal
- Because of their fixed size (inflexibility), arrays are rarely used as function parameters, instead we use slices
Slices
- Slices represent variable length sequences whose elements all have the same type. A slice type is written []T, where the elements have type T; it looks like an array without the size
- A slice has three components: a pointer, a length, and a capacity.
- The pointer points to the first element of the array that is reachable through the slice
- The slice operator s[i:j], where 0<=i<=j<=cap(s), creates a new lice that refers to elements i through j-1 of the sequence s, which may be an array variable, a pointer to an array, or another slice.
- Slices are not comparable
- You can use the append() function to append items to a slice
- If there is space for the new element in the slice, the element extends the slice
- else, the slice's underling array needs to be copied to a location to where there is capacity for the new element
Maps
- In Go, a map is a reference to a hash table, and a map is written a map[K]V where K and V are types of its keys and values.
- You can't take the address of a map element
- The zero value for a map types is nil
- Most operations on maps, including lookup, delete, len, and range loops are safe to perform on a nil map reference, since it behaves like an empty map, but storing to a nil map reference causes a panic.
- lookup returns ok variable whether or not the key exists
- You must allocate the map before you can store into it
Structs
- A struct is an aggregate data type that groups together zero or more names values of arbitrary types as a single entity. Each value is called a field.
- You can take the address of a struct field
- The zero value for a struct is composed of the zero values for each of its fields.
- If you plan to use a struct in multiple packages, then the field names should be capitalized.
JSON
- conding/json package
- json.Marshal([]struct|struct) === JSON.stringify(val)
- json.MarshalIndent([]struct|struct) === JSON.stringify(val,null,' ')
- json.Unmarshal(string,*struct) === JSON.parse
- Unmarshaling ignores the feilds that are in the JSON string but that are not in the struct
Text and HTML Templates
- You can use the text/template and html/template package to make subsituting the values of variables into text or HTML template easier
Chapter 5: Functions
- A function declaration has a name, a list of parameters, and optional list of reults, and a body:
func name(parameter-list) (result-list) {
body
}
- The parameter list specifies the names and types of the function's parameters, which are local variables whose values or arguments are supplied by the caller.
func hypot(x, y float64) {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(3,4)) // "5"
- x and y are parameters in teh declaration, 3 and 4 are arguments of the call, and the function returns a float64 value.
- The type of a function is sometimes called its signature
- Two functions have the same type pr signature id they have the same sequence of parameter types and the same sequence of result types. The names of the parameters and results don't affect the type, nor does whether or not they were declared using the factored form.
- Arguments are passed by value, so the function recieves a copy of each argument. Modifications to the copy do not affect the caller. However, if the argument contains some kind of reference like a pointer, slice, map, function, or channel, the the caller may be affected by any modificatuon to variables indirectly reffered to by the argument.
- Go's garbage collector recycles unused memory, but no dont assume it will release unused operating system resources like open files and network connections. They should be closed explicitly.
- Use the fmt.Errorf method to return a string formatted error
- Anonymous functions:
strings.Map(func(r rune) rune {
return r+1,
},"HAL-9000") // IBM.:111
- A variadic function is one that can be called with varying number of arguments.
func cum(vals ...int) int {
total := 0
for _, val := range vals {
total += val
}
return total
}
- Implicitly, the caller allocates an array, copies the arguments into it, and passes a slice of the entire array to the function
The function and argument expressions are evaluated when the statement is executed, but the actual call is deferred until the function that contains the defer statement has finished, whether normally, by executing a return statement or falling off the end, or abnormally, by panicking.
A defer statement is often used wit h paired operations like open and close, connect and disconnect, or lock and unlock to ensure that resources are released in all cases, no matter how complex the control flow. The right place for a defer statement that releases a resource is immediately after the resource has been successfully acquired.
func title(url string) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
ct := resp.Header.Get("Content-Type")
if ct != "text/html" && ! strings.HasPrefix(ct,"text/html;") {
return fmt.Errorf("%s has type %s, not text/html",url,ct)
}
doc, err := html.Parse(resp.Body)
if err != nil {
return fmt.Errorf("parsing %s as HTML: %v",url,err)
}
// ...print doc's title element
return nil
}
- An out of bounds array access or nil pointer reference can't be caught at compile time. When the go runtime detects these mistakes at runtime, it panics.
During a typical panic, normal execution stops, all deferred function calls in that goroutine are exec ute d, and the program crashes with a log message. This log message includes the panic value, which is usually an error message of some sort, and, for each goroutine, a stack trace showing the stack of function cal ls that were active at the time of the panic. This log message often has enough information to diagnose the root cause of the problem without running the prog ram again, so it should always be included in a bug rep ort about a panicking program.
- You can induce your own panic by calling the built in panic() function. It is best to call the built in panic() function when some impossible situation occurs.
Chapter 6: Methods
- An object is simply a value or variable that has methods and a method is a function associated with a particular type.
- A method is declared with a variant of the ordinary function declaration in which an extra parameter appears before the function name. The parameter attaches thee function to the type of that parameter.
package geometry
import "math"
type Point struct{ X, Y float64}
// traditional function
func Distance(p, q Point) float64 {
return math.Hypot(q.X-p.X,q.Y-p.Y)
}
// same thing, but as a method of the point type
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X,q.Y-p.Y)
}
Because calling a function makes a copy of each argument value, if a function needs to update a variable, or if an argument is so large that we wish to avoid copying it, we must pass address of the variable using a pointer. The same goes for methods that need to update the receiver variable, we attach them to the pointer type, such as *Point
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}
p := Point{X: 2.5, Y: 2.5}
// ...
(*p).ScaleBy(2)
fmt.Printf("%v",p) // {5,5}
// ...
- Struct embedding: embedding allows complex types with many methods to be built up by the composition of several fields, each providing a few methods
- A variable or method of an object is said to be encapsulated if it is inaccessible to clients of the object. Encapsulation, sometimes referred to as information hiding, is a key aspect of OOP. You cna control encapsulation by either writing methods / properties in Uppercase of Lowercase.
Chapter 7: Interfaces
Interface types express generalizations or abstractions about the behaviors of other types. By generalizing, interfaces let us write functions that are more flexible and adaptable because they are not tied to the details of one particular implementation.
- Interface Types are abstract types that don't expose the representation or internal structure of its values, or the set of basic operations they support; it reveals only some of their methods.
type Reader interface {
Read(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
type ReadWriter interface {
Reader
Writer
}
type ReadWriteCloser interface {
Reader
Writer
Closer
}
Chapter 8: Goroutines and Channels
Concurrent programming, the expression of a program as a composition of several autonomous activities, has never been more important than it is today. Web servers handle requests for thousands of clients at once. Tablet and phone apps render animations in the user interface while simultaneously performing computation and network requests in the background. Even traditional batch problems—read some data, compute, write som e output—use concurrency to hide the latency of I/O operations and to exploit a modern computer’s many processors, which every year grow in number but not in speed.
This chapter presents goroutines and channels, which support communicating sequential processes or CSP, a model of concurrency in which values are passed between independent activities (goroutines) but variables are for the most part confined to a single activity. Chapter 9 covers some aspects of the more traditional model of shared memory multithreading, which will be familiar if you’ve used threads in other mainstream languages.
- In go, each concurrently executing activity is called a goroutine
- The main function is the main goroutine
- Other goroutines are called using the keyword go. A go statement causes the function to be called in a newly created goroutine. The go statement itself completes immediately.
f() // call f(); wait for it to return
go f() // Create a new goroutine that calls f(); don't wait
If goroutines are the activities of a concurrent Go program, channels are the connections between them. A channel is a communication mechanism that lets one goroutine send values to another goroutine. Each channel is a conduit for values of a particular type, called the channel’s element type. The type of a channel whose elements have type int is written chan int.
ch := make(chan int) // ch has type 'chan int'
- A channel is a reference to the data structure created by make. When we copy a channel or pass one as an argument to a function, we are copying a reference. so caller and callee refer to the same data structure. As with other reference types, the zero value of a channel is nil.
- A channel has two principle operations, send and receive, collectively known as communications. A send statement transmits a value from one goroutine, through the channel, to another goroutine executing a corresponding receive expression. Channels support a third operation, close, which sets a flag indicating that no more values will ever be sent on this channel; subsequent attempts to send will panic. Receive values on a closed channel yield the values that have been sent until no more values are left; any receive operations thereafter complete immediately and yield the zero value of the channel’s element type.
ch <- x // a send statement
x = <-ch // a receive statement in an assignment statement
<-ch // a receive statement; result is discarded
close(ch)
ch = make(chan int) // unbuffered channel
ch = make(chan int, 0) // unbuffered channel
ch = make(chan int, 3) // buffered channel with capacity 3
You needn’t close ever y channel when you’ve finished with it. It’s only necessary to close a channel when it is important to tell the receiving goroutines that all data have been sent.
- When a channel is supplied as a function parameter, it is nearly always with the intent that it be used exclusively for sending or exclusively for receiving.
To document this int ent and prevent misuse, the Go type system provides unidirectional channel types that expose on ly one or the other of the send and receive operations. The type chan<- int, a send-only channel of int, allows sends but not receives. Conversely, the type <-chan int, a receive-only channel of int, allows receives but not sends. (The position of the <- arrow relative to the chan keyword is a mnemonic.) Violations of this discipline are detected at compile time.
Chapter 9: Concurrency with Shared Variables
A race condition is a situation in which the program does not give the correct result for some interleavings of the operations of multiple goroutines. Race conditions are pernicious because they may remain latent in a program and appear infrequently, perhaps only under heavy load or when using cer tain compilers, platforms, or architectures. This makes them hard to reproduce and diagnose.
We’ll rep eat the definition, since it is so important: A data race occurs whenever two goroutines access the same variable concurrently and at least one of the accesses is a write. It follows from this definition that there are three ways to avoid a data race.
- sync package sync.Mutex, sync.Mutex.Lock(), sync.Mutex.Unlock()
- defer mutex.Unlock()
If some other goroutine has acquired the lock, this operation will block unt il the other goroutine cal ls Unlock and the lock becomes available again. The mutex guards the shared variables. By convention, the variables guarded by a mutex are declare d immediately after the declaration of the mutex itself. If you deviate from this, be sure to document it.
- defer mutex.Unlock()
Chapter 10: Packages and the Go tools
The only configuration most users ever need is the GOPATH environment variable, which specifies the root of the workspace. When switching to a different workspace, users update the value of GOPATH.
There was a lot to cover here. I didn't take as good of notes as I would have liked, but I will try to learn more about the Go language as I need to. I plan to rewrite the backend of this site in Go to make it faster and so that I can use Go for backend of different projects.
Standard Go Project Layout
Directories
/cmd
- Main applications for this project. The directory name for each application should natch the name of the executable you have.
- It's common to have a small main function that imports and invokes the code form the /internal and /pkg directories and nothing else.
/internal
- Private application and library code.
/pkg
- Library code that;s ok for use by external applications, Note that the internal directory is a better way to ensure your private packages are not importable because it's enforced by Go.
/vendor
- Application dependencies. The go mod vendor command will create the /vendor directory for you
/api
OpenAPI.Swagger specs, JSON schema files, protocol definition files
/web - Web Application Directories
- web application specific components: static web assets, server side templates and SPAs
/config
- configuration file templates or default configs
/init
- System init and process manager / supervisor configs
/scripts
- scripts to perform on various build, install, analysis operations
/build
- packaging and continous integration
/deployments
- deployment configurations
/test
- additional test apps and test data
/docs
- design and user documents
/tools
- supporting tools for this project
/examples
- examples for your applications and/or public libraries
/third_party
- external helper tools, forked code and other third party utilities
/githooks
- Git hooks
/assets
- Other assets that go along with your repository (images, logos, etc.)
/website
- Place to keep your project's website data if you are not using Github pages