[Golang] go concurrent goroutine

Concurrent go

Explanation

Some people go likened c language of the 21st century, the first go is because the design is relatively simple, the second in the 21st century the most important thing is to concurrent programming, and go from level to support concurrent language.

At the same time, concurrent program memory management is very complex, and provides a mechanism for garbage collection in go.

Go to concurrent programming language and built-in API is based on the upper communicating sequential process model CSP (communicating sequential processes). This means that explicit locks can be avoided, because Go to send and receive data through a relatively secure channel to achieve synchronization, which greatly simplifies the writing concurrent programs.

Go language program to implement concurrent use of two main tools. goroutine and channel.

goroutine

goroutine go is the core of concurrent programming, in the final analysis, goroutine coroutine is smaller than the thread, go inside the language to help achieve the shared memory between goroutine. Goroutine execution requires minimal stack memory (probably 4-5kb), of course, will be scalable according to the corresponding data. Precisely because of this, thousands of concurrent tasks can be run simultaneously. goroutine than thread easier to use, more efficient and lighter.

Under normal circumstances, a general computer running dozens of threads on a bit too large a load, but the same machine but can easily make hundreds of goroutine resources to compete.

use

Want to create a goroutine, just add a keyword go in front of ordinary functions, you can create concurrent execution unit.

In concurrent programming, a process we usually want to cut into pieces, and then let each goroutine each responsible for a job, when a program starts, the main function runs in a separate goroutine, we call it the main goroutine. The new goroutine will be created with the go statement. Concurrent design language and go, so we can easily achieve this goal.

package main

import (
    "fmt"
    "time"
)

func main() {
    // 创建一个goroutine
    go func() {
        for i:=0;i<10;i++ {
            fmt.Printf("子goroutine输出%d\n",i)
            time.Sleep(time.Second * 2)
        }
    }()

    for i:=0;i<10;i++ {
        fmt.Printf("主main goroutine输出%c\n",i+'a')
        time.Sleep(time.Second * 2)
    }
}

Output:

主main goroutine输出a
子goroutine输出0
子goroutine输出1
主main goroutine输出b
子goroutine输出2
主main goroutine输出c
子goroutine输出3
主main goroutine输出d
子goroutine输出4
主main goroutine输出e
子goroutine输出5
主main goroutine输出f
子goroutine输出6
主main goroutine输出g
子goroutine输出7
主main goroutine输出h
子goroutine输出8
主main goroutine输出i
主main goroutine输出j
子goroutine输出9

Process finished with exit code 0

characteristic

When the main goroutine quit, other goroutine will exit.

package main

import (
"fmt"
"time"
)

func newTask() {
    i := 0
    for {
        i++
        fmt.Printf("new goroutine: i = %d\n", i)
        time.Sleep(1 * time.Second) //延时1s
    }
}

func main() {
    //创建一个 goroutine,启动另外一个任务
    go newTask()

    fmt.Println("main goroutine exit")
}

Output:

Examples

Here we'll create an example, main goroutine will calculate the value of the first 45 elements Fibonacci number sequence. Due to inefficient use of recursive calculation function, it will run quite a long time, during which we want users to see a visible logo to indicate that the program is still in normal operation, so do a little animated icon:

func main() {
    go spinner(100 * time.Millisecond)
    const n = 45
    fibN := fib(n) // slow
    fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)
}

func spinner(delay time.Duration) {
    for {
        for _, r := range `-\|/` {
            fmt.Printf("\r%c", r)
            time.Sleep(delay)
        }
    }
}

func fib(x int) int {
    if x < 2 {
        return x
    }
    return fib(x-1) + fib(x-2)
}

After the animation display for a few seconds, the call fib (45) successfully returns, and print the results:

Fibonacci(45) = 1134903170

Then the main function returns. The main function returns, all goroutine will be directly interrupted, the program exits.

Here we look at a clock server example, an order of execution, it will be every second of the current time is written to the client:

package main

import (
    "io"
    "log"
    "net"
    "time"
)

func main() {
    listener, err := net.Listen("tcp", "localhost:8000")
    if err != nil {
        log.Fatal(err)
    }

    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Print(err) // e.g., connection aborted
            continue
        }
        handleConn(conn) // handle one connection at a time
    }
}

func handleConn(c net.Conn) {
    defer c.Close()
    for {
        _, err := io.WriteString(c, time.Now().Format("15:04:05\n"))
        if err != nil {
            return // e.g., client disconnected
        }
        time.Sleep(1 * time.Second)
    }
}

In the above code, we listen through the Listener object connected to the network port, the code above is by localhost listening the tcp: port 8000, while Accept will block the program until a new connection is created, and then returns a net .Conn object represents a connection.

handleConn function handles a full client connections. For dead in a loop, with time.Now () to get the current time, and then written to the client. Since net.Conn realized io.Writer interface, we can directly write to the content. This infinite loop will execute until the write failure. The most likely reason is that the client take the initiative to disconnect. handleConn function calls the server side is connected with the closing defer this case, and then returns to the main function, continues to wait for a next connection request.

