Monday, 17 January 2022

Difference between "docker stop" and "docker kill"

 To stop a running container, you can use either "docker stop" or "docker kill" command to do so. Although it seems doing the same thing, actually there is a slight difference between them.

"docker stop" command will send a hardware signal SIGTERM to the container, if the process received the SIGTERM signal, the developer can code to do some clean up and releasing some resources before terminating the process.

However "docker kill" command will send a hardware signal SIGKILL to the container and stop it immediately.

Usually "docker stop" is used to stop a running container. If the container is hanging there for a long time or failed to stop. Then you can try to use "docker kill"to kill the container anyway.

Sunday, 26 December 2021

Why do we need an accordant pace in computer systems

In the past, we always know clock is used to adjust the pace of the computer systems. But we don't understand why we need an accordant pace in computer systems. Why don't we just let the components in the computer do as much and fast as they can?

The reason is that the different components have dependency between each other. So if we image the whole computer systems is a succession program, of course we don't need accordant pace any more. But this will make the components in computer wait for each other's result to go on. So this can't make components work in parallel way.

So obviously clock has this advantage which is that it can make different components work in parallel way become possible.

Sunday, 28 November 2021

DO NOT use the return of append function to create new slice in Go

For Golang developers, it is very common to use append function to append new elements into slice. The common syntax is like below:

    var slice := make([]int, 0)
    slice = append(slice, 123)
And for some requirements, you may want to create a new slice based on an existing slice. We know the below the code will work perfectly.
    slice := []int{1,2,3}
    newSlice := make([]int, 0)
    newSlice = append(newSlice, append(slice, 4)...)
Below is complete example code to show the result:
package main

import (
    "fmt"
)

func main() {
    // here create the origin slice with cap 10 to make sure that
    // the slice is big enough to hold more data
    slice := make([]int, 0, 10)
    slice = append(slice, []int{1, 2, 3}...)
    newSlice := make([]int, 0)
    newSlice = append(newSlice, append(slice, 4)...)
    fmt.Println(slice)
    fmt.Println(newSlice)
    fmt.Printf("The address of slice: %v\n", &slice[0])
    fmt.Printf("The address of newSlice: %v\n", &newSlice[0])
}
The result is:
[1 2 3]
[1 2 3 4]
The address of slice: 0xc000014050
The address of newSlice: 0xc00007a000
You can see the two slices have different addresses.
However you may think if we can simplify the code by appending value to the origin slice and assign the return value to the newSlice variable like below:
    slice := []int{1,2,3}
    newSlice := make([]int, 0)
    newSlice = append(slice, 4)
This looks simpler and cleaner, right? NO, this won't work! Let's see an example:
package main

import (
    "fmt"
)

func main() {
    // here create the origin slice with cap 10 to make sure that
    // the slice is big enough to hold more data
    slice := make([]int, 0, 10)
    slice = append(slice, []int{1, 2, 3}...)
    newSlice := make([]int, 0)
    newSlice = append(slice, 4)
    fmt.Println(slice)
    fmt.Println(newSlice)
    fmt.Printf("The address of slice: %v\n", &slice[0])
    fmt.Printf("The address of newSlice: %v\n", &newSlice[0])
}
The result is:
[1 2 3]
[1 2 3 4]
The address of slice: 0xc00010a000
The address of newSlice: 0xc00010a000
See newSlice is pointing to the same address of slice?

This is because append function does not guarantee that it always returns a new slice. It only reallocate a new slice when the capacity of the slice in the first parameter is not enough to hold more data. So if you are expecting to use append function to get a new slice like above, you will probably mess up the origin slice and get the wrong output.
So to be safe, if you want to get a new slice based on an existing slice, you use the code below:
    slice := []int{1,2,3}
    newSlice := make([]int, 0)
    newSlice = append(newSlice, append(slice, 4)...)
Or below code if it is clearer.
    slice := []int{1,2,3}
    newSlice := make([]int, 0)
    newSlice = append(newSlice, slice...)
    newSlice = append(newSlice, 4)
I encountered this problem when I am practicing LeetCode problem 797



Wednesday, 17 November 2021

How to add optional arguments in Go function without impacting the existing function calls

