This article comes from the public number: Hook hook Java universe (wechat: Javagogo), mo to promote, all dry goods!

The original link: mp.weixin.qq.com/s/K22qpunHA… Author: Snow merciless

Recently I talked to my brothers who were interviewing for the job, and it is not enough to just know Java. I saw a joke on Pulse last week: the mobile terminal development has started internal volume, and the interview is very difficult. In fact, Java is not the same, big data, artificial intelligence, have to begin to understand. It’s been a rough winter…

But I’d like to take a look at Go first, because there are some things in Java that work well with Go. Ah, learn first. This article is about Go concurrency fundamentals.


coroutines

There are no threads in Go, only goroutines. Coroutines are much lighter than threads, and a program can start thousands of Goroutines at will.

Goroutine is scheduled by the Go Runtime, unlike threads. That is, the concurrency of Go is scheduled by Go itself, deciding how many goroutines to execute at the same time, and when to execute which ones. This is completely transparent to us as developers. We just need to tell Go at coding time to launch several Goroutines, and we don’t care how they are scheduled to be executed.

Starting a Goroutine is very simple. Go provides us with the Go keyword, which is much simpler than other programming languages, as shown in the following code:

Func main() {go FMT.Println(" I am main goroutine") FMT.Println(" I am main goroutine") time.sleep (time.second)}Copy the code

This launches a Goroutine that calls the FMT.Println function and prints “Snow heartless”. So there are two goroutines in this code, a main Goroutine that is started by the main function and a goroutine that I started myself with the go keyword.

The syntax for the go keyword can be summarized from the example as follows:

go function()
Copy the code

The go keyword, followed by a method or function call, launches a Goroutine and lets the method run in the newly launched Goroutine.

Running the example above, you can see the following output:

I am main Goroutine flying snow mercilessCopy the code

As can be seen from the output, the program is concurrent. The goroutine started with the go keyword does not block the execution of the main Goroutine, which is why we see the above print.

Tip: In this example, time.sleep (time.second) means to wait a Second. In this case, main Goroutine is told to wait a Second, otherwise the program will exit after main Goroutine finishes executing. You won’t see the relentless snow printing in the new Goroutine that launches.

Channel

So if you start multiple Goroutines, how do they communicate with each other? This is the problem that the ** channels provided by the Go language solve.

1. Declare a channel

In the Go language, declaring a channel is as simple as using the built-in make function, as follows:

ch:=make(chan string)
Copy the code

Where chan is a keyword that indicates the channel type. The following string indicates that the data in a channel is of type string. As you can also see from the channel declaration, chan is a collection type.

Once chan is defined, it can be used. There are only two operations for a chan: send and receive.

  • Receive: Gets the value in chan with the operator<- chan.
  • Send: Sends a value to chan and places the value in chan with the operatorchan <-.

Tip: Note that both the send and receive operators are <-, but in different positions. The received <- operator is to the left of chan, and the sent <- operator is to the right of chan.

Now I modify the previous example to use chan instead of waiting for the time.Sleep function, as shown in the following code:

Func main() {ch:=make(chan string) go func() {FMT.Println(" FMT ") ch <- "goroutine complete "}() FMT.Println(" I am main Goroutine ") v:=<-ch FMT.Println(" chan received from: ",v)}Copy the code

When you run this example, you can see that the program does not exit, and you can see the relentless output of the snow, which achieves the effect of time.Sleep, as shown below:

I am main Goroutine flying snow heartless received in chan value is: Goroutine completeCopy the code

It can be interpreted as follows:

In the example above, we send a value to the variable CH of type chan in the newly launched Goroutine; In main Goroutine, a value is received from the variable ch, and if there is no value in CH, the block waits until there is a value in CH to receive.

Why won’t the program exit before the new Goroutine completes?

Since there is no value in the chan created by make, and main Goroutine wants to get a value from chan, it waits until another Goroutine sends a value to chan.

A channel is a bit like a pipe between two Goroutines. One goroutine can send data to the pipe, and the other one can fetch data from the pipe, similar to what we call a queue.

2. Unbuffered channel

In the example above, the chan created with make is an unbuffered channel with a capacity of 0 and no data to store. Therefore, an unbuffered channel only serves to transfer data, and the data does not stay in the channel. This also means that the send and receive operations of an unbuffered channel take place simultaneously, which can also be called a synchronous channel.

3. Buffered channels

