zoukankan      html  css  js  c++  java
  • golang高并发

    golang 为什么能做到高并发

      goroutine是go并行的关键,goroutine说到底就是携程,但是他比线程更小,几十个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB),当然会根据相应的数据伸缩。也正因为如此,可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。

    一些高并发的处理方案基本都是使用协程,openresty也是利用lua语言的协程做到了高并发的处理能力,PHP的高性能框架Swoole目前也在使用PHP的协程。
    协程更轻量,占用内存更小,这是它能做到高并发的前提。

    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)方法顺着代码一直往上看。

    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

  • 相关阅读:
    源码篇——安卓启动流程
    Android源代码结构分析
    使用Contacts Contract Content Provider操作通讯录最佳实践
    Linux输入子系统 : 按键驱动
    运算类实现 及 GNU Makefile基本结构
    输入系统:epoll & inotify
    FFmpeg:初步编译使用[Android]
    Linux学习 :SPI通讯协议
    Linux学习 : 自己写bootloader
    Linux学习 :移植U-boot_2012.04.01到JZ2440开发板
  • 原文地址:https://www.cnblogs.com/tsxylhs/p/10995466.html
Copyright © 2011-2022 走看看