Unlike C# you can put "Optional" keyword to add optional arguments in function, there is no direct way to create optional parameters in Go functions. And there is no method overloading in Go either. Even so, if you really need to add optional arguments to an existing function, maybe because this function is in a common library which is called by different software modules and you don't want to update all the modules for this new argument change, there is still a way to implement optional arguments thanks to variadic arguments in Go functions.

Variadic arguments allow developer to put any number of trailing arguments as the input of functions. For example:

package main

import (
  "fmt"
)

func main() {
  fmt.Printf("1 + 2 = %d\n", add(1, 2))
  fmt.Printf("1 + 2 + 3 + 4 + 5 = %d\n", add(1, 2, 3, 4, 5))
}

func add(args ...int) int {
  sum := 0
  for i:=0; i < len(args); i++ {
    sum += args[i]
  }
  return sum
}
The output is:
1 + 2 = 3
1 + 2 + 3 + 4 + 5 = 15
With the help of variadic arguments, you can easily to implement optional arguments. For example, you have a function to generate characters in a common library for a game development. This function can generate characters with name and sex for now. 
package main

import (
  "fmt"
)

func main() {
  createCharactor("Bertram", "Male")
}

func createCharactor(name string, sex string) {
  fmt.Println("---------- New Charactor ----------")
  fmt.Printf("Name is: %s\n", name)
  fmt.Printf("Sex is: %s\n", sex)
  fmt.Println("------ New Charactor Created ------")
}
The output is: 
---------- New Charactor ----------
Name is: Bertram
Sex is: Male
------ New Charactor Created ------
Now you want to add age argument to this function without impacting the existing function call. You can do like following:
package main

import (
  "fmt"
)

func main() {
  createCharactor("Bertram", "Male")
  fmt.Println()
  createCharactor("Kai", "Male", 15)
}

func createCharactor(name string, sex string, age ...int) {
  fmt.Println("---------- New Charactor ----------")
  fmt.Printf("Name is: %s\n", name)
  fmt.Printf("Sex is: %s\n", sex)
  if len(age) > 0 {
    fmt.Printf("Age is: %d\n", age[0])
  }
  fmt.Println("------ New Charactor Created ------")
}
The output is:
---------- New Charactor ----------
Name is: Bertram
Sex is: Male
------ New Charactor Created ------

---------- New Charactor ----------
Name is: Kai
Sex is: Male
Age is: 15
------ New Charactor Created ------
But the above code is not reusable to add other arguments with different data types. For example, what if you need to add title, email later? So in order to make the variadic argument more generic, we can use "interface{}" type as the variadic argument which gives the ability to add more arguments with different data types.

According to this, the above code can be refactored as below:
package main

import (
  "fmt"
)

func main() {
  createCharactor("Bertram", "Male")
  fmt.Println()
  createCharactor("Kai", "Male", 15, "Software Engineer", "kai@gmail.com")
  fmt.Println()
	
}

func createCharactor(name string, sex string, args ...interface{}) {
  fmt.Println("---------- New Charactor ----------")
  fmt.Printf("Name is: %s\n", name)
  fmt.Printf("Sex is: %s\n", sex)
  if len(args) > 0 {
    fmt.Printf("Age is: %d\n", args[0].(int))
    fmt.Printf("Tile is: %s\n", args[1].(string))
    fmt.Printf("Email is: %s\n", args[2].(string))
  }
  fmt.Println("------ New Charactor Created ------")
}
The output is:
---------- New Charactor ----------
Name is: Bertram
Sex is: Male
------ New Charactor Created ------

---------- New Charactor ----------
Name is: Kai
Sex is: Male
Age is: 15
Tile is: Software Engineer
Email is: kai@gmail.com
------ New Charactor Created ------

However, this solution does not convey the meaning of the optional arguments. So you either put a comment above the header to specify what it means for each optional argument, or comment where you call the functions, otherwise it will become very difficult to understand the code. So this solution is not recommended unless you have a very tight deadline to meet the delivery date and really have no time to refactor code.  

Sunday, 24 October 2021

Pointer Receiver vs Value Receiver

Receivers are same as parameters in function in Go. They are also passed by value when functions are called. Because of this, it is preferred to use pointers as receivers instead of passing values directly. This is because usually we use receivers to implement OOP programming style. If passed by value, the properties of the objects will not be able to update.

