Golang high concurrency principle

The processing power of the software is not only related to memory, but also whether it is blocked, whether it is processed asynchronously, CPU and so on. So is it possible to have a language that uses smaller processing units and uses less memory than threads, so its concurrent processing power can be higher. So Google did this, there is the golang language, and golang supports high concurrency from the language level.

Go's high concurrency processing core-goroutine

Goroutine is the core of Go's parallel design. In the final analysis, goroutine is actually a coroutine. It is smaller than threads and takes up less resources. Dozens of goroutines may be reflected in the bottom layer. It is five or six threads. Go language helps you achieve memory sharing between these goroutines. Only a small amount of stack memory (approximately 4 ~ 5KB) is required to execute goroutine, and of course it will scale according to the corresponding data. Because of this, tens of thousands of concurrent tasks can be run simultaneously. Goroutine is easier to use, more efficient, and lighter than thread.
Coroutines are more lightweight and take up less memory, which is the prerequisite for high concurrency.

A simple web service

package main

import (
    "fmt"
    "log"
    "net/http"
)

func response(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello world!") //这个写入到w的是输出到客户端的
}

func main() {
    http.HandleFunc("/", response)
    err := http.ListenAndServe(":9000", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

Then compile

go build -o test_web.gobin
./test_web.gobin

 Then visit

curl 127.0.0.1:9000
Hello world!

 Next, we understand step by step how this Web service runs and how to achieve high concurrency.

We followed the http.HandleFunc ("/", response) method and looked up the code.

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux

type ServeMux struct {
    mu    sync.RWMutex//读写锁。并发处理需要的锁
    m     map[string]muxEntry//路由规则map。一个规则一个muxEntry
    hosts bool //规则中是否带有host信息
}
一个路由规则字符串,对应一个handler处理方法。
type muxEntry struct {
    h       Handler
    pattern string
}

 The above is the definition and description of DefaultServeMux. We see the ServeMux structure, which has a read-write lock to handle concurrent use. The muxEntry structure contains handler processing methods and routing strings.

Next, let's see what the http.HandleFunc function, which is DefaultServeMux.HandleFunc, does. We first look at the second parameter of mux.Handle HandlerFunc (handler)

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    mux.Handle(pattern, HandlerFunc(handler))
}
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)  // 路由实现器
}
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

The custom response method we pass is forced to be converted to HandlerFunc type, so the response method we pass by default implements the ServeHTTP method.

We next look at the first parameter of mux.Handle.

func (mux *ServeMux) Handle(pattern string, handler Handler) {
    mux.mu.Lock()
    defer mux.mu.Unlock()

    if pattern == "" {
        panic("http: invalid pattern")
    }
    if handler == nil {
        panic("http: nil handler")
    }
    if _, exist := mux.m[pattern]; exist {
        panic("http: multiple registrations for " + pattern)
    }

    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    mux.m[pattern] = muxEntry{h: handler, pattern: pattern}

    if pattern[0] != '/' {
        mux.hosts = true
    }
}

 Store the routing string and the handler function in the ServeMux.m map table, the muxEntry structure in the map, as described above, a route corresponds to a handler processing method.

Next we look at what http.ListenAndServe (": 9000", nil) does

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

func (srv *Server) ListenAndServe() error {
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

net.Listen ("tcp", addr) is to build a service with TCP protocol using port addr. tcpKeepAliveListener is to monitor the port of addr.

Next is the key code, HTTP processing

func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()
    if fn := testHookServerServe; fn != nil {
        fn(srv, l)
    }
    var tempDelay time.Duration // how long to sleep on accept failure

    if err := srv.setupHTTP2_Serve(); err != nil {
        return err
    }

    srv.trackListener(l, true)
    defer srv.trackListener(l, false)

    baseCtx := context.Background() // base is always background, per Issue 16220
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    for {
        rw, e := l.Accept()
        if e != nil {
            select {
            case <-srv.getDoneChan():
                return ErrServerClosed
            default:
            }
            if ne, ok := e.(net.Error); ok && ne.Temporary() {
                if tempDelay == 0 {
                    tempDelay = 5 * time.Millisecond
                } else {
                    tempDelay *= 2
                }
                if max := 1 * time.Second; tempDelay > max {
                    tempDelay = max
                }
                srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
                time.Sleep(tempDelay)
                continue
            }
            return e
        }
        tempDelay = 0
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx)
    }
}

For l.Accept () accepts the TCP connection request, c: = srv.newConn (rw) creates a Conn, and Conn holds the information of the request (srv, rw). Start goroutine, pass the requested parameters to c.serve, and let goroutine execute.

This is the most critical point of GO high concurrency. Each request is executed by a separate goroutine.

 

So where did the previously set routes match? It is done by analyzing the URI METHOD etc. in c.readRequest (ctx) of c.server, and executing serverHandler {c.server} .ServeHTTP (w, w.req). Look at the code

 

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    handler := sh.srv.Handler
    if handler == nil {
        handler = DefaultServeMux
    }
    if req.RequestURI == "*" && req.Method == "OPTIONS" {
        handler = globalOptionsHandler{}
    }
    handler.ServeHTTP(rw, req)
}

The handler is empty, just the second parameter of ListenAndServe in the project we just started. We are nil, so we go to DefaultServeMux, we know that we set DefaultServeMux to start routing, so in DefaultServeMux I can find the handler corresponding to the requested route, and then execute ServeHTTP. As mentioned earlier, why our response method has the ServeHTTP function. This is probably the process.

 

 

Conclusion

We have basically learned the entire working principle of HTTP that has forgotten GO, and learned why it can achieve high concurrency in WEB development. These are just the tip of the iceberg of GO, and the Redis MySQL connection pool. To be familiar with this language or write more, you can master it. Flexible and skilled use.

 

 


 

Published 127 original articles · Likes 24 · Visits 130,000+

Guess you like

Origin blog.csdn.net/Linzhongyilisha/article/details/105473880