golang高併發

golang 爲何能作到高併發html

  goroutine是go並行的關鍵,goroutine說到底就是攜程,可是他比線程更小,幾十個goroutine可能體如今底層就是五六個線程,Go語言內部幫你實現了這些goroutine之間的內存共享。執行goroutine只需極少的棧內存(大概是4~5KB),固然會根據相應的數據伸縮。也正由於如此,可同時運行成千上萬個併發任務。goroutine比thread更易用、更高效、更輕便。golang

一些高併發的處理方案基本都是使用協程,openresty也是利用lua語言的協程作到了高併發的處理能力,PHP的高性能框架Swoole目前也在使用PHP的協程。
協程更輕量,佔用內存更小,這是它能作到高併發的前提。web

go web開發中怎麼作到高併發的能力

學習go的HTTP代碼。先建立一個簡單的web服務。併發

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)
    }
}

 

而後啓動框架

這樣簡單的一個WEB服務就搭建起來。接下來咱們一步一步理解這個Web服務是怎麼運行的,怎麼作到高併發的。
咱們順着http.HandleFunc("/", response)方法順着代碼一直往上看。tcp

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
}

 

上面是DefaultServeMux的定義和說明。咱們看到ServeMux結構體,裏面有個讀寫鎖,處理併發使用。muxEntry結構體,裏面有handler處理方法和路由字符串。
接下來咱們看下,http.HandleFunc函數,也就是DefaultServeMux.HandleFunc作了什麼事。咱們先看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)
}

 

咱們看到,咱們傳遞的自定義的response方法被強制轉化成了HandlerFunc類型,因此咱們傳遞的response方法就默認實現了ServeHTTP方法的。高併發

咱們接着看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
    }
}

 

將路由字符串和處理的handler函數存儲到ServeMux.m 的map表裏面,map裏面的muxEntry結構體,上面介紹了,一個路由對應一個handler處理方法。
接下來咱們看看,http.ListenAndServe(":9000", nil)作了什麼學習

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),就是使用端口addr用TCP協議搭建了一個服務。tcpKeepAliveListener就是監控addr這個端口。
接下來就是關鍵代碼,HTTP的處理過程

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()接受TCP的鏈接請求,c := srv.newConn(rw)建立一個Conn,Conn裏面保存了該次請求的信息(srv,rw)。啓動goroutine,把請求的參數傳遞給c.serve,讓goroutine去執行。
這個就是GO高併發最關鍵的點。每個請求都是一個單獨的goroutine去執行。
那麼前面設置的路由是在哪裏匹配的?是在c.serverde的c.readRequest(ctx)裏面分析出URI METHOD等,執行serverHandler{c.server}.ServeHTTP(w, w.req)作的。看下代碼

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)
}

 

handler爲空,就咱們剛開始項目中的ListenAndServe第二個參數。咱們是nil,因此就走DefaultServeMux,咱們知道開始路由咱們就設置的是DefaultServeMux,因此在DefaultServeMux裏面我必定能夠找到請求的路由對應的handler,而後執行ServeHTTP。前邊已經介紹過,咱們的reponse方法爲何具備ServeHTTP的功能。流程大概就是這樣的。

咱們看下流程圖

結語

咱們基本已經學習忘了GO 的HTTP的整個工做原理,瞭解到了它爲何在WEB開發中能夠作到高併發,這些也只是GO的冰山一角,還有Redis MySQL的鏈接池。要熟悉這門語言仍是多寫多看,才能掌握好它。靈活熟練的使用。

原文地址:www.cnblogs.com/feixiangmanon/p/10504081.html

相關文章
相關標籤/搜索