For example, we have an object called "person", it has age property. Initially when created, the person has initialised the age to 0, then we use method to change it to 18. See code below:

package main

import (
	"fmt"
)

type Person struct {
	Name string
	Age int
}

func main() {
	person := Person{Name : "Leo"}
	fmt.Println(person)
	person.SetAge(18)
	fmt.Println(person)
	
}

func (p *Person) SetAge(age int) {
	p.Age = age
}
The output of the result is:
{Leo 0}
{Leo 18}
However if the receiver is passed by value like below:

func (p Person) SetAge(age int) {
	p.Age = age
}

Then the age property will not be updated. However if the receiver is passed by value like below:

{Leo 0}
{Leo 0}

Sunday, 3 October 2021

Variables and Data Types in Javascript

This is a study note about Javascript from https://www.geeksforgeeks.org/introduction-to-javascript-course-learn-how-to-build-a-task-tracker-using-javascript/

Javascript is untyped language. Once a variable is created in javascript using 'var', you can assign any other type of data to this variable.

var text = 'Hello World'
text = 123

Above code is valid in Javascript. Variable text can be assigned a string value and then later change to a number.

1. Difference between var and let in Javascript

Basically, 'var' is function scoped, however 'let' is block scoped.

https://www.geeksforgeeks.org/difference-between-var-and-let-in-javascript/

Let's use use 'let' for most of the cases because 'var' is confusing.

2. Number type in Javascript

The number type in Javascript contains both integer and floating point numbers. Besides these numbers, we also have 'special-numbers' in Javascript that are: 'infinity', '-Infinity' and 'NaN'. The 'NaN' denotes a computational error.

let num = 2; // integer
let num2 = 1.3; // floating point number
let num3 = Infinity; // Infinity
let num4 = 'something here too'/2; // NaN

3. String type in Javascript

There are three types of quotes in Javascript:

let str = "Hello There";
let str2 = 'Single quotes works fine';
let phrase = `can embed ${str}`;

There's no difference between 'single' and "double" quotes in Javascript. Backticks provide extra functionality as with the help of them we can embed variables inside them.

let variable = "Leo";

// embed a variable
alert( `Hello, ${variable}!` ); // Hello, Leo!

4, Undefined type in Javascript

The meaning of undefined is 'value is not assigned'.

let x;
console.log(x); // undefined

5, Object type in Javascript

Everything in Javascript is Object. There are two ways to create objects.

let person = new Object();
let person = {};

You can initialise object with properties like below:

let person = {
name: "Mukul",
age: 22,
}







Sunday, 13 June 2021

Mistakes That Should Not Happen Again

 Database:

DO NOT use "key" keyword as a column name, because it is a reserved keyword in MySQL.


Docker:

Clean docker using below command from time to time to release more memory:

docker system prune

Then type u and press "enter".





Monday, 12 April 2021

Channels in Go

 This is a study note for YouTube tutorial: https://www.youtube.com/watch?v=YS4e4q9oBaU&t=20036s


Channel basics

You can only create channels using make function.

ch := make(chan int) // chan int means create a integer channel

Below is an example how to use channel to transfer data between goroutines.

var wg = sync.WaitGroup{}

func main() {
ch := make(chan int)
wg.Add(2)
go func() {
i := <-ch
fmt.Println(i)
wg.Done()
}()
go func() {
ch <- 42
wg.Done()
}()
wg.Wait()
}

The result is:
42


Restricting data flow

Be noted that channel operation will block the goroutine when executing. If you have unequal receiver and sender, then the additional goroutines will be blocked and give runtime errors.

func main() {
ch := make(chan int)
wg.Add(3)
go func() {
i := <-ch
fmt.Println(i)
wg.Done()
}()
go func() {
ch <- 42
wg.Done()
}()
go func() {
ch <- 27
wg.Done()
}()
wg.Wait()
}

27 fatal error: all goroutines are asleep - deadlock!

You can use bidirectional communication using channel in goroutines.

func main() {
ch := make(chan int)
wg.Add(2)
go func() {
i := <-ch
fmt.Println(i)
ch <- 27
wg.Done()
}()
go func() {
ch <- 42
fmt.Println(<-ch)
wg.Done()
}()

wg.Wait()
}

