This is a creation in Article, where the information may have evolved or changed.
On a (one-day learning _go from the mistakes of the basic one) said part of the Golang error prone place, in order to let the reader clear learning, I decided to separate.
New () and make () use
arrays, structs, and all value types can use new, slice, map, and channel, using make.
What a simple concept.
In order to have a deeper understanding, do not confuse the use of new and make memory analysis to prevent the jump pit
type Person struct { name string age int}func main() { p1 := Person{"wuxiao", 10} p2 := &PersonP{"wuxiao", 15} // == new(Person)}
P1 Memory Status Graph
It can be seen that P1 in memory is a contiguous block of memory
P2 Memory Status graph
The hexadecimal value represents the pointer address 0x ..
and references the actual data.
In some blogs, the following examples are often mentioned:
p1 := Person{"wuxiao", 10}func setName(p Person) { p.name = "xiaobai"}
The above code makes a value copy.
Value copy
p2 := &PersonP{"wuxiao", 15}func setName(p *Person) { p.name = "dabai"}
Pointer copy
As seen from the above analysis, the first case in function SetName () Modifies p does not modify the original data, but in the second case it is definitely modified because the address stored in P references the original data block.
Summary: Passing a value type as a parameter to a function or as a receiver of a method seems to be an abuse of memory because the value type is always a pass-through copy. On the other hand, the value type of memory is allocated on the stack, and memory allocations are fast and inexpensive. If you pass a pointer instead of a value type, the Go compiler will, in most cases, assume that you need to create an object and move the object onto the heap, resulting in additional memory allocations: Therefore, when using pointers instead of value types as arguments, you need to use them according to your needs.
Next I'll take a slice to understand make.
We create a slice of the underlying array with 6 elements, using the make ([] int,len,cap) syntax to specify the capacity. As follows:
arr = make([]int, 5, 6) arr[3] = 44 arr[4] = 333
Slice memory state
What happens when we create another sub-slice and change some elements?
arr := make([]int, 5, 6) arr[3] = 44 arr[4] = 333 subArr := arr[1:4] subArr[1] = 77
Slice
Modifying the Subarr also modifies the underlying array, which is why slices are used extensively in Golang.
When adding elements to a slice using the built-in function append (), it does a lot of complex work internally, to allocate memory.
arr := make([]int, 5, 6) subArr := arr[:] arr = append(arr, 1, 2)
Using append () checks if the slice has an unused number of containers, and if not, allocates more memory. Allocating memory is a fairly expensive operation, so append attempts to estimate the operation by increasing twice times the original capacity at a time. Allocating more memory at once is often more efficient and faster than allocating less memory multiple times.
Allocating more memory usually means allocating new memory and copying the data from the old array to the new array (resulting in a change in the address value).
Memory changes after append
It can be seen that there will be two different underlying arrays, which may inadvertently be a mistake for beginners.
Process and channel use
The process and the channel I think is the core of Golang, in order to be easy to understand, to prevent errors in writing code, I will follow some examples to explain the core.
Look at the point here.
What is the difference between unbuffered and buffered channel?
One is synchronization.
A non-synchronous.
Simply understand:
unbuffered use of the channel to send data, you must have this channel type of the process to receive data to continue to send data, or enter a permanent block (deadlock).
Buffered if the buffer size is 1, when the channel sends the data, only when the second value is placed, the first one has not been taken away, the time will be blocked.
The following three examples better illustrate synchronization and non-synchronization:
data := make(chan string) //因为data没有值,所以会选择default执行(发送接收数据都 //是一样的道理),这里就达成看一个无阻塞的效果。 select { case msg := <-data: fmt.Println("received data", msg) default: fmt.Println("no data.....") }
执行结果:no data.....
data := make(chan string, 1) //给通道加上缓冲的话,会怎么选择? data <- "wuxiao" //猜测:data已经有值,所以会选择 received data 执行。 select { case msg := <-data: fmt.Println("received data", msg) default: fmt.Println("no data.....") }
执行结果:received data wuxiao
- As mentioned above, when the buffer is not full, sending data to a buffered cache is not blocked, and the data read from the buffer is not blocked.
data := make(chan string)//没有缓冲了 data <- "wuxiao" //因为该channels没有缓冲,发送数据,导致死锁(有的书上写为永远阻塞) select { case msg := <-data: fmt.Println("received data", msg) default: fmt.Println("no data.....") }
执行结果: fatal error: all goroutines are asleep - deadlock!
Why is there an error?
Official explanation: unbuffered channels combine communication-the exchange of a value-with synchronization-guaranteeing that's a calcul Ations (Goroutines) is in a known state
The non-buffered channel communicates to ensure that two processes are in a known state.
pipelining mode
In the development we can use the co-path and the channel to achieve concurrency, but also according to their own needs to use concurrent design patterns to improve efficiency, concurrent design patterns have fan-in and fan-out, pipeline and so on.
Pipeline mode can be easily understood by a plurality of stages, the adjacent two phases are connected by the channel, each stage has its own goroutine. Each phase performs the following three operations (in addition to the first and last stages):
1. 通过 channel 接收数据上流的数据2. 对接收到的数据进行操作3. 将新生成的数据通过 channels 发送数据给下游
Obviously, the first phase is only the send pipeline, and the last phase is only the receive pipeline.
It is generally said that the first stage can be understood as "producer" and that the last stage is understood as "consumer".
//第一阶段是为gen 函数,首先启动一个 goroutine,//通过goroutine 把数字发送到 channel,当数字发送完时关闭channel。func gen(nums ...int) <-chan int { out := make(chan int) go func() { for _, n := range nums { out <- n } close(out) }() return out}//第二阶段是 sq 函数,它从第一阶段返回的通道来接受整数,把//所接收的整数发送自己创建的通道中并返回给下游,并且等第一//阶段通道数字全部发给下游会关闭了上流通道,然后在关闭自己创建的管道。func sq(in <-chan int) <-chan int { out := make(chan int) go func() { for n := range in { out <- n * n } close(out) }() return out}//main 函数为流水线的最后一个阶段。//会从第二阶段接收数字,并逐个打印出来,直到来自于上游的接收管道关闭func main() { //由于 sq 函数的channel 类型一样,所以组合任意个 sq 函数 for n := range sq(sq(gen(2, 4, 5)) ){ fmt.Println(n) }}
If I change the last stage above
out := range sq(sq(gen(2, 4, 5)) ) fmt.Println(<-out) // 16 or 256 , 625
There is a resource leak here. On the one hand goroutine consumes memory and runtime resources, on the other hand the heap references in the Goroutine stack prevent the GC from performing a recycle operation. Since goroutine cannot be recycled, they have to quit on their own.
So how to solve this problem?
Use an explicit cancellation.
In the go language, we can do this by closing a channel, because the receive operation data on a closed channel is always returned immediately, and the return value is the 0 value of the corresponding type.
To put it simply, a closed operation on a pipe is actually a broadcast signal to all receivers.
func main() { // 当关闭 done channel时 //给所有 goroutine发送信号 // 接收到后都会正常退出。 done := make(chan struct{}) defer close(done) in := gen(done, 2, 4, 5) c1 := sq(done, in) out := sq(done, c1) fmt.Println(<-out) }func sq(done <-chan struct{}, in <-chan int) <-chan int { out := make(chan int) go func() { defer close(out) for n := range in { select { case out <- n * n: case <-done: return } } }() return out}
In fact, the above mode is to remind us to use the process and channel when careful resource leaks,
Close closes after the data is sent to prevent deadlocks.
Some other modes of interest can click View mode to learn address one and pattern learning address two
I still have a lot of shortcomings in this piece, I hope you can discuss more. Common progress