Defragmentation of concurrent golang

Concurrent and Parallel

Concurrency: the same period of time to perform multiple tasks.
Parallel: the same time to perform multiple tasks.

Go through the implementation of concurrent language goroutine. goroutine similar thread, the thread belonging to the user mode, we can create hundreds of thousands of concurrent goroutine work as needed. goroutine is done by running the scheduling of the language go, and the thread is done by the operating system scheduling.
Go language also provides a channel of communication carried out between multiple goroutine. goroutine and the channel is an important foundation to achieve language go to uphold the CSP (Communicating Sequential Process) concurrency model.

goroutine

When in Java / c ++, we want to achieve concurrent programming, we often want to protect their own thread pool, and to make their own packed one after another task and then themselves to schedule threads to perform tasks and maintain on-line text switching, all of this is usually We will spend a lot of mental programmers. Can not have a mechanism, the programmer only needs to define a number of tasks, allow the system to help us to assign these tasks to the CPU concurrency enforce it? Go language goroutine is such a mechanism, the reason can be called a modern language Go programming language, because it has built-in language level mechanism for scheduling and context switching.

Use goroutine

Go keywords used in the program to create a goroutine go to a function. A function can be created multiple goroutine, a goroutine must correspond to a function.

Start a single goroutine

Start goroutine way is very simple, just add a keyword go in front of the called function (normal function and anonymous functions).
for example:

package main

import (
    "fmt"
)

func hello() {
    fmt.Println("hello goroutine")
}
func main() {
    hello()
    fmt.Println("main goroutine done!")
}

Hello this example, the function and the following statement is serial, is the result of the execution of the print main goroutine done after printing hello goroutine!
Next we call the function hello plus go in front of the keyword, which is a start, executive hello goroutine this function.

func main() {
    go hello()
    fmt.Println("main goroutine done!")
}

This time the results of the implementation to print only the main goroutine done!, And did not print hello goroutine, why?
When the program starts, Go program will () function creates a default goroutine for the main. When the main function returns the goroutine is over, all () function to start goroutine will end with the main, goroutine main function where the king is like a night game in the right, the other like a different ghost goroutine those different ghost king to die night it will convert all of the GG.
So we find ways to make the main function wait hello function, the easiest way is to sleep rough up.

func main(){
    go hello()
    fmt.Println("main goroutine done!")
    time.Sleep(time.Second)
}

The implementation of the above code you will find that this time the first print main goroutine done!, Then immediately print Hello Goroutine!
First, why print main goroutine done! Because we need to spend some time in creating a new goroutine time, but this time goroutine main function where execution is to continue.

sync.WaitGroup

Synchronization using blunt time.sleep in code is certainly not appropriate, Go sync.WaitGroup language can be used to implement concurrent tasks. sync.WaitGroup There are several ways to look at:

Method Name Function
(WG WaitGroup) the Add (int Delta) Delta counter +
(WG
WaitGroup) the Done () calculator -1
(WG * WaitGroup) the Wait () blocks until the counter becomes 0

Internal sync.WaitGroup maintains a counter, the counter value can increase and decrease. For example, when we started the N concurrent tasks, will be the value of N counter is incremented, the counter is decremented by calling the Done () method of each completed task to wait by calling the Wait () concurrently executing the task, when the counter value 0, it means that all concurrent tasks have been completed.
We use sync.WaitGroup the above code to optimize it:

var wg sync.WaitGroup

func hello() {
    defer wg.Done()
    fmt.Println("hello 1")
}
func main() {
    wg.Add(1)
    go hello()
    fmt.Println("main 2")
    wg.Wait()
}

Note that sync.WaitGroup is a structure, when the need to pass a pointer passed.

Launch multiple goroutine

Go concurrency in languages ​​is so simple, we can also launch multiple goroutine. Let's take an example:

var wg sync.WaitGroup

func hello(i int) {
    defer wg.Done()
    fmt.Println("hello ,", i)
}
func main() {
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go hello(i)
    }
    wg.Wait()
}

Repeatedly executing the above code shows that each print sequence numbers are not the same, because the 10 goroutine executed concurrently, the control room goroutine random.

goroutine thread

Growable stack