42 27

Goroutines can automatically manage the directions of the communication. However in most cases, we would like to make channel in single directions, which means one goroutine is dedicated to be receiver and another be a sender. To do this, you can put channel as function parameter for goroutine to restrict the data flow. Because function parameter can be used to cast a bi channel to a single direction channel.

func main() {
ch := make(chan int)
wg.Add(2)
go func(ch <-chan int) { // Here actually cast bi channel to receiver channel
i := <-ch
fmt.Println(i)
wg.Done()
}(ch)
go func(ch chan<- int) {
ch <- 42
wg.Done()
}(ch)

wg.Wait()
}

42

Now the first goroutine is restricted to receive-only, and the second is restricted to send-only.

Buffered channels

You can add a buffer for a channel by simply specifying the second parameter of make function with buffer size.

var wg = sync.WaitGroup{}

func main() {
ch := make(chan int, 50)
wg.Add(2)
go func(ch <-chan int) {
i := <-ch
fmt.Println(i)
wg.Done()
}(ch)
go func(ch chan<- int) {
ch <- 42
ch <- 27
wg.Done()
}(ch)

wg.Wait()
}

27 will be buffered instead of throwing a "deadlock" error.



For...range loops with channels

The above code is not a correct way to handle buffered channels. "for...range" loops is preferred in this case.

var wg = sync.WaitGroup{}

func main() {
ch := make(chan int, 50)
wg.Add(2)
go func(ch <-chan int) {
for i := range ch {
fmt.Println(i)
}
wg.Done()
}(ch)
go func(ch chan<- int) {
ch <- 42
ch <- 27
wg.Done()
}(ch)

wg.Wait()
}

The result is:

42 27 fatal error: all goroutines are asleep - deadlock!

You can see there is "deadlock" error again, but this time it is not because of the sender goroutine, it is because of the for range loop in the receiver goroutine. Because the receiver goroutine keeps trying to receive data even there is no data sent anymore.

To solve this problem, you can use "close(ch)" function to close channel in the sender goroutine if there is no data to send anymore

var wg = sync.WaitGroup{}

func main() {
ch := make(chan int, 50)
wg.Add(2)
go func(ch <-chan int) {
for i := range ch {
fmt.Println(i)
}
wg.Done()
}(ch)
go func(ch chan<- int) {
ch <- 42
ch <- 27
close(ch)
wg.Done()
}(ch)

wg.Wait()
}

The result is:

42 27

In fact, channel has a second return value which you can use for checking if the channel is open or closed.

var wg = sync.WaitGroup{}

func main() {
ch := make(chan int, 50)
wg.Add(2)
go func(ch <-chan int) {
for {
if i, ok := <-ch; ok {
fmt.Println(i)
} else {
break
}
}
wg.Done()
}(ch)
go func(ch chan<- int) {
ch <- 42
ch <- 27
close(ch)
wg.Done()
}(ch)

wg.Wait()
}

The result is:

42 27


Select statements

You can use a select statement to manage the workflow in a goroutine. Select statement is similar to switch statement, but select statement is only used for communications. The case statement refers to the communications on different channels.

var logCh = make(chan string)
var doneCh = make(chan struct{})

func main() {
go logger()

logCh <- "test1"
logCh <- "test2"
time.Sleep(100 * time.Millisecond)
doneCh <- struct{}{} // empty struct used as a signal here
}

func logger() {
for {
select {
case log := <-logCh:
fmt.Println(log)
case <-doneCh:
break
}
}
}


References: https://www.youtube.com/watch?v=YS4e4q9oBaU&t=20036s

Goroutines

 This is a study note for YouTube tutorial: https://www.youtube.com/watch?v=YS4e4q9oBaU&t=20036s

Goroutines are green threads. Green threads are created and managed by applications instead of kernel like the native threads. One of the benefits in green thread is that it can be created very fast.

Creating goroutines

Use keyword "go" before the invocation of the function to turn normal functions into goroutines.
func main() {
go sayHello()

}

