Three core designs of Go: Interface, Goroutine and Channel

Less is More — Wikipedia

interface

Go is an interface oriented programming language, interface design is naturally the most important. The neat thing about the interface design in Go is that empty interfaces can be used as the “Duck” type, which gives static languages like Go a certain amount of dynamism without losing the compile-time checks that static languages have for type safety.

source code

From the underlying implementation point of view, an interface is actually a structure containing two members. One of the member Pointers points to an area containing type information, which can be understood as a virtual table pointer, while the other points to concrete data, which is actually referenced by the interface.

Where interfaceType contains some information about the interface itself, _type represents the specific implementation type, which is described in detail in eface below, bad is a state variable, fun is an array of Pointers of length 1, Store the pointer to method after the address of fun[0]. The Go Runtime package contains a hash table that can be used to obtain the ITAB. The link and inhash are used to store the corresponding position in the hash table and set the identifier. The main code is as follows:

The structure of Itab is as follows:

Where interfaceType contains some information about the interface itself, _type represents the specific implementation type, which is described in detail in eface below, bad is a state variable, fun is an array of Pointers of length 1, Store the pointer to method after the address of fun[0]. The Go Runtime package contains a hash table that can be used to obtain the ITAB. The link and inhash are used to store the corresponding position in the hash table and set the identifier. The main code is as follows:

The implementation of the empty interface is slightly different. Any object in Go can be represented as interface{}, similar to void* in C, and interface{} contains type information.

The structure of Type is as follows:

i_example

As for the application of interface, here is a simple example of Go interacting with the Mysql database.

Mysql > create a task information table in mysql test library

The most basic four operations of database interaction are: add, delete, change and check. Here, take query as an example:

Go to query all the data in this table

Among them:

This code can realize the look-up table this simple logic, but there is a small problem is that we only four fields this table structure is simple, if in a 20 + field even more table to query, the code is too inefficient, this time we can introduce interface {} to optimization. The optimized code looks like this:

Interface {} can store any type of data, so we can construct two arrays of args and VALUES, where each value of ARgs points to the address of the corresponding value of values, to read the data in batches and subsequent operations. It is worth noting that Go is a strongly typed language. In addition, different interface{} contains different type information, which requires type conversion when performing related operations such as assignment.

Go also provides good support for Mysql transactions. General operations use db object methods and transactions use SQL.tx objects. Tx objects can be created using db’s Begin method. Tx objects also have Query,Exec, and Prepare methods for database interaction, similar to db operations. After the query or modification operation is complete, you need to call Commit() or Rollback() of the TX objects.

For example, you now need to use a transaction to update the user table you created earlier

Note: The “:=” and “=” operators should not be confused

If no transaction is required, the code for the update looks like this:

Contrast this with the code above, which added a few lines of code because it was simpler, and replaced the DB object with the TX object.

goroutine

Concurrent: dealing with different things at the same time Parallel: doing different things at the same time

Go supports parallelism at the language level, and Goroutine is at the heart of Go’s parallel design. In essence, Goroutines are coroutines, with a separate call stack that can manage itself. You can think of Goroutines as lightweight threads. But Thread is operating system scheduled, preemptive. Goroutine is scheduled through its own scheduler.

scheduler

The Go scheduler implements the G-P-M scheduling model, which has three important structures: M, P and G

M : Machine (OS thread)

P : Context (Go Scheduler)

G : Goroutine

The underlying data structure looks like this:

The interaction between M, P, and G can be illustrated by the following diagrams from the Go Runtime Scheduler

In the figure above, there are two physical threads, M, each with a context, P, and each with a running Goroutine G. The grayed G’s in the figure are not running, but are in the ready state, waiting to be scheduled. The runQueue is maintained by P.

M1 in the figure may have been created or fetched from the thread cache. When M0 returns, it must try to get P to run G. Normally, it will try to “steal” a P from another thread. Failing that, it will put G in a global runqueue and put itself in the thread cache. All ps periodically check the global runqueue, otherwise G on the global runqueue will never execute.

In another case, the task G assigned by P is quickly completed (due to uneven distribution), which results in some P’s being idle while the system is still running. But if the Global Runqueue runs out of task G, then P has to take some G from other PS to execute it. Typically, if A P steals a task from another P, it ‘steals’ half of the runqueue, ensuring that each thread is fully utilized.

How does P “steal” to G from queues maintained by other PS? This involves the work-Stealing algorithm, on which more information can be found in this article.

g_example

A simple example of how Goroutine works

This code is very simple, with two different Goroutines running asynchronously

The running results are as follows:

Then make a small change, just switch the positions of the two functions in main(), and change the rest of the code:

One interesting thing happens:

The reason is simple, too, because when main() returns, it does not wait for other goroutines (non-main Goroutines) to finish. In the above example, the main function creates a new Goroutine after executing the first say(), and the program ends before it can be executed, so the above results appear.

channel

Goroutine runs in the same address space, so access to shared memory must be synchronized. The Go language provides a good communication mechanism, channel, to meet the communication between goroutine data. A channel is similar to a two-way pipe in a Unix shell: you can send and receive values through it.

source code

The structure of Waitq is as follows

A channel is a queue with a lock. Sendx and Recvx can be regarded as producer and consumer queues, respectively storing goroutine waiting for read operation on channel and Goroutine waiting for write operation on channel, as shown in the figure below.

Write channel (ch < -x) as follows (only core code selected) :

It can be divided into three cases:

  • A goroutine is blocked on a channel, and chanbuf is empty, sending data directly to that Goroutine.
  • Chanbuf has space available: put data into Chanbuf.
  • Chanbuf no space available: blocks the current goroutine.





Reading channel(<-ch) is similar to sending and is not shown in code.

c_example

A simple example of a Goroutine communicating with a channel is simple:

Here we define two cached channels, jobs and Results. If we change both of them to uncached channels, we will get an error, but we can handle it as follows:

A more common channel operation is select. When there are multiple channels, you can use select to listen to the flow of data on the channel.

Because both CH1 and CH2 are empty, neither case1 nor case2 will be read successfully. Select executes the default statement.