After completion of the above operation code to be executed by a network connection nc (netcat).

$ go build gopl.io/ch8/clock1
$ ./clock1 &
$ nc localhost 8000
13:58:54
13:58:55
13:58:56
13:58:57
^C

Through the above code we have completed the above requirements, but this time only meet a person's connection, if two or more people to connect, then it can only be the first person exits the connection to the people behind incoming connections.

If you want to achieve more than this time connected while print time, we can change our code of a little bit into demand can be achieved using goroutine, as follows:

for {
    conn, err := listener.Accept()
    if err != nil {
        log.Print(err) // e.g., connection aborted
        continue
    }
    go handleConn(conn) // handle connections concurrently
}

By the above code, we can achieve the above requirements, every client connection, it will create a goroutine outside the main main goroutine, so that everyone at the same time the effect of printing time can be achieved.

Next we look at the demand for in-depth, in the implementation of the code, and allows the addition of port time zone, so that when a user is connected, you can get time to the current time zone. Code is implemented as follows:

package main

import (
    "flag"
    "io"
    "log"
    "net"
    "time"
)

var port = flag.String("port","8000","请输入端口号")

func main() {
    flag.Parse()
    listener,err := net.Listen("tcp","localhost:"+*port)
    if err != nil {
        log.Fatal(err)
    }


    for {
        conn,err := listener.Accept()
        if err != nil {
            log.Print(err)
            continue
        }

        go handleConnect(conn)

    }


}

func handleConnect(c net.Conn) {

    // 关闭
    defer c.Close()

    for {
        _,err := io.WriteString(c,time.Now().Format("15:04:05\n"))
        if err != nil {
            return
        }
        time.Sleep(1 * time.Second)
    }

}

Code execution:

runtime package

Gosched

When runtime.Gosched () to let the CPU time slice, so that the current goroutine execute permissions, scheduler schedule other tasks waiting to run and get in next time round piece of cpu time to recover from the location of the execution of the transfer cpu .

A bit like running a relay race, A ran a run into the code runtime.Gosched () took the baton to B, A to recover from, B continue to run.

The following code is not used runtime.Gosched () code:

package main

import (
    "fmt"
)

func main() {
    go func(str string) {
        for _,val := range str {
            fmt.Printf("子goroutine:%c\n",val)
        }
    }("hello")

    for _,val := range "world" {
        fmt.Printf("主goroutine:%c\n",val)
    }
}

Output:

子goroutine:h
子goroutine:e
子goroutine:l
子goroutine:l
子goroutine:o
主goroutine:w
主goroutine:o
主goroutine:r
主goroutine:l
主goroutine:d

Process finished with exit code 0

Let Let the main goroutine use runtime.Gosched cpu time slice. as follows:

package main

import (
    "fmt"
    "runtime"
)

func main() {
    go func(str string) {
        for _,val := range str {
            fmt.Printf("子goroutine:%c\n",val)
        }
    }("hello")

    for _,val := range "world" {
        runtime.Gosched() // 让出cpu时间片
        fmt.Printf("主goroutine:%c\n",val)
    }
}

When let out of time slice, the resulting output is:

子goroutine:h
子goroutine:e
子goroutine:l
子goroutine:l
子goroutine:o
主goroutine:w
主goroutine:o
主goroutine:r
主goroutine:l
主goroutine:d

Process finished with exit code 0

When let out cpu time slice, it will perform the first sub-goroutine, then the execution of the main goroutine.

Goexit

Call runtime.Goexit () will immediately terminate the current goroutine YES, the scheduler ensures that all registered defer to delay calling is performed.

package main

import (
"fmt"
"runtime"
)

func main() {
    go func() {
        defer fmt.Println("A.defer")

        func() {
            defer fmt.Println("B.defer")
            runtime.Goexit() // 终止当前 goroutine, import "runtime"
            fmt.Println("B") // 不会执行
        }()

        fmt.Println("A") // 不会执行
    }()     //不要忘记()

    //死循环,目的不让主goroutine结束
    for {
    }
}

Result of the program:

GOMAXPROCS

Runtime.GOMAXPROCS call () is used to set the maximum number of CPU cores may be parallel computation, and returns the previous value.

package main

import (
    "fmt"
    "runtime"
)

func main () {
    // 设置cpu核数
    // 括号内的参数表示设置的最大cpu核数
    // 返回值表示上一次设置的cpu核数
    n1 := runtime.GOMAXPROCS(1) // 第一次调用返回默认的核数 4 
    fmt.Println(n1)

    n2 := runtime.GOMAXPROCS(2) // 第二次调用返回上一次设置的核数 1 
    fmt.Println(n2)
    
}

Results are as follows:

4
1

Process finished with exit code 0

Guess you like

Origin www.cnblogs.com/liujunhang/p/12535987.html