func sayHello() {
fmt.Println("Hello")
}
In many cases, we may need anonymous functions to run goroutines.
func main() {
var msg = "Hello"
go func() {
fmt.Println(msg)
}()
time.Sleep(100 * time.Millisecond)

}
The result is:
Hello

From above example, you can see that goroutines are able to use the variable in the outer space. This is handled by go runtime. However this is not recommended to use, because later when you change the variable, it could impact the behaviour in goroutine. Instead, you can function parameter to pass value, in this case the variables in upper block will not impact the goroutine, because the value is duplicated in goroutine.
func main() {
var msg = "Hello"
go func(msg string) {
fmt.Println(msg)
}(msg)
msg = "Goodbye"
time.Sleep(100 * time.Millisecond)

}
The result is:
Hello


Synchronization

You may notice the sleep function call in main function is not really good approach, because it really slows down the program. So we can use WaitGroup to solve this problem.

WaitGroups

var wg = sync.WaitGroup{}

func main() {
var msg = "Hello"
wg.Add(1)
go func(msg string) {
fmt.Println(msg)
wg.Done() // decrement the wait group by 1
}(msg)
msg = "Goodbye"
wg.Wait() // wait until wait group be 0

}

To illustrate further, below is slightly more complicated example that uses two goroutine.

var wg = sync.WaitGroup{}
var counter = 0

func main() {
for i := 0; i < 10; i++ {
wg.Add(2)
go sayHello()
go increment()
}
wg.Wait()

}

func sayHello() {
fmt.Printf("Hello #%v\n", counter)
wg.Done()
}

func increment() {
counter++
wg.Done()
}

The result is:
Hello #3 Hello #1 Hello #7 Hello #8 Hello #7 Hello #0 Hello #7 Hello #3 Hello #0 Hello #10

Mutexes

From above result, you see the number is not in sequence. In order to do that, you need to use mutex to lock the shared variables between goroutines.

var wg = sync.WaitGroup{}
var counter = 0
var m = sync.RWMutex{}

func main() {
for i := 0; i < 10; i++ {
wg.Add(2)
m.RLock()
go sayHello()
m.Lock()
go increment()
}
wg.Wait()

}

func sayHello() {
fmt.Printf("Hello #%v\n", counter)
m.RUnlock()
wg.Done()
}

func increment() {
counter++
m.Unlock()
wg.Done()
}

Actually this example has no sense, because it is totally remove the multi threading advantages.

Parallelism

You can use  "runtime.GOMAXPROCS(int)" function to set the number of low-level threads you want to use. If you set the parameter to be -1, the function return the thread number you set or return the number of cores by default if you didn't set any.

func main() {
fmt.Printf("Threads: %v\n", runtime.GOMAXPROCS(-1))
runtime.GOMAXPROCS(1)
fmt.Printf("Threads: %v\n", runtime.GOMAXPROCS(-1))

}

Threads: 8 Threads: 1

The number is better to use the number of cores, because if you set a big value, you could slow down the performance.

Best practices

  • Don't create goroutines in libraries
    • Let consumer control concurrency
  • When creating a goroutine, know how it will end
    • Avoids subtle memory leaks
  • Check for race conditions at compile time -  go run -race main.go 


References: https://www.youtube.com/watch?v=YS4e4q9oBaU&t=20036s

Sunday, 11 April 2021

Interfaces in Go

 This is a study note from YouTube tutorial: https://www.youtube.com/watch?v=YS4e4q9oBaU&t=12098s

Interface is a collection of function prototypes. You can define interface like below:

type Writer interface {
Write([]byte) (int, error)
}

Interfaces should be implemented as methods.

type ConsoleWriter struct{}

func (cw ConsoleWriter) Write(data []byte) (int, error) {
n, err := fmt.Println(string(data))
return n, err
}

Polymorphism

You can use interface to realise polymorphism like below:

func main() {
var w Writer = ConsoleWriter{} // Here can use different structs
//with different implementation
w.Write([]byte("Hello go!"))

}

Implicit implementation

Except polymorphism, you can use interface to do implicit implementation. What does implicit implementation means? Since you don't need to use "implements" keyword to implement an interface like JAVA and some other languages, interfaces decouple the definitions from their implementations. There are a lot of packages which utilise structs with fields and methods. So you can design your own interfaces which can utilise the methods in the structs.