OS threads generally have a fixed stack memory (usually 2MB), a stack goroutine only a small stack (typically 2KB), goroutine stack is not fixed at the beginning of its life cycle, he can demand increase and narrow, goroutine stack size limit of up to 1GB, although rarely used so much. So create about one hundred thousand goroutine go in languages ​​is also possible.

goroutine scheduling

os os kernel thread is scheduled, it is operated by goroutine go own scheduler when scheduled, the scheduler uses a system of m: n scheduling techniques (multiplexing / schedule of m to n goroutine OS threads). goroutine kernel context switch does not require scheduling, the scheduler scheduling a goroutine much lower than the cost of a thread.

GOMAXPROCS

go runtime scheduler using GOMAXPROCS OS parameters to determine how many threads need to perform simultaneously GO code default value is the number of CPU cores on a machine, for example, on a 8-core machine, the scheduler will go codes simultaneously scheduled on 8 OS threads. () function set the number of CPU core logic of the current program occupies runtime.GOMAXPROCS go through complicated language.
Before go1.5 version, the default is to use single-core execution, after Go1.5 version, use all logical CPU cores default.

Relationship between the operating system and language thread GO goroutine of:
1. The operating system thread corresponding to a plurality of user mode goroutine.
2.go program can simultaneously multiple operating system threads.
3.goroutine OS thread and many to many relationship, i.e., m: n

channel

The function simply does not make sense concurrent execution between function and function need to exchange data in order to reflect the significance of the concurrent execution of the function.
Although the use of shared memory for data exchange, but a different shared memory race condition exists goroutine easily occur. To ensure the integrity of data exchange, you must use the mutex lock the memory is that this approach will inevitably lead to performance problems.
concurrency model go language is CSP, promote communication through shared memory, rather than through shared memory for communication.
If goroutine Go program is concurrent execution body, channel is the connection between them, channel is that it gives a goroutine send a specific value to the communication mechanism of another goroutine.
go language channel is a special type of channel, channel like a belt or a queue, always follow FIFO rules guarantee the order of data transmission and reception, each channel is a specific type of catheter, i.e. declaration channel when you need to specify the element type.

Statement channel

Channel type declaration format:

var 变量  chan 元素类型

A few examples:

var ch1 chan int
var ch2 chan bool
var ch3 chan []int

Create a channel

Reference is null channel type, the channel type is nil.

var ch chan int
fmt.Println(ch)    //<nil>

You need to make use of the function declaration in order to use the rear channels after initialization. Create a channel format is as follows:

make(chan 元素类型, [缓冲大小])

Buffer size is optional.
A few examples:

cha4 := make(chan int)
cha5 := make(chan bool)
cha6 := make(chan []int)

channel operation

Channel has a transmission (send), receiving (the receive) and closing (Close) three operations. Transmission and reception use <- symbol.
Now let's use the following statement to define a channel:

ch := make(chan int)

send

The value is sent to a channel.

ch <- 10  //把10发送到ch中

receive

Receiving a value from the channel.

x := <- ch     //从ch中接收值并赋值给变量x
<- ch       // 从ch中接收值,忽略结果

shut down

Let's close the channel by invoking the built-close function.

close(ch)

Thing about closing the channel needs to be noted that only a notification when the recipient goroutine all data have been sent only to the need to close the channel. Channel can be recycled garbage collection mechanism, he is not the same and close the file, close the file after the end of the operation is to be done, but closing the channel is not required.
After closing the channel has the following characteristics:

  1. A closed channel for retransmission value would cause panic.
  2. To receive a closed channel will always get the value until the channel is empty.
  3. Of a closed channel and no worth performing a reception operation will be a corresponding type of zero value.
  4. Closing a channel that has been closed will cause panic.

    Unbuffered channel

    Unbuffered channel, also known as the blocked channels. We look at the following code:

    func main(){
    ch := make(chan int)
    ch <- 10
    fmt.Println("发送成功了")
    }

    The above code can compile, but will be executed when the following error:
    fatal error: All goroutines are asleep - deadlock!

1 goroutine [chan the send]:
main.main ()
/Users/huoshuaibing/gowork/src/github.com/studygolang/day12/main.go:9 + 0x54
Exit Status 2
Why deadlock errors it?
Because we use ch: = make (chan int) is created unbuffered channel, unbuffered channel can only be sent when someone receives value in value. Just like you do not live in the district collection points and courier cabinet, courier call you have to put this item to your hands, it is simply an unbuffered channel must receive to be sent.
The above code will be blocked in ch <- 10 this line of code to form a deadlock, then how to solve this problem?
A method goroutine is enabled to receive a value, for example:

