Looks like no one added any tags here yet for you.
Golang is a statically typed, compiled programming language designed at Google by Robert Griesemer, Rob Pike, and Ken Thompson. Golang was designed at Google in 2007 to improve programming productivity in an era of multicore, networked machines and large codebases.
Pros:
Ease of use
A Smart Standard library
Strong security built-in
Garbage collected language.
Minimalism & Readability
Concurrency
Cons:
No generics
Error-handling boilerplate & lack of compile-time checks unhandled errors
Shortage of high-level parallelism and concurrency features
Go doesn't require an explicit class definition as Java, C++, C#, etc do. Instead, a "class" is implicitly defined by providing a set of "methods" which operate on a common type. The type may be a struct or any other user-defined type.
Go doesn't have classes, but it uses structs for data organization. Composition involves creating structs and embedding them within other structs. This allows you to combine the functionalities of different structs to create more complex ones. Instead of inheritance, Go relies on interfaces to define expected behaviors for structs.
Cloud services
Media platforms
Broadcast providers
Projects with microservice architecture
Golang has types and methods and allows an object-oriented style of programming, there is no type hierarchy.Golang has some properties of object oriented programming like Encapsulation , Composition , but it doesn't have inheritance , classes , function overloading .
Basic type: Numbers, strings, and booleans .
Aggregate type: Array and structs .
Reference type: Pointers, slices, maps, functions, and channels .
Interface type
Yes. A Go function can return multiple values, each separated by commas in the return statement.
The GOPATH environment variable specifies the location of your workspace. It defaults to a directory named go inside your home directory
The command go env GOPATH prints the effective current GOPATH; it prints the default location if the environment variable is unset.
Goroutines are incredibly lightweight “threads” managed by the go runtime. They enable us to create asynchronous parallel programs that can execute some tasks far quicker than if they were written in a sequential manner.
nil is a predeclared identifier in Go that represents zero values for pointers, interfaces, channels, maps, slices and function types.
The process involves several stages, including lexical analysis, parsing, semantic analysis, optimization, and code generation. Here’s a simplified overview of how a Go compiler works:
Lexical Analysis The first step is to break down the source code into tokens. This process is called lexical analysis or scanning. The compiler identifies keywords, identifiers, literals, operators, and other language elements and converts them into tokens. It discards whitespace and comments, as they are not needed for further processing.
Parsing The compiler then performs parsing, which involves analyzing the structure of the code to create a parse tree or an abstract syntax tree (AST). The parse tree represents the syntactic structure of the program, showing how different language elements are related.
Semantic Analysis The compiler performs semantic analysis on the parse tree to check for errors and ensure that the code adheres to the language’s rules. It verifies variable declarations, types, scope rules, and other semantic aspects of the code.
Intermediate Representation(IR) After semantic analysis, the compiler generates an intermediate representation (IR) of the code. This is an abstract, platform-independent representation of the program. Now the outcome of the IR Process are 1. Dead Code elimination. 2. Function Call Inline. 3. Devirtualize functions. 4. Escape Analysis
Static Single Assignment(SSA) The compiler performs various optimizations on the intermediate representation to improve the efficiency of the generated machine code. Common optimizations include constant folding, loop optimization, and dead code elimination.
Code Generation The optimized intermediate representation is then translated into machine code for the target platform. The compiler generates assembly code or directly produces machine code, depending on the target architecture.
Linking If the program consists of multiple source files or external libraries, a linker combines the generated machine code into a single executable file. It resolves references between different parts of the program and produces a complete executable binary.
Execution The final executable can be run on the target machine, executing the logic specified in the original Go source code.
ARRAY : An array is a fixed collection of data. The emphasis here is on fixed, because once you set the length of an array, it cannot be changed.
arr := [4]int{3, 2, 5, 4}
SLICE : Slices are much more flexible, powerful, and convenient than arrays. Unlike arrays, slices can be resized using the built-in append function .
slicee := make([]Type, length, capacity)
The interface is a collection of methods as well as it is a custom type. Interfaces can make code clearer, shorter, more readable, and they can provide a good API between packages, or clients (users) and servers (providers).
Concurrency means that an application is making progress on more than one task at the same time (concurrently). Well, if the computer only has one CPU the application may not make progress on more than one task at exactly the same time, but more than one task is being processed at a time inside the application. It does not completely finish one task before it begins the next.
Parallelism means that an application splits its tasks up into smaller subtasks which can be processed in parallel, for instance on multiple CPUs at the exact same time.
Parallelism does not require two tasks to exist. It literally physically run parts of tasks OR multiple tasks, at the same time using the multi-core infrastructure of CPU, by assigning one core to each task or sub-task.
Threads: A thread is just a sequence of instructions that can be executed independently by a processor. Threads use a lot of memory due to their large stack and requires call to OS for resources (such as memory) which is slow. so doesn’t always guarantee a better performance than processes in this multi-core processor world.
Goroutines exists only in the virtual space of go runtime and not in the OS. and A goroutine is created with initial only 2KB of stack size. Each function in go already has a check if more stack is needed or not and the stack can be copied to another region in memory with twice the original size. This makes goroutine very light on resources.
Channels are the pipes that connect concurrent goroutines. You can send values into channels from one goroutine and receive those values into another goroutine.
Channels are goroutine-safe and can store and pass values between goroutines
Channels provide FIFO semantics.
Channels cause goroutines to block and unblock, which we just learned about.
⬆
A closure is a function value that references variables from outside its body. The function may access and assign to the referenced variables.
message := "hello world"
printMessageFunc := func(message string) {
fmt.Println(message)
}
printMessageFunc(message)
The runtime library implements garbage collection, concurrency, stack management, and other critical features of the Go language. The Package runtime contains operations that interact with Go's runtime system, such as functions to control goroutines.
With the help of runtime package
fmt.Println(runtime.NumCPU())
We can restrict the number of goroutines running at the same time , like below
package main
import (
"flag"
"fmt"
"time"
"sync"
)
// Fake a long and difficult work.
func DoWork() {
time.Sleep(500 * time.Millisecond)
}
func main() {
maxNbConcurrentGoroutines := flag.Int("maxNbConcurrentGoroutines", 2, "the number of goroutines that are allowed to run concurrently")
nbJobs := flag.Int("nbJobs", 5, "the number of jobs that we need to do")
flag.Parse()
concurrentGoroutines := make(chan struct{}, *maxNbConcurrentGoroutines)
var wg sync.WaitGroup
for i := 0; i < *nbJobs; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
concurrentGoroutines <- struct{}{}
fmt.Println("doing", i)
DoWork()
fmt.Println("finished", i)
<-concurrentGoroutines
}(i)
}
wg.Wait()
}
There are three different ways to find type of variable in Golang
reflect.TypeOf Function: Using the golang inbuilt package reflect we can find the Type of variable
string_type := "hello"
fmt.Println("string_type", reflect.TypeOf(string_type))
reflect.ValueOf.Kind() Function : Using the golang inbuilt package reflect we can find the Type of variable
var string_type = "Hello Go";
fmt.Println("string_type", reflect.ValueOf(string_type).Kind())
%T with Printf : You can use Printf also to find value of variable
var string_type = "Hello Go";
fmt.Printf("string_type=%T\n", string_type)
Value - The Value type of a mpa can be anything, including another map
Key - The key type of Map can only be values that can be compared i.e - boolean, numeric, string, pointer, channel, and interface types, and structs or arrays , that excludes - slices, maps, and functions
Microservices is an architectural style that structures an application as a collection of services that are
Highly maintainable and testable
Loosely coupled
Independently deployable
Organized around business capabilities
Owned by a small team
Garbage collection is a term for the process of automatic memory recycling. A garbage collector (or GC, for short) is a system that recycles memory on behalf of the application by identifying which parts of memory are no longer needed. Some concepts to understand the GC:
Stack allocation is a memory allocation method used for variables with a fixed size and a known lifetime. When you create a variable on the stack, the memory for that variable is automatically managed by the system.
func add(a, b int) int {
var result int // Stack-allocated variable
result = a + b
return result
}
Dynamic memory allocation or Heap allocation is used for variables with a dynamic size or a longer lifetime than the function in which they are defined. Variables allocated on the heap need to be explicitly managed by the programmer or by the Go garbage collector. Here are some characteristics of heap-allocated variables
func createSlice() []int {
return make([]int, 10) // Heap-allocated slice
}
The Go garbage collector (GC) uses a mark-sweep technique to manage memory. This two-phased process identifies and reclaims unused memory on the heap.
Here's a breakdown of the GC cycle in Golang:
Mark Phase
The GC starts by identifying all "live" objects. These are objects that are still reachable by your program's running code. It achieves this by traversing the program's memory structures, starting from the goroutines and their stacks. Any object reachable from these starting points is marked as live. Data structures like slices, maps, and channels that reference other objects also get marked during this traversal.
Sweep Phase
Once the marking phase is complete, the GC knows which objects are actively being used. It then sweeps through the entire heap memory. Any memory location that is not marked as live is considered garbage and is reclaimed. This reclaimed memory becomes available for future object allocations.
Triggers for GC Cycle
The GC cycle doesn't run on a fixed schedule. Instead, it's triggered dynamically based on the heap size. A key factor is the heap growth ratio, controlled by the environment variable GOGC. By default, it's set to 100. When the heap size reaches twice the size it was at the end of the previous GC cycle (100% growth), a new GC cycle is initiated.
Adjusting GOGC allows you to fine-tune the balance between GC overhead and memory usage. A higher value allows the heap to grow larger before triggering GC, but it might also lead to more memory fragmentation.
Compiletime is the time at which the source code is converted into an executable code
Runtime is the time at which the executable code is started running.
The default number generator is deterministic, so it’ll produce the same sequence of numbers each time by default , so you need to seed it with different number you can do it with nano seconds like below
s1 := rand.NewSource(time.Now().UnixNano())
r1 := rand.New(s1)
fmt.Print(r1.Intn(100))
A goroutine is created with initial only 2KB of stack size. Each function in go already has a check if more stack is needed or not and the stack can be copied to another region in memory with twice the original size. This makes goroutine very light
It would be set by default to length of the slice
You can use the length (len(slice)) property to find if the slice is empty
if(len(slice_data)==0){
fmt.Println("Empty slice")
}
Types can be implicitly inferred from expressions and don’t need to be explicitly specified.The special handling of interfaces and the implicit typing makes Go feel very lightweight and dynamic.
Go’s compiler and runtime employ a technique known as escape analysis to determine whether a variable needs to escape from the current function scope. Variables that do not escape (stay within the function scope) are more likely to be stack-allocated, while variables that escape (are passed to other functions, returned, or stored in global variables) are more likely to be heap-allocated. Escape analysis helps optimize memory usage and minimize the overhead of garbage collection.
Example of goroutine and channel
example:
package main
import "fmt"
func reverseString(s string, c chan string) {
r := ""
for _, value := range s {
r = string(value) + r
}
c <- r // send new string to c
}
func main() {
s := "hello world"
p := "new string"
c := make(chan string)
go reverseString(s, c)
go reverseString(p, c)
result1, result2 := <-c, <-c // receive from c
close(c)
fmt.Println(result1)
fmt.Println(result2)
}