Use any type to implement interfaces

You don't have to use struct to implement interfaces. In fact, you can use any type to implement interfaces.

func main() {
myInt := IntCounter(0)
var inc Incrementer = &myInt
for i := 0; i < 10; i++ {
fmt.Println(inc.Increment())
}
}

type Incrementer interface { // define interface
Increment() int
}

type IntCounter int // define int type

func (ic *IntCounter) Increment() int { // implement interface
*ic++ // Here is important, it increment the type itself which is int
return int(*ic)
}

Interface embedding

You can embed interface into another interface like structs. Below is a writer example code from the tutorial video.

func main() {
var wc WriterCloser = NewBufferredWriterCloser()
wc.Write([]byte("Hello Go developers, this is a test"))
wc.Close()
}

type Writer interface {
Write([]byte) (int, error)
}

type Closer interface {
Close() error
}

type WriterCloser interface {
Writer // Here two interfaces are
Closer // embedded in composer interface
}

type BufferedWriterCloser struct {
buffer *bytes.Buffer
}

func (bwc *BufferedWriterCloser) Write(data []byte) (int, error) {
n, err := bwc.buffer.Write(data)
if err != nil {
return 0, err
}
v := make([]byte, 8)
for bwc.buffer.Len() > 8 {
_, err := bwc.buffer.Read(v)
if err != nil {
return 0, err
}
_, err = fmt.Println(string(v))
if err != nil {
return 0, err
}

}
return n, nil
}

func (bwc *BufferedWriterCloser) Close() error {
for bwc.buffer.Len() > 0 {
data := bwc.buffer.Next(8)
_, err := fmt.Println(string(data))
if err != nil {
return err
}
}
return nil
}

func NewBufferredWriterCloser() *BufferedWriterCloser {
return &BufferedWriterCloser{
buffer: bytes.NewBuffer([]byte{}),
}
}

Interface type conversion

Interface variable can be converted to the value type (structs or other types) using dot oeprtation.

func main() {
var wc WriterCloser = NewBufferredWriterCloser()
wc.Write([]byte("Hello Go developers, this is a test"))
//wc.Close()

bwc := wc.(*BufferedWriterCloser) // Here wc is converted to
// to struct directly, in
// this way you can access
// the buffer field directly
fmt.Println(bwc.buffer)
}

You can check the if the conversion is successful or failed by checking the second return value from the conversion operation.

func main() {
r, ok := wc.(io.Reader)
if ok {
fmt.Println(r)
} else {
fmt.Println("Conversion failed")
}
}

The result is:
Conversion failed

Empty interface and type conversion

Anything in Go can be an empty interface. This is useful when you want to get a value and then later want to covert it onto others. Because empty interface has no method in it. So in order to use it, you have to do a type conversion.
func main() {
var myObj interface{} = NewBufferredWriterCloser()
if wc, ok := myObj.(io.WriteCloser); ok { // Here do convertion
wc.Write([]byte("Hello Go developers, this is a test"))
wc.Close()
}

r, ok := myObj.(io.Reader)
if ok {
fmt.Println(r)
} else {
fmt.Println("Conversion failed")
}
}

Type switch

You can use switch to check the type of a variable.
func main() {
var i interface{} = 0
switch i.(type) {
case int:
fmt.Println("i is an integer")
case string:
fmt.Println("i is a string")
default:
fmt.Println("I don't know what it is")

}
}





References: https://www.youtube.com/watch?v=YS4e4q9oBaU&t=12098s

Wednesday, 7 April 2021

Turn off the startup chime permanently using terminal

 The startup chime is the only reason I want to smash my Macbook Pro sometimes. And Apple didn't provide an easy way to turn this thing off. I could not find the setting below in Apple support although it mentioned that it should be there for MacOS Big Sur 11 or later. However I am using the latest MacOS Catalina 10, but I still cannot find it.

But I found a solution that works for me.

You can try the below command to turn it off.

 sudo nvram StartupMute=%01 

If you want to turn it on.

 sudo nvram StartupMute=%00 

For me, I prefer to keeping it mute forever.










Difference between "docker stop" and "docker kill"

 To stop a running container, you can use either "docker stop" or "docker kill" command to do so. Although it seems doin...