func recv(c chan int) {
    ret := <-c
    fmt.Println("接收成功", ret)
}
func main() {
    ch := make(chan int)
    go recv(ch)
    ch <- 10
    fmt.Println("发送成功")
}

Send action on unbuffered channel blocking until another goroutine reception operation is performed on the channel, then the value to be sent successfully, two goroutine will continue; the contrary, if the receiving operation is performed first, the recipient goroutine blocked until another goroiutine transmit a value on the channel.
Unbuffered communication channel will result in transmission and reception goroutine synchronization, therefore, unbuffered channel also called a Synchronization channel.

Channel buffer

Solution to the above problem is to use there there is a channel buffer. We can make use of the time function of the initial channel capacity for the specified channel, for example:

func main() {
    ch := make(chan int, 1)
    ch <- 10
    fmt.Println("发送成功")
}

As long as the capacity of the channel is greater than zero, then the channel is a channel buffer capacity represents the number of channels in the channel elements can be stored. As you cells express cabinet only so many compartments to fit on the grid is full, it is blocked until someone removed a courier to fill it up a.
We can use the built-in function to get len the number of elements within the channel using the channel capacity function to get cap.

How elegant cycle value from the channel

When transmitting data through the limited channel, we can close the passage by the close function to notify the channel from the received value goroutine stop waiting. When the passage is closed, the channel sends the value to cause panic, received from the channel is always zero value in the value type. How to determine whether a channel that was closed it?
We look at the following example:

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)
    go func() {
        for i := 0; i < 100; i++ {
            ch1 <- i
        }
        close(ch1)
    }()
    go func() {
        for {
            i, ok := <-ch1
            if !ok {
                break
            }
            ch2 <- i * i
        }
        close(ch2)
    }()
    for i := range ch2 {
        fmt.Println(i)
    }
}

From the above example, we see two ways in the receiver determines whether the channel is worth when closed, our commonly used are for range manner.

Unidirectional channel

Sometimes we will channel as an argument between multiple tasks function is passed, many times we use channel will function in different tasks in its limitations, such as only send or receive. Go language unidirectional channel is provided to handle this situation. For example, we transform the above example as follows:

func counter(out chan<- int) {
    for i := 0; i < 100; i++ {
        out <- i
    }
    close(out)
}
func squarer(out chan<- int, in <-chan int) {
    for i := range in {
        out <- i * i
    }
    close(out)
}
func printer(in <-chan int) {
    for i := range in {
        fmt.Println(i)
    }
}
func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)
    go counter(ch1)
    go squarer(ch2, ch1)
    printer(ch2)
}

Wherein, chan <- int is only a transmission channel, it may be transmitted but not received; <-chan int is only a reception channel can be received but not transmitted. In the function and any parameter passing operation in the bidirectional channel assignment into a unidirectional channel it is possible, but the converse is not.

select multiplexer

In some scenarios we need to receive data from a plurality of channels. Upon receiving the data channel, the data can be received if there is no blocking will occur. You might write the following code uses traversed ways to achieve:

for {
    data,ok := <- ch1
    data,ok := <- ch2
    ...
}

Although this method can achieve a plurality of channels received from the demand values, but much worse operating performance. To cope with this scenario, the GO built select keyword, a plurality of channels can operate simultaneously in response. The analogous select switch statement, it has a series branch and a default branch case, each case will correspond to a channel of communication process. select waits until the communication operation of a case is completed, the case will be executed branch corresponding statement. The following format:

select {
case <- ch1:
      ...
case <-ch2
      ...
default:
      默认操作
}

Small example to demonstrate about the use of select:

package main

import (
    "fmt"
)

func main() {
    ch := make(chan int, 1)
    for i := 0; i < 10; i++ {
        select {
        case x := <-ch:
            fmt.Println(x)
        case ch <- i:
        }
    }
}

Use the select statement can improve the readability of the code. If more than one case at the same time meet, then select randomly pick one. For there is no case of select {} I will wait.

Concurrency and security lock

Sometimes the Go may be present in a plurality of simultaneously operating goroutine resources (critical), this race condition occurs. Examples analogy crossroads in life have a car to compete in all directions; there is competition toilet on the train carriage inside. for example:

