教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 Golang之高并发原理

Golang之高并发原理

发布时间:2022-01-26   编辑:jiaochengji.com
教程集为您提供Golang之高并发原理等资源,欢迎您收藏本站,我们将为您提供最新的Golang之高并发原理资源
<h1>用一个简单的web服务探究golang的高并发原理</h1>

        高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求。要做到高并发,就要做到资源合理分配,需要多少就分配多少,反之小马拉大车,拉不动,大马拉小二轮,造成资源浪费。

        严格意义上说,单核的CPU是没法做到并行的,只有多核的CPU才能做到严格意义上的并行,因为一个CPU同时只能做一件事。那为什么是单核的CPU也能做到高并发。这就是操作系统进程线程调度切换执行,感觉上是并行处理了。所以只要进程线程足够多,就能处理C1K C10K的请求,但是进程线程的数量又受到操作系统内存等资源的限制。每个线程必须分配8M大小的栈内存,不管是否使用。当然了,软件的处理能力不仅仅跟内存有关,还有是否阻塞,是否异步处理,CPU等等。那么是不是可以有一种语言使用更小的处理单元,占用内存比线程更小,那么它的并发处理能力就可以更高。所以Google就做了这件事,就有了golang语言,golang从语言层面就支持了高并发。

<h2>go的高并发处理核心 - goroutine</h2>

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

<h2>go web开发中怎么做到高并发的能力</h2>

先看一份简单的web服务代码

<pre><code>package main import ( "fmt" "log" "net/http" ) func response(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello world! This is the Go") //这个写入到w的是输出到客户端的 } func main() { http.HandleFunc("/", response) err := http.ListenAndServe(":8088", nil) if err != nil { log.Fatal("ListenAndServe: ", err) } }复制代码</code></pre>

这是一个简单的WEB服务,我们刚刚说过goroutine是go语言支持高并发的核心,接下来我们一步一步理解这个Web服务是怎么做到高并发的。

我们顺着http.HandleFunc("/", response)方法顺着代码一直往上看。

<pre><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 }复制代码</code></pre>

上面是DefaultServeMux的定义和说明。我们看到ServeMux结构体,里面有个读写锁,处理并发使用。muxEntry结构体,里面有handler处理方法和路由字符串。
接下来我们看下,http.HandleFunc函数,也就是DefaultServeMux.HandleFunc做了什么事。我们先看mux.Handle第二个参数HandlerFunc(handler)

<pre><code>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) }复制代码</code></pre>

我们看到,我们传递的自定义的response方法被强制转化成了HandlerFunc类型,所以我们传递的response方法就默认实现了ServeHTTP方法的。

我们接着看mux.Handle第一个参数。

<pre><code>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 } }复制代码</code></pre>

将路由字符串和处理的handler函数存储到ServeMux.m 的map表里面,map里面的muxEntry结构体,上面介绍了,一个路由对应一个handler处理方法。

接下来我们看看,http.ListenAndServe(":8900", nil)做了什么

<pre><code>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)}) }复制代码</code></pre>

net.Listen("tcp", addr),就是使用端口addr用TCP协议搭建了一个服务。tcpKeepAliveListener就是监控addr这个端口。

接下来就是关键代码,HTTP的处理过程

<pre><code>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) } }复制代码</code></pre>

在上面代码块的结尾处,我们发现一个关键字go,他是用来启动一个goroutine的关键字。

<pre><code>go c.serve(ctx)复制代码</code></pre>

这句代码的作用就是启动goroutine,让goroutine去执行c.serve。这个就是go语言高并发最关键的点。每一个请求都是一个单独的goroutine去执行,c.serve(ctx)里面就是每一次请求的路由匹配逻辑,该函数里面分析出URI METHOD等,执行serverHandler{c.server}.ServeHTTP(w, w.req)等

所以一次web服务的处理大概就是这样子

使用单独的goroutine去执行就能实现高并发,这是为什么呢?这又得长篇大论了,请关注另一篇博文《 golang高并发探究之协程》

到此这篇关于“Golang之高并发原理”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
走进Golang之编译器原理
golang会取代php吗
学习golang开始前的准备工作
Golang之美
Golang号称高并发,但高并发时性能不高解决办法
Go from 2020
想系统学习GO语言(Golang
Golang并发原理及GPM调度策略(一)
Golang之高并发原理
golang runtime 简析

[关闭]
~ ~