golang高并发的深入理解
作者:飞翔码农 发布时间:2023-06-19 09:12:01
前言
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的连接池。要熟悉这门语言还是多写多看,才能掌握好它。灵活熟练的使用。
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值。
来源:https://www.cnblogs.com/feixiangmanon/p/10504081.html


猜你喜欢
- filter是Python的内置方法。官方定义是:filter(function or None, sequence) -> list
- from win32com.client import DispatchEximport timeie=DispatchEx("I
- 熵值法也称熵权法,是学术研究,及实际应用中的一种常用且有效的编制指标的方法。1.简单理解 信息熵机器学习中的决策树算法是对信息熵的一种典型的
- 我们有理由相信采用新的内核版本(2.2.16-3 smp)也应该有性能的提升: OS2: Newer minor version kerne
- 安装Python环境ANACONDA是一个Python的发行版本,包含了400多个Python最常用的库,其中就包括了数据分析中需要经常使用
- 列表(元组)基本操作符回顾len()函数在列表与元组上的使用示例如下:names = ['Neo', 'Lily
- 用的昨天刚接触到的库,在windows下通过paramiko来登录linux系统并执行了几个命令,基本算是初试成功,后面会接着学习的。代码:
- 1. 题目编写程序, 4名牌手打牌,计算机随机将52张牌(不含大小鬼)发给4名牌手,在屏幕上显示每位牌手的牌。提示:设计出3个类:Card类
- 目录一.权限简介二.权限表结构设计:第一版三.权限表结构设计:第二版四.客户管理之动态“一级”菜单五.客户管理之动态“二级”菜单六.客户管理
- 本文实例为大家分享了python调用百度语音REST API的具体代码,供大家参考,具体内容如下(百度的rest接口的部分网址发生了一定的变
- 教你如何清除SQL日志 1.打开查询分析器,输入命令DUMP TRANSACTION 数据库名 WITH NO_LOG2. 再打开企业管理器
- <title>动态序列比对</title> <script> function pairaln(seq1
- 本文假设某些特定类型的文件和大小为0的文件为垃圾文件,可以自由扩展代码的列表,也就是垃圾文件的类型。from os.path import
- 本文实例讲述了Python实现二维有序数组查找的方法。分享给大家供大家参考,具体如下:题目:在一个二维数组中,每一行都按照从左到右递增的顺序
- 下面先给大家介绍下mpvue跳转页面,具体内容如下所示:正准备写一个小程序,得知了mpvue开源的消息,又恰巧之前刚刚学习了一点vue,便开
- 最近老婆大人的公司给老婆大人安排了一个根据关键词查询google网站排名的差事。老婆大人的公司是做seo的,查询的关键词及网站特别的多,看着
- 误区 #29:可以通过对堆建聚集索引再DROP后进行堆上的碎片整理Nooooooooooooo!!! &
- 本文实例讲述了python生成IP段的方法。分享给大家供大家参考。具体实现方法如下:#!/usr/local/bin/python#-*-
- 二值图像的凸壳指的是包围输入二值图像白色区域的最小的凸多边形的像素集合。skimage中的函数from skimage.morphology
- 本文实例讲述了python检测远程服务器tcp端口的方法。分享给大家供大家参考。具体如下:python检测远程服务器tcp端口的代码,这段代