First, object-oriented
1.1. Differences with Java Object orientation
Golang is a language with an object-oriented programming style, but without the extends, implements keywords of traditional object-oriented languages such as Java.
In Golang, through the interface or the combination of the structure to achieve the strict “inheritance”, through non-invasive interface to implement the strict “polymorphic”, through the structure and package and function implementation code details of “packaging”, the encapsulation, inheritance and polymorphism, it can be well achieved by OO thinking and realistic demand of the program.
1.2. Structure combination
Consider this scenario: Animal has the basic characteristics of Name and Age. Now we need to implement a Dog type, and Dog type needs to have all the characteristics of Animal, and has its own bark() method. What’s the difference between using Java and Golang to implement this scenario?
Dog extends Animal: Class extends Animal: Class extends Animal
- Java
public abstract class Animal {
protected String name;
protected int age;
}
public class Dog extends Animal {
public void bark(a) {
System.out.println(age + "Old" + name + "Woof woof woof..."); }}public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.name = "tom";
dog.age = 2;
dog.bark(); // 2 years old Tom is barking...}}Copy the code
In Golang, inheritance can be achieved by combining constructs like this:
- Golang
package oom
type Animal struct {
Name string
Age int
}
type Dog struct {
*Animal
}
func (d *Dog) Bark(a) {
fmt.Printf("%d year old %s is woof woof...", d.Age, d.Name)
}
// ----------
package main
func main(a) {
dog := &oom.Dog{&oom.Animal{
Name: "tom",
Age: 2,
}}
dog.Bark() // 2 years old Tom is barking...
}
Copy the code
Golang uses a non-intrusive interface to achieve “polymorphism”.
1.3. Non-invasive interface
The interface of the Go language is not an interface concept provided in other languages (C++, Java, C#, etc.). Before the Go language, interfaces existed primarily as contracts between different components. The implementation of the contract is mandatory; you must declare that you actually implement the interface. To implement an interface, you need to inherit from that interface:
interface IFoo { void Bar(); } class Foo implements IFoo {// Java implements IFoo... } class Foo: public IFoo {// C++ grammar //... } IFoo foo = new Foo;Copy the code
This type of interface is called intrusive. The main manifestation of “intrusive” is that the implementation class needs to explicitly declare that it implements an interface. This mandatory interface inheritance is one of the most questionable features of object-oriented programming.
Golang’s non-invasive interface does not require any keywords to declare the implementation relationship between a type and an interface. As long as a type implements all the methods of the interface, that type is the implementation type of the interface.
Suppose we now have a Factory interface that defines Produce() and Consume() methods, with CafeFactory as its implementation type. This can be done with the following code:
package oom
type Factory interface {
Produce() bool
Consume() bool
}
type CafeFactory struct {
ProductName string
}
func (c *CafeFactory) Produce(a) bool {
fmt.Printf(CafeFactory successfully produced %s, c.ProductName)
return true
}
func (c *CafeFactory) Consume(a) bool {
fmt.Printf("CafeFactory consumption %s succeeded", c.ProductName)
return true
}
// --------------
package main
func main(a) {
factory := &oom.CafeFactory{"Cafe"}
doProduce(factory)
doConsume(factory)
}
func doProduce(factory oom.Factory) bool {
return factory.Produce()
}
func doConsume(factory oom.Factory) bool {
return factory.Consume()
}
Copy the code
As you can see, a CafeFactory is a Factory as long as it implements all Factory methods without explicitly declaring their implementation relationships using the implements keyword.
Golang’s non-invasive interface has many benefits:
1. In Go, the inheritance tree of a type doesn’t mean anything. We just need to know what methods the type implements and what each method means
2. When implementing a type, you just need to worry about what methods you should provide, rather than how thin the interface should be. Interfaces are defined by users on demand without prior planning
3. There is no need to import a package to implement an interface, because more references to an external package mean more coupling. Interfaces are defined by users according to their own requirements. Users do not need to care about whether similar interfaces have been defined by other modules
To sum up, the benefits of non-invasive interfaces are simple, efficient, and implemented on demand.
1.4, interface{} empty interface
Interface {} An empty interface is any type of interface, and all types are implementation types of the empty interface. Since Golang’s requirement for implementation types is that all methods of the interface are implemented, and empty interfaces have no methods, any type can act as an empty interface.
Here is an example of a type determination using an empty interface as a parameter:
func getType(key interface{}) string {
switch key.(type) {
case int:
return "this is a integer"
case string:
return "this is a string"
default:
return "unknown"}}Copy the code
Second, exception handling
2.1. Differences with Java exception handling
In Java, try.. catch.. Finally is used to handle exceptions. The code with possible exceptions will be wrapped by a try block, and the relevant exceptions will be caught and processed in the catch block. Finally, the final operation (resource release and lock release) will be performed uniformly through the finally block.
And exception handling of Golang (more aptly error handling) way too much than the simple Java, all possible abnormal methods or code directly return error as the second response value, in the program to judgment, the return value is not empty is processed and interrupt execution immediately, to avoid the spread of the error.
value, err := func(param)
iferr ! =nil {
// An exception is returned and processed
fmt.Printf("Error %s in pack1.Func1 with parameter %v", err.Error(), param1)
return err
}
// Func executes correctly, proceed with the following code
Process(value)
Copy the code
Golang introduces a standard pattern for error handling called the Error interface, which is defined as follows:
type error interface {
Error() string
}
Copy the code
For most functions, if you want to return an error, you can roughly define the following pattern, with error as the last of several return values, but it is not mandatory:
unc main() {
if res, err := compute(1.2."x"); err ! =nil {
panic(err)
} else {
fmt.Println(res)
}
}
func compute(a, b int, c string)(res int, err error) {
switch c {
case "+" :
return a + b, nil
case "-":
return a - b, nil
case "*":
return a * b, nil
case "/":
return a / b, nil
default:
return - 1, fmt.Errorf("Operator is not legal")}}Copy the code
Of course, Golang has the same flexibility as Java for customizing Error types, defining a PathError structure that is an Error type when the Error interface is implemented:
- PathError
type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Error(a) string {
return e.Op + "" + e.Path + ":" + e.Err.Error()
}
Copy the code
- main
func GetStat(name string) (fi FileInfo, err error) {
var stat syscall.Stat_t
err = syscall.Stat(name, &stat)
iferr ! =nil {
// Returns the PathError error type
return nil, &PathError {"stat", name, err}
}
// Program normal, return nil
return fileInfoFromStat(&stat, name), nil
}
Copy the code
This type of exception handling is a hallmark of Golang, and has received mixed reviews:
- Advantages: The code is clear, all exceptions need to be taken into account and handled as soon as they occur
- Disadvantages: Code redundancy, all exceptions need to pass
if err ! = nil {}
To do judgment and processing, can not achieve unified capture and processing
2.2. Comma OK mode
When writing code with Golang, many methods often use this pattern when an expression returns two arguments: OK, the first argument is a value or nil, and the second argument is true/false or an error. In an if conditional statement that requires an assignment, using this pattern to detect the value of the second parameter makes the code look elegant and concise. This pattern is very important in the Golang coding specification. This is also a reflection of Golang’s own multi-return nature.
2.3. Defer, Panic and Recover
Defer, Pannic, and Recover are commonly used keywords in Golang error processing. Their respective uses are:
2.3.1, defer
What defer does is to delay the execution of a piece of code, usually to close a resource or to perform any final actions that must be performed. The defer section will execute regardless of whether something goes wrong, similar to the finally block in Java:
func CopyFile(dst, src string) (w int64, err error) {
srcFile, err := os.Open(src)
iferr ! =nil {
return
}
// Delay closing srcFile
defer srcFile.Close()
dstFile, err := os.Create(dstName)
iferr ! =nil {
return
}
// Delay closing dstFile
defer dstFile.Close()
return io.Copy(dstFile, srcFile)
}
Copy the code
Defer can also execute functions or anonymous functions:
defer func(a) {
// Clean up
} ()
// This is the way to pass parameters to an anonymous function
var i := 1
defer func(i int) {
// Do your complicated cleanup
} (i)
Copy the code
Note that defer uses a stack to maintain the code that needs to be executed, so the defer functions are executed in the reverse order of what defer declared.
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
Copy the code
The execution result is
3
2
1
Copy the code
2.3.2, panic
When panic() is called in the middle of a function execution, the normal function execution flow is immediately terminated, but the statement in the function that deferred execution using the defer keyword is expanded normally, after which the function returns to the calling function. This causes the panic process to be executed layer by layer until all executing functions in the owning Goroutine are terminated.
Panic is similar to the Throw keyword in Java, which is used to throw an error and prevent the program from executing.
Here is the basic usage:
panic(404)
panic("network broken")
panic(Error("file not exists"))
Copy the code
2.3.3, recover
Recover is used to catch errors thrown by Panic and process them. It needs to be used in conjunction with defer, similar to the Catch code block in Java:
func main(a) {
fmt.Println("main begin")
// You must first declare defer, otherwise panic exceptions will not be caught
defer func(a) {
fmt.Println("defer begin")
if err := recover(a); err ! =nil {
// Err is what panic passed in
fmt.Println(err)
}
fmt.Println("defer end")
}()
f()
// there is an error in f, from here the following code will not be executed
fmt.Println("main end")}func f(a) {
fmt.Println("f begin")
panic("error")
// From here the following code will not be executed
fmt.Println("f end")}Copy the code
The final execution result is:
main begin
f begin
defer begin
error
defer end
Copy the code
Use Recover to process the panic directive, and defer must be declared before Panic, otherwise Recover will not catch panic when it occurs.
Third, concurrent programming
3.1 Introduction and comparison of CSP (MPG) concurrency model
In Java, we usually rely on shared memory (global variables) as the medium of communication between threads, but in Golang we use channels as the medium of communication between coroutines, which is also emphasized in Golang:
Do not communicate through shared memory, but communicate to share memory
In Java, communication using shared memory is often thread unsafe, so we often need to do a lot of extra processing, including locking (synchronization), using atomic classes, using volatile to improve visibility, and so on.
3. CSP is an abbreviation for Communicating Sequential Processes (CSP). The core idea of CSP is that multiple threads communicate through channels (corresponding to the Chan structure in Golang), which can be understood as pipes in the operating system or messaging middleware (the difference is that MQ serves intercoroutines, not processes).
In MPG, M refers to the kernel thread, P refers to the context environment, and G refers to the coroutine. In MPG, M and P together constitute the environment in which G can run. M and P are one-to-one correspondence, and different G can be dynamically mapped and controlled through P. So the coroutine in Golang is a user-mode thread built on top of some thread.
The details of how MPG maps, what states G has, and how the scheduler works are not covered in this article.
3.2 Use of Goroutine and Channel
Starting a Thread in Java requires creating a Thread implementation class or a Runnable implementation class, overriding the run method, and starting a Thread to perform a specific task with t.start(), but starting a Goroutine in Golang is as simple as using the go keyword.
// Start the coroutine to execute a piece of code
go fmt.Println("go")
// Start the coroutine execution function
go SomeMethod(1.1)
// Enable the coroutine to execute anonymous functions
go func(a) {
go fmt.Println("go")
}()
Copy the code
A few things to note about coroutines:
- Main runs the main coroutine, and the other coroutines are the guardian coroutines of the main coroutine. When the main coroutine dies, the other coroutines also die
- Coroutines will die after executing the required methods and code, and will also die when the program ends due to panic
A channel is a communication method between Goroutines provided by Golang at the language level. We can use channels to pass messages between two or more Goroutines, so passing an object through a channel behaves similarly to passing arguments to a function, such as passing Pointers.
Channels are type dependent. That is, a channel can only pass a value of one type, which needs to be specified when the channel is declared.
The general declaration form of a channel is:
var chanName chan ElementType
The only difference from a normal variable declaration is the addition of the chan keyword before the type. ElementType specifies the element types that this channel can pass. For example, we declare a pass type int channel:
var ch chan int
Alternatively, we declare a map with a channel of type bool:
var m map[string] chan bool
Initializing a channel is also easy, using the built-in make() function:
ch := make(chan int)
The most common uses of channel include writing and reading. The syntax for writing (sending) a data to a channel is straightforward as follows:
ch <- value
Writing data to a channel usually causes the program to block until some other Goroutine reads data from the channel. The syntax for reading data from a channel is
value := <-ch
If a channel has not written data before, reading data from the channel will also block the program until data is written to the channel. We will also talk later about how to control a channel to accept only write or only read, i.e., one-way channels.
A channel has the following features:
- The read and write operations are atomic operations, so there is no need to worry about data security in the case of concurrency. The write of data in a channel is visible to all coroutines
- Blocked coroutines in channels are FIFO and read and write data in strict enqueue order
- For unbuffered channels, reads and writes occur synchronously, with writes blocking until a reader and reads blocking until a writer, similar to the SynchronousQueue in Java. Read and write to a buffered channel are asynchronous, blocking if the queue is full until there is a reader, and blocking if the queue is empty until there is a writer, similar to LinkedBlockingQueue in Java
- Writes and reads of a channel that is nil are permanently blocked
Fourth, garbage recycling
4.1 Java garbage collection system
Java implements garbage collection on top of the JVM, which is huge, It includes garbage collector (G1, CMS, Serial, ParNew, etc.), garbage collection algorithm (mark-sweep, mark-collation, copy, generational collection), reachable algorithm (reachable analysis, reference counting), reference type, JVM memory model, etc. In JDK 1.7, Java began to use the G1 garbage collector for garbage collection. Its features and collection process are as follows:
4.2 Golang three-color marking method
Three-color marking method, the main process is as follows:
- All objects start out white
- Find all reachable objects starting from root, color them gray, and place them on a pending queue
- Iterate over the gray object queue, and place its reference object in the queue marked gray and itself marked black
- After processing the gray object queue, perform cleaning
To further study can refer to this article: legendtkl.com/2017/04/28/…