var x int64
var wg sync.WaitGroup

func add() {
    for i := 0; i < 100; i++ {
        x = x + 1
    }
    wg.Done()
}
func main() {
    wg.Add(2)
    go add()
    go add()
    wg.Wait()
    fmt.Println(x)
}

The above code we opened the values ​​of two variables x goroutine to accumulate, these two goroutine competition will exist in data access and modify the x variable, leading to discrepancies with the final results expected.

Mutex

Mutex is a commonly used method for controlling access to shared resources, they can ensure that only one goroutine can access the shared resources. Go language used in sync packets to achieve Mutex mutex. Mutex lock to fix the problem using the above code:

var x int64
var wg sync.WaitGroup
var lock sync.Mutex

func add() {
    for i := 0; i < 100; i++ {
        lock.Lock()
        x = x + 1
        lock.Unlock()
    }
    wg.Done()
}
func main() {
    wg.Add(2)
    go add()
    go add()
    wg.Wait()
    fmt.Println(x)
}

When the mutex released pending goroutine before they can obtain a lock to enter the critical section, multiple goroutine while waiting for a lock; use a mutex to ensure the same time and only one goroutine enter the critical areas, while others are waiting for a lock of goroutine , wake strategy is random.

Write mutex

Completely mutually exclusive when mutex, the reality is that many scenarios are reading and writing less, when we read a concurrent resource does not involve resources when there is no need to modify locked, read-write such a scenario lock is a better choice. Using the sync packet write lock RWMutex type, in Go.
Read-write locks in two ways: read and write locks. When goroutine get a read lock, other goroutine if it is to acquire a read lock will continue to get the lock, if the acquisition is to write lock will wait, when goroutine get a write lock, the other goroutine either acquire a read lock or a write lock We will wait.

Read-write lock Example:

var (
    x      int64
    wg     sync.WaitGroup
    lock   sync.Mutex
    rwlock sync.RWMutex
)

func write() {
    // lock.Lock()   // 加互斥锁
    rwlock.Lock() // 加写锁
    x = x + 1
    time.Sleep(10 * time.Millisecond) // 假设读操作耗时10毫秒
    rwlock.Unlock()                   // 解写锁
    // lock.Unlock()                     // 解互斥锁
    wg.Done()
}

func read() {
    // lock.Lock()                  // 加互斥锁
    rwlock.RLock()               // 加读锁
    time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒
    rwlock.RUnlock()             // 解读锁
    // lock.Unlock()                // 解互斥锁
    wg.Done()
}

func main() {
    start := time.Now()
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go write()
    }

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go read()
    }

    wg.Wait()
    end := time.Now()
    fmt.Println(end.Sub(start))
}

sync.Once

Said front: This is an advanced knowledge points. A delay spending a lot of time to actually use the initialization operation to re-execute it is a good practice. Because pre-initialize a variable (such as done in the init function to initialize) increases the program start-up delay, and it is possible to actual implementation in this variable is not to spend, then this operation is not necessary to initialize the. We look at an example:
sync.Once actually inside a mutex and a Boolean value, mutex to ensure data security and Boolean values, and Boolean value that records initialization is complete. This design can guarantee initialization operation when initialization is safe and concurrent operation will not be executed multiple times.

sync.Map

Go languages ​​built-in map is not concurrent safe. Consider the following example:

var m = make(map[string]int)

func get(key string) int {
    return m[key]
}
func set(key string, value int) {
    m[key] = value
}
func main() {
    wg := sync.WaitGroup{}
    for i := 0; i < 20; i++ {
        wg.Add(1)
        go func(n int) {
            key := strconv.Itoa(n)
            set(key, n)
            fmt.Println("k=:%v, v:=%v\n", key, get(key))
            wg.Done()
        }(i)
    }
    wg.Wait()
}

The code above to open a small number of goroutine when there is no problem, when complicated by the way, the implementation will be reported fatal error: concurrent map writes error.
Like this scenario it is necessary to map lock to ensure the safety of concurrent, sync language pack go in an out of the box to provide concurrency Security map-sync.Map. Meanwhile sync.Map operating method such as a built-Store, Load, LoadOrStore, Delete, Range like.

Guess you like

Origin blog.51cto.com/13766835/2423674