A buffered channel is like a blocking queue, with its internal elements first in, first out. The second argument to make specifies the size of a channel, thus creating a buffered channel, as shown in the following code:

cacheCh:=make(chan int,5)
Copy the code

Create a channel with a capacity of 5 and an internal element of type int. This means that the channel can hold up to 5 elements of type int, as shown in the following figure:

The picture

Have a buffer channel

A buffered channel has the following characteristics:

  • There is a buffer queue inside a buffered channel;
  • The send operation inserts elements to the end of the queue. If the queue is full, it blocks and waits until another Goroutine executes. The receive operation frees up the queue.
  • The receive operation takes the element from the head of the queue and removes it from the queue. If the queue is empty, it blocks and waits until another Goroutine executes, and the send operation inserts the new element.

Because a buffered channel is like a queue, you can get its capacity and the number of elements in it. The following code looks like this:

CacheCh :=make(chan int,5) cacheCh < -2 cacheCh < -3 fmt.Println("cacheCh capacity :",cap(cacheCh),", len(cacheCh))Copy the code

The capacity of a channel can be obtained through the built-in function Cap, that is, the maximum number of elements it can store, and the number of elements in a channel can be obtained through the built-in function Len.

Tip: an unbuffered channel is actually a channel with a capacity of 0. Make (chan int,0).

4. Close the channel

A channel can also be closed using the built-in close function, as shown in the following code:

close(cacheCh)
Copy the code

If a channel is closed, no data can be sent to it, and if it is, a Painc exception will be raised. However, it is possible to receive data from a channel, if there is no data in the channel, with zero values of the element type.

5. A one-way channel

Sometimes, we have some special business requirements, such as limiting a channel to receive but not send, or limiting a channel to send but not receive. Such channels are called one-way channels.

Declaring a one-way channel is also very simple, just declare it with the <- operator, as shown below:

onlySend := make(chan<- int)
onlyReceive:=make(<-chan int)
Copy the code

Note that the position of the declared one-way channel <- operator is the same as the send and receive operations described above.

A one-way channel is used more often in the arguments of a function or method to prevent operations from affecting the channel.

As for the counter function in the following example, its parameter out is a channel that can only be sent, so when the parameter out is used in the counter function, it can only be sent, and if it is received, the program will fail to compile.

Unter (out chan< -int) {// Function contents use variable out, can only be sent}Copy the code

6. Select + channel sample

Suppose I want to download a file from the Internet, and I start three Goroutines to download it and send the results to three channels. The result of which channel will be used if the first one is downloaded well.

In this case, if we tried to get the results of the first channel, the program would be blocked, unable to get the results of the remaining two channels, and unable to determine which downloaded first. In the Go language, multiplexing can be achieved through the SELECT statement, which is formatted as follows:

select {
case i1 = <-c1:
     //todo
case c2 <- i2:
 //todo
default:
 // default todo
}
Copy the code

The overall structure is very similar to switch, with case and default, but the select case is a channel that can be operated.

Note: Multiplexing can be simply understood as: Select can listen to any data generated by any of N channels, and then perform the corresponding branch to receive the data and process it.

With the SELECT statement, you can implement the download example. The following code looks like this:

Func main() {make(chan string) secondCh := make(chan string) secondCh := make(chan string) threeCh := make(chan string) Go func() {firstCh <- downloadFile("firstCh")}() go func() {secondCh <- downloadFile("secondCh") }() go func() {threeCh <- downloadFile("threeCh")}() select { case filePath := <-firstCh: fmt.Println(filePath) case filePath := <-secondCh: fmt.Println(filePath) case filePath := <-threeCh: FMT.Println(filePath)}} func downloadFile(chanName string) string {// time.Sleep(time.Second) return chanName+":filePath" }Copy the code

If one of these cases can be executed, the SELECT statement selects that case for execution. If more than one case can be executed at the same time, the select statement selects one at random so that each case has an equal chance of execution. If a SELECT does not have any cases, it will wait forever.

conclusion

In the Go language, communication is encouraged to share memory through communication, rather than through shared memory communication. In fact, it is advocated to transfer data through channel sending and receiving messages, rather than by modifying the same variable. Therefore, channel is preferred in data flow and transfer scenarios because it is concurrency safe and has good performance.


Welcome big guys to pay attention to the public account of the Java universe (wechat: Javagogo), refuse hydrology, harvest dry goods!