This is a creation in Article, where the information may have evolved or changed.
First, Golang concurrency theory
Golang references the CSP of C.A.R Hoare in concurrent design, namely communicating sequential processes concurrency model theory. But as John Graham-cumming says, most golang programmers or enthusiasts just stay on the "know" level and understand the CSP theory is not much, after all, most programmers are engaged in engineering. But those who want to learn about CSP can download the latest version of the CSP paper from here.
Wikipedia outlines the differences between the CSP model and another concurrency model Actor model:
The actor model is broadly speaking similar to the CSP model. But there are some fundamental differences between the two models in terms of the primitives provided:
The –CSP model process is anonymous, while the actor in the Actor model has an identity.
The message delivery of the –CSP model includes a rendezvous point between the sending and receiving message processes, that is, the sender can only send messages when the recipient is ready to receive the message. Instead, message delivery in the Actor model is asynchronous, that is, the sending and receiving of messages does not have to be done at the same time, and the sender can send the message before the receiver is ready to receive the message. These two schemes can be thought of as being dual to each other. In some sense, the intersection-based system can simulate the asynchronous message system by constructing a buffered communication method. The asynchronous system can synchronize the sender and receiver to simulate the intersection-point communication mode by constructing a message/response protocol.
–CSP uses an explicit channel for message delivery, while the Actor model sends a message to the named destination actor. Both of these methods can be considered to be dual. In a sense, a process can receive messages from a channel that actually has an identity, and by constructing the actors into a class channel behavior pattern can also break the name coupling between actors.
Second, Go Channel basic operation syntax
The basic operating syntax for Go channel is as follows:
c: = Make (chan bool)//Create a non-buffered bool channel
C <-x//Send a value to a channel
<-c//Receive a value from a channel
x = <-c//receives a value from channel C and stores it in X
X, OK = <-C//receives a value from the channel, if the channel is closed or no data, then OK will be set to False
the non-buffered channel is popular for both communication and synchronization.
Iii. scenario where channel is used as signal (Signal)
1. Wait for an event
Waiting for an event, sometimes by close a channel, is enough. For example:
Testwaitevent1.go
Package Main
Import "FMT"
Func Main () {
Fmt. Println ("Begin doing something!")
c: = Make (chan bool)
Go func () {
Fmt. Println ("Doing something ...")
Close (c)
}()
<-c
Fmt. Println ("done!")
}
Here main goroutine through "<-c" to wait for "completion event" in Sub Goroutine, sub Goroutine to promote this event through close channel. Of course, you can also act as an event notification by writing a bool value to the channel. Main Goroutine blocks the wait when there is no data readable on channel C.
About the results of the output:
According to the definition of the order of the close channel and the recv from channel in the Go memory model: Theclosing of a channel happens before a receive that Returns a zero value because the channel is closed.
We can easily determine the output of the above program:
Begin doing something!
Doing something ...
done!
If Close (c) is replaced with c<-true, it is defined in the Go memory model:A receive from A unbuffered channel happens Before the Send on that channel completes.
"<-c" to be completed before "c<-true", but also does not affect the output order of the log, the output is still the above three rows.
2. Collaboration with multiple Goroutines
Ditto, the close channel can also be used to collaborate on multiple goroutines, such as the following example, we created 100 worker Goroutine, which are blocked in the "<-start" after being created , until we give the start signal in main goroutine : "Close (start)", these goroutines begin to actually run concurrently.
Testwaitevent2.go
Package Main
Import "FMT"
Func worker (start chan bool, index int) {
<-start
Fmt. Println ("This is Worker:", index)
}
Func Main () {
Start: = Make (chan bool)
For i: = 1; I <= 100; i++ {
Go worker (start, i)
}
Close (start)
Select {}//deadlock we expected
}
3. Select
"Basic operation of Select"
Select is a unique operation of the go language, and with select we can send/receive operations on multiple channel simultaneously. The following is the basic operation of SELECT.
Select {
Case x: = <-Somechan:
... Use X for some action
Case Y, OK: = <-Someotherchan:
... Use Y for some action,
Check the OK value to determine if the Someotherchan has been closed
Case Outputchan <-Z:
When the Z-value is successfully sent to the channel
Default
... This branch is executed when none of the above case can communicate
}
"Customary law: For/select"
When we use SELECT, we seldom just evaluation it once, we often use it in conjunction with for {} and choose the appropriate time to exit from for{}.
for {
Select {
Case x: = <-Somechan:
... Use X for some action
Case Y, OK: = <-Someotherchan:
... Use Y for some action,
Check the OK value to determine if the Someotherchan has been closed
Case Outputchan <-Z:
When the Z-value is successfully sent to the channel
Default
... This branch is executed when none of the above case can communicate
}
}
"End Workers"
The following is a common method of terminating sub worker Goroutines, where each worker goroutine a die channel through select to get the exit notification of main goroutine in a timely manner.
Testterminateworker1.go
Package Main
Import (
"FMT"
"Time"
)
Func worker (Die chan bool, index int) {
Fmt. Println ("Begin:this is Worker:", index)
for {
Select {
Case XX:
The branch of doing things
Case <-die:
Fmt. Println ("Done:this is Worker:", index)
Return
}
}
}
Func Main () {
Die: = Make (chan bool)
For i: = 1; I <= 100; i++ {
Go worker (die, I)
}
Time. Sleep (time. Second * 5)
Close (die)
Select {} //deadlock We expected
}
"End Validation"
Sometimes after a worker is terminated, main Goroutine wants to confirm that the worker routine is actually exiting, using the following method:
Testterminateworker2.go
Package Main
Import (
"FMT"
"Time"
)
Func worker (Die chan bool) {
Fmt. Println ("Begin:this is Worker")
for {
Select {
Case XX:
The branch of doing things
Case <-die:
Fmt. Println ("Done:this is Worker")
Die <-True
Return
}
}
}
Func Main () {
Die: = Make (chan bool)
Go Worker (die)
Die <-True
<-die
Fmt. Println ("Worker Goroutine has been terminated")
}
"The closed channel will never block."
The following shows the results of reading and writing on a channel that has been closed:
Testoperateonclosedchannel.go
Package Main
Import "FMT"
Func Main () {
CB: = Make (chan bool)
Close (CB)
x: = <-CB
Fmt. Printf ("% #v \ n", X)
X, OK: = <-CB
Fmt. Printf ("% #v% #v \ n", X, OK)
CI: = make (chan int)
Close (CI)
Y: = <-ci
Fmt. Printf ("% #v \ n", y)
CB <-True
}
$go Run Testoperateonclosedchannel.go
False
False false
0
Panic:runtime Error:send on closed channel
You can see that a read operation is performed on an already close unbuffered channel, returning a 0 value of the channel corresponding type, such as the BOOL channel returning the False,int channel returns 0. But writing to the close channel will trigger panic. However, neither reading nor writing can cause blocking.
"Close the channel with the cache"
What happens when you switch unbuffered channel to buffered channel? Let's look at the following example:
Testclosedbufferedchannel.go
Package Main
Import "FMT"
Func Main () {
c: = make (chan int, 3)
C <-15
C <-34
C <-65
Close (c)
Fmt. Printf ("%d\n", <-c)
Fmt. Printf ("%d\n", <-c)
Fmt. Printf ("%d\n", <-c)
Fmt. Printf ("%d\n", <-c)
C <-1
}
$go Run Testclosedbufferedchannel.go
15
34
65
0
Panic:runtime Error:send on closed channel
You can see that the channel with the buffer is slightly different. Although it is close, we can still read the 3 values written before closing. On the fourth read, the 0 value of the channel type is returned. Writing to this type of channel also triggers panic.
"Range"
The range in Golang is often used alongside the channel to read all values from the channel. The following is a simple example:
Testrange.go
Package Main
Import "FMT"
Func Generator (Strings Chan string) {
Strings <-"Five hour ' s New York jet lag"
Strings <-"and Cayce Pollard wakes in Camden town"
Strings <-"to the dire and ever-decreasing circles"
Strings <-"of disrupted circadian rhythm."
Close (strings)
}
Func Main () {
Strings: = Make (Chan string)
Go Generator (strings)
For s: = Range Strings {
Fmt. Printf ("%s\n", s)
}
Fmt. Printf ("\ n")
}
Four, Hidden state
Here's an example of how the channel is used to hide the state:
1. Example: Unique ID Service
Testuniqueid.go
Package Main
Import "FMT"
Func newuniqueidservice () <-chan string {
ID: = Make (Chan string)
Go func () {
var counter int64 = 0
for {
ID <-fmt. Sprintf ("%x", counter)
Counter + = 1
}
}()
Return ID
}
Func Main () {
ID: = Newuniqueidservice ()
For I: = 0; I < 10; i++ {
Fmt. Println (<-id)
}
}
$ go Run testuniqueid.go
0
1
2
3
4
5
6
7
8
9
The newuniqueidservice is associated with the main goroutine via a channel, and the main goroutine does not need to know the details of the UniqueID implementation and the current state, just get the latest ID through the channel.
V. Default situation
I think John Graham-cumming here mainly wants to tell us the practice usage of the default branch of SELECT.
1. Select for non-blocking receive
idle:= make (Chan []byte, 5)//Use a buffered channel to construct a simple queue
Select {
Case B = <-idle://Attempt to read from the idle queue
...
Default://Queue empty, assign a new buffer
Makes + = 1
b = Make ([]byte, size)
}
2. Select for non-blocking send
idle:= make (Chan []byte, 5) //Use a buffered channel to construct a simple queue
Select {
Case Idle <-B://Try inserting a buffer into the queue
...
Default://Queue full?
}
VI. Nil Channels
1, nil channels blocking
The read and write operation of a channel without initialization will be blocked, as in the following example:
Package Main
Func Main () {
var c Chan int
<-c
}
$go Run Testnilchannel.go
Fatal Error:all Goroutines is asleep–deadlock!
Package Main
Func Main () {
var c Chan int
C <-1
}
$go Run Testnilchannel.go
Fatal Error:all Goroutines is asleep–deadlock!
2. Nil channel is useful in select
Look at the following example:
Testnilchannel_bad.go
Package Main
Import "FMT"
Import "Time"
Func Main () {
var C1, C2 chan int = make (chan int), make (chan int)
Go func () {
Time. Sleep (time. Second * 5)
C1 <-5
Close (C1)
}()
go func () {
time. Sleep (time. Second * 7)
C2 <- 7
Close (C2)
} ()
for {
Select {
case x: = <-c1:
FMT. PRINTLN (x)
case x: = <-c2:
FMT. PRINTLN (x)
}
}
fmt. Println ("Over")
}
We originally expected the program to output 5 and 72 numbers alternately, but the actual output was:
5
0
0
0
... ... 0 dead Loop
Then carefully analyze the code, the original select each time in case order evaluate:
– The front 5s,select has been blocked;
– Section 5S,C1 returns a 5 after being close, "case x: = <-c1" This branch returns, select Output 5, and re-select
– The next select is also starting from the "case x: = <-C1" Branch evaluate, because C1 is close, according to the previous knowledge, close channel will not block, we will read out the channel corresponding type of 0 value, this is 0 The select outputs 0 again, and even if C2 returns, the program does not go to the C2 branch.
– And so on, program infinite loop output 0
We use the nil channel to improve the program to achieve our intentions, the code is as follows:
Testnilchannel.go
Package Main
Import "FMT"
Import "Time"
func main () {
var c1, C2 Chan int = make (chan int), make (chan int)
go func () {
time. Sleep (time. Second * 5)
C1 <- 5
Close (C1)
} ()
Go func () {
Time. Sleep (time. Second * 7)
C2 <-7
Close (C2)
}()
for {
Select {
Case x, OK: = <-c1:
If!ok {
C1 = Nil
} else {
Fmt. PRINTLN (x)
}
Case x, OK: = <-c2:
If!ok {
C2 = Nil
} else {
Fmt. PRINTLN (x)
}
}
If C1 = = Nil && C2 = = Nil {
Break
}
}
Fmt. Println ("Over")
}
$go Run Testnilchannel.go
5
7
Over
It can be seen that the next select will block on the channel by placing the closed channel to nil, allowing the Select to continue with the following branch evaluation.
Seven, timers
1. Time-out mechanism timeout
A select with a time-out mechanism is a regular tip, and here is the sample code that implements the 30s timeout select:
Func worker (start chan bool) {
Timeout: = time. After (* time. Second)
for {
Select {
... do some stuff
Case <-Timeout:
Return
}
}
}
2, Heartbeat Heartbeart
Similar to the timeout implementation, here is a simple heartbeat Select implementation:
func worker (start chan bool) {
Heartbeat: = time. Tick (* time. Second)
for {
Select {
//... do some stuff
Case <-Heartbeat:
/... do heartbeat Stuff
}
}
}