1. What are the advantages or features of Go compared to other languages?

  • Go allows cross-platform compilation, compiling a binary executable that can be deployed directly on the corresponding system and run.
  • Go inherently supports high concurrency at the language level, through goroutine and channel. channel is based on the CSP concurrency model, which is called ``sharing memory through communication’’; Go implements its own scheduling mechanism in the runtime: GMP, which reduces the cost of switching between kernel and user states.
  • Go’s code style is mandatorily uniform and will not compile if it does not follow the rules.

2. GMP model in Golang?

The GMP model is golang’s own scheduling model, which abstracts the following three structures.

  • G: which is the concurrent goroutine, managed by Go runtime. We can think of it as a user-level thread.
  • P: processor. Whenever a goroutine is created, it is added to the local queue of goroutines on P, and if P’s local queue is full, it is maintained in the global queue.
  • M: System threads. There are scheduling functions on M, which is the real scheduling executor. M needs to be bound to P and will let P pick a goroutine to execute according to the following principle.

The priority is to get the goroutine from P’s local queue for execution; if the local queue is not available, it will get it from the global queue, and if the global queue is also not available, it will steal the goroutine from the other P.

3. What are the features of goroutine’s concurrent threads, compared to threads?

goroutine is very light, with an initial allocation of only 2KB, which is automatically expanded when the stack space is not enough. At the same time, it stores its own execution stack information, which can be used to recover contextual information during scheduling.

Threads are heavier and generally have an initial size of a few MB (different systems allocate different sizes) and are scheduled by the operating system, which is the basic unit of scheduling. golang implements its own scheduling mechanism, and goroutine is its basic unit of scheduling.

4. Go’s garbage collection mechanism?

Go uses a three-color marking method that divides objects in memory into three types.

  • White objects: objects that have not been used.
  • Gray objects: objects that have references to the current object, but have not been scanned further for the referenced object.
  • Black objects, the above mentioned gray objects have all been scanned for reference objects, so you don’t need to scan it next time.

When garbage collection starts, Go will mark the root object as gray and the other objects as white, and then iterate through the search from the root object, following the above definition to keep scanning the gray object to mark it. When there are no gray objects, all objects have been scanned, and then you can start clearing the white objects.

5. How does go’s memory allocation work?

Go’s memory allocation is based on Google’s TCMalloc allocation algorithm, the core idea of which is memory pooling + multi-level object management. The core idea is memory pooling + multi-level object management. memory pooling mainly pre-allocates memory to reduce the frequency of requests to the system; multi-level objects are: mheap, mspan, arenas, mcentral, mcache. they use mspan as the basic allocation unit. The specific allocation logic is as follows.

  • When the object to be allocated is larger than 32K, it is allocated from mheap.
  • When the object to be allocated is less than or equal to 32K and greater than 16B, it is allocated from the mcache on P. If mcache has no memory, it is fetched from mcentral. If mcentral does not have it either, it is requested from mheap. If mheap also does not have memory, it requests memory from the operating system.
  • When the object to be allocated is less than or equal to 16B, allocate it from the micro allocator on mcache.

6. What is the internal implementation of the channel?

The channel maintains two internal goroutine queues, one for data to be sent and the other for data to be read.

Whenever the number of goroutines that can be buffered exceeds the number of reads and writes to the channel, the current goroutine is hung on the corresponding queue until another goroutine performs the opposite read or write operation to bring it back up.

7. What happens if I read or write to a channel that has been closed?

When a channel is closed, if you continue to write data to it, the program will simply panic. If you read the closed channel, it will not generate pannic and you can still read the data. However, if a closed channel has no data to read, it will get a zero value, which is the default value of the corresponding type.

In order to know whether the current channel is closed or not, you can use the following write method to determine.

1
2
3
if v, ok := <-ch; !ok {
 fmt.Println("channel 已关闭,读取不到数据")
}

You can also keep fetching data from the channel using the following writeup.

1
2
3
for data := range ch {
 // get data dosomething
}

This usage ends the for loop after the data in the channel has been read, and executes the code that follows.

8. Why is map not thread-safe?

When a map is scaled up or down, it needs to migrate data. The migration process does not use a locking mechanism to prevent concurrent operations, but instead marks a bit with a 1 to indicate that data is being migrated. If another goroutine also writes to the map, it will panic when it detects the 1 bit.

If we want a concurrency-safe map, we need to use sync.map.

9. Why does the key of a map have to be of comparable type?

When a new key - value is inserted, the hash operation is performed on the key to get a hash value, and then the lower bit of the hash value (the number of bits depends on the number of buckets, for example, if the number of buckets is 5 at the beginning, the lower 5 bits are taken) is used to decide which bucket to hit. for example, if the number of buckets is 5 at the beginning, then the lower 5 bits are taken) to decide which bucket to hit.

After a bucket is hit, it will decide which of the 8 keys is hit based on the higher 8 bits of hash value. If, unfortunately, there is a hash conflict, i.e., there is already other key in that position, it will look for other empty positions to insert. If they are all full, the overflow pointer is used to point to a new bucket and the search steps are repeated.

From the above process, we can see that when determining hash conflicts, i.e., whether there is already other key in the location, we must compare, so the key must be of comparable type. Slice, map, and function cannot be used as keys.