前言

GO语言在WEB开发领域中的使用越来越广泛,Hired 发布的《2019 软件工程师状态》报告中指出,具有 Go 经验的候选人是迄今为止最具吸引力的。平均每位求职者会收到9 份面试邀请。

想学习go,最基础的就要理解go是怎么做到高并发的。

那么什么是高并发?

高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求。

严格意义上说,单核的CPU是没法做到并行的,只有多核的CPU才能做到严格意义上的并行,因为一个CPU同时只能做一件事。那为什么是单核的CPU也能做到高并发。这就是操作系统进程线程调度切换执行,感觉上是并行处理了。所以只要进程线程足够多,就能处理C1K C10K的请求,但是进程线程的数量又受到操作系统内存等资源的限制。每个线程必须分配8M大小的栈内存,不管是否使用。每个php-fpm需要占用大约20M的内存。所以目前有线程的Java就比只有进程的PHP的并发处理能力高。当然了,软件的处理能力不仅仅跟内存有关,还有是否阻塞,是否异步处理,CPU等等。Nginx作为单线程的模型却可以承担几万甚至几十万的并发请求,Nginx的话题说起来也就更多了。

我们继续聊我们的Go,那么是不是可以有一种语言使用更小的处理单元,占用内存比线程更小,那么它的并发处理能力就可以更高。所以Google就做了这件事,就有了golang语言,golang从语言层面就支持了高并发。

go为什么能做到高并发

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

然后编译

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

然后访问

curl 127.0.0.1:9000
Hello world!

这样简单的一个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的连接池。要熟悉这门语言还是多写多看,才能掌握好它。灵活熟练的使用。

------------------------------------end

一起关注高性能WEB后端技术,关注公众号

golang高并发的理解的更多相关文章

  1. golang高并发

    golang 为什么能做到高并发 goroutine是go并行的关键,goroutine说到底就是携程,但是他比线程更小,几十个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这 ...

  2. PHP项目:如何用PHP高并发检索数据库?

    对于抢票.秒杀这种业务,我说说自己对这种高并发的理解吧,这里提出个人认为比较可行的几个方案: 方案一:使用队列来实现 可以基于例如MemcacheQ等这样的消息队列,具体的实现方案这么表述吧 比如有1 ...

  3. PHP 高并发、抢票、秒杀 解决方案

    对于抢票.秒杀这种业务,我说说自己对这种高并发的理解吧,这里提出个人认为比较可行的几个方案:方案一:使用队列来实现可以基于例如MemcacheQ等这样的消息队列,具体的实现方案这么表述吧比如有100张 ...

  4. go---weichart个人对Golang中并发理解

    个人觉得goroutine是Go并行设计的核心,goroutine是协程,但比线程占用更少.golang对并发的处理采用了协程的技术.golang的goroutine就是协程的实现. 十几个gorou ...

  5. [golang]Golang实现高并发的调度模型---MPG模式

    Golang实现高并发的调度模型---MPG模式 传统的并发形式:多线程共享内存,这也是Java.C#或者C++等语言中的多线程开发的常规方法,其实golang语言也支持这种传统模式,另外一种是Go语 ...

  6. Golang语言快速上手到综合实战高并发聊天室

    需要的联系我:QQ:1844912514 Go是Google开发的一种编译型,可并行化,并具有垃圾回收功能的编程语言.2015,Go迎来了全迸发的一年.时隔一年,回头再看,Go已跻身主流编程语言行列. ...

  7. 聊聊高并发(二十五)解析java.util.concurrent各个组件(七) 理解Semaphore

    前几篇分析了一下AQS的原理和实现.这篇拿Semaphore信号量做样例看看AQS实际是怎样使用的. Semaphore表示了一种能够同一时候有多个线程进入临界区的同步器,它维护了一个状态表示可用的票 ...

  8. Golang适合高并发场景的原因分析

    http://blog.csdn.NET/ghj1976/article/details/27996095 典型的两个现实案例: 我们先看两个用Go做消息推送的案例实际处理能力. 360消息推送的数据 ...

  9. php-fpm和cgi,并发响应的理解以及高并发和多线程的关系

    首先搞清楚php-fpm与cgi的关系 cgi cgi是一个web server与cgi程序(这里可以理解为是php解释器)之间进行数据传输的协议,保证了传递的是标准数据. php-cgi php-c ...

随机推荐

  1. websocket(二)--简单实现网页版群聊

    websocket可以实现服务端的消息推送,而不必在客户端轮询,大大的节省的资源,对于实时通讯来说简直是个大喜讯. 在上一篇文章中介绍了协议握手,这篇文章将通过实现简单的群聊来帮助进一步了解webso ...

  2. django(权限、认证)系统——自定义UserProfile储存User额外信息

    上篇文章我们引出了Django内置的权限控制系统,讲了安装,和最核心和基本的User模型的API和其Manager的API. 接下来我们继续深入下去,使用User对象做一些事情,首先当然就是创建一个U ...

  3. java把结果集序列化成json通过out流传给前台步骤

    1.把处理好的list或map序列化成JSON字符 /** * 序列化集合成JSON字符 * @param list * @return */ public static String structu ...

  4. 不吹不擂,你想要的Python面试都在这里了【315+道题】

    写在前面 近日恰逢老男孩全栈8期毕业季,课程后期大家“期待+苦逼”的时刻莫过于每天早上内容回顾和面试题问答部分[临近毕业每天课前用40-60分钟对之前内容回顾.提问和补充,专挑班里不爱说话就的同学回答 ...

  5. BZOJ_3282_Tree_LCT

    BZOJ_3282_Tree_LCT Description 给定N个点以及每个点的权值,要你处理接下来的M个操作. 操作有4种.操作从0到3编号.点从1到N编号. 0:后接两个整数(x,y),代表询 ...

  6. [JSOI2008]星球大战starwar BZOJ1015

    并查集 正序处理时间复杂度为n^2,考虑逆序处理,这样,时间复杂度从n^2降为nlogn 附上代码: #include <cstdio> #include <algorithm> ...

  7. 从零搭建基于golang的个人博客网站

    原文链接 : http://www.bugclosed.com/post/14 从零搭建个人博客网站需要包括云服务器(虚拟主机),域名,程序环境,博客程序等方面.本博客 就是通过这几个环节建立起来的, ...

  8. surging如何使用swagger 组件测试业务模块

    1.前言 微服务架构概念的提出已经有非常长一段时间了,但在近期几年却开始频繁地出现,大家都着手升级成微服务架构,使用着各种技术,大家认为框架有服务治理就是微服务,实现单一协议的服务调用,微服务虽然没有 ...

  9. javascript正则表达式学习(二)--位置匹配

    文章首发于sau交流学习社区 一.前言 正则表达式是匹配模式,要么是匹配字符,要么匹配位置. 其实在开发中很少用到匹配位置,本篇文章主要包含: 二.什么是位置 位置:相邻字符之间的位置. 三.如何匹配 ...

  10. [区块链\理解BTCD源码]GO语言实现一个区块链原型

    摘要 本文构建了一个使用工作量证明机制(POW)的类BTC的区块链.将区块链持久化到一个Bolt数据库中,然后会提供一个简单的命令行接口,用来完成一些与区块链的交互操作.这篇文章目的是希望帮助大家理解 ...