首页 > 其他分享 >你是否想知道如何应对高并发?Go语言为你提供了答案!

你是否想知道如何应对高并发?Go语言为你提供了答案!

时间:2023-12-29 14:26:14浏览次数:28  
标签:协程 语言 调度 并发 线程 答案 Go

image

并发编程是当前软件领域中不可忽视的一个关键概念。随着CPU等硬件的不断发展,我们都渴望让我们的程序运行速度更快、更快。而Go语言在语言层面天生支持并发,充分利用现代CPU的多核优势,这也是Go语言能够广泛流行的一个重要原因。

在Java中,要支持高并发有几种方案可供选择。首先,我们可以通过开启多部署节点集群来增加高并发处理能力,通过增加机器硬件来实现。其次,我们可以在单节点上开启多线程来处理请求。然而,即使在单节点内创建线程也是非常耗费资源的。因此,通常情况下我们会使用线程池来管理线程的创建和销毁。然而,有一个公式你可能会很熟悉,即核心线程数等于CPU核数的一半加一。这意味着我们并不是线程创建得越多,对于我们的Java程序就越好。

在我们明确了问题的痛点之后,我们可以进一步探究一下Go语言是如何解决这些问题,并且将高并发作为Go语言的一项特色功能。

goroutine

我们在Java中开启线程的方式是直接创建一个Thread对象。然而,在Go语言中,如果我们想要实现异步处理,我们可以使用"go"关键字来开启一个goroutine协程。协程的最大优势在于其轻量级,可以轻松创建上百万个协程而不会导致系统资源的耗尽,而线程和进程通常最多也不能超过1万个。举个例子:

go f()  // 创建一个新的 goroutine 运行函数f

在Go语言中,我们可以非常简单地使用关键字"go"来开启一个协程,从而实现异步处理函数f。只需在函数f的调用前面加上"go"关键字,就能使得该函数在一个独立的协程中异步执行。

不仅可以使用"go"关键字来开启一个协程异步执行具名函数,还可以使用"go"关键字来开启一个协程异步执行匿名函数。

go func(){
  // ...
}()

今天我们的重点不在这里,而是要讨论为什么Go语言适合处理高并发的情况。我们都知道,操作系统的CPU最小调度单位是线程,然而Go语言却使用了协程的概念。那么问题来了,Go语言是如何将这些协程交给CPU来处理的呢?如果无法将它们交给CPU处理,那么就算再创建多少协程也无法运行代码。在这里,我们就需要了解一下Go语言的调度器,也就是GPM调度模型。

GPM调度模型

可以借鉴一下以下图例,总的来说,我们可以像线程池一样,无论创建了多少协程,都需要将它们放入队列中。然后,剩下的任务就交给调度器来处理。

image

其中:

  • G:使用关键字"go"加上一个函数调用可以创建一个goroutine(简称G)。每次调用"go f()"都会创建一个新的G,其中包含要执行的函数f以及相关的上下文信息。
  • 全局队列(Global Queue)是用来存放等待运行的 G(Goroutine)的地方。
  • P 是指 goroutine 执行所需的物理资源,每个 P 最多可以承载 GOMAXPROCS 个 goroutine 的执行。
  • P 的本地队列是类似于全局队列的,它存放了等待运行的G,并且数量限制在256个以内。每当新建一个G时,优先将其加入到P的本地队列中,如果本地队列已满,则会批量移动部分G到全局队列中。
  • 为了使线程能够执行任务,需要通过获取调度器(P)来获取任务(G)。线程首先尝试从调度器的本地队列获取任务,如果本地队列为空,则线程会尝试从全局队列或其他调度器的本地队列获取任务。一旦线程获取到任务,就会执行任务,并在任务执行完毕后再次从调度器获取下一个任务,持续重复这个过程。

Goroutine 调度器和操作系统调度器通过 M 结合起来,形成了调度的基本单位。在这个结合中,每个 M 代表一个内核线程,而操作系统调度器则负责将这些内核线程分配到 CPU 的核心上进行执行。

channel

单纯地将函数并发执行是没有意义的,因为函数与函数之间需要进行数据交换,才能真正体现并发执行函数的意义。

虽然可以利用共享内存进行数据交换,但是在不同的 goroutine 中使用共享内存容易导致竞态问题的出现。为了确保数据交换的正确性,许多并发模型都需要通过使用互斥量对内存进行加锁来解决这个问题。然而,这种做法往往会带来性能问题,因为加锁操作会引入额外的开销。

Go语言采用的并发模型是CSP(Communicating Sequential Processes),这个模型强调了通过通信共享内存的方式来实现并发,而不是通过共享内存来实现通信。这种设计理念使得Go语言在处理并发任务时更加高效和安全。

如果说 goroutine 是Go程序中实现并发执行的主体,那么channel就是连接这些goroutine之间的纽带。channel是一种能够使得一个goroutine向另一个goroutine发送特定值的通信机制。

Mutex(互斥锁)在实现上也是使用了重量级锁。与Java的互斥锁相比,Go语言的Mutex有以下几点区别:

内存开销:Go语言的Mutex相对较轻量,使用较少的内存。这是因为Go语言的Mutex只包含一个字段,用于表示锁的状态,而Java的互斥锁通常包含更多的字段和数据结构。

锁的语法:在Go语言中,可以使用mutex.Lock()和mutex.Unlock()方法来手动控制锁的获取和释放,这样可以更灵活地控制锁的粒度。而在Java中,使用synchronized关键字来实现互斥锁,锁的粒度相对固定,只能对整个方法或代码块进行加锁。

锁的性能:由于Go语言的Mutex较为轻量,并且采用了更高效的实现方式,比如以下几个方面:

  • 自旋锁:在低并发的情况下,Go语言的Mutex会采用自旋锁的方式。自旋锁是一种忙等待的锁,当一个Goroutine尝试获取锁时,如果锁已经被其他Goroutine持有,则该Goroutine会一直循环检查锁的状态,直到成功获取锁。这种方式避免了线程切换的开销,提高了性能。
  • 优化的调度策略:Go语言的调度器在处理Goroutine的调度时会进行优化,尽量将锁的持有者与等待者调度到同一个处理器(P)上执行,减少线程之间的上下文切换和锁竞争的开销。
  • 等待队列:当一个Goroutine无法获取到Mutex锁时,它会进入等待队列,等待锁的释放。Go语言的Mutex的等待队列是基于链表实现的,相比Java的互斥锁使用的等待队列,具有更低的内存开销和更高的效率。

总结

并发编程是当前软件领域中一个重要的概念。Go语言通过goroutine和channel的特性,天生支持高并发处理,充分利用现代CPU的多核优势。与Java相比,Go语言的协程更加轻量级,可以轻松创建上百万个协程。Go语言的调度器采用GPM调度模型,通过将协程放入队列中,由调度器分配给CPU处理。此外,Go语言采用CSP模型,通过channel实现协程之间的通信,避免了共享内存带来的竞态问题。相比之下,Go语言的Mutex锁更轻量、灵活,并且具有更高的性能。总的来说,Go语言适合处理高并发的情况,成为了当前软件开发领域的热门语言之一。

标签:协程,语言,调度,并发,线程,答案,Go
From: https://www.cnblogs.com/guoxiaoyu/p/17933653.html

相关文章

  • Google 2023年最受欢迎的Chrome浏览器扩展
    前言Google最近发布了2023年最受欢迎的Chrome浏览器扩展插件,总计包括了12款扩展:可在此处下载其中包含多款AI驱动的扩展插件上榜,快来看看有你经常用的吗?插件汇总插件下载Scribe:使用AI记录工作流程,并创建分步指南,轻松培训和指导同事。DeepLTranslate:即时翻译网页......
  • Golang - sync.Pool底层源码详解
    sync.Pool是sync包下的一个组件,用来提高对象复用几率,减少gc的压力,减少内存分配,它是并发安全的,常用来存储并复用临时对象。任何存放区其中的值可以在任何时候被删除而不通知,在高负载下可以动态的扩容,在不活跃时对象池会收缩。可伸缩的,其大小仅受限于内存的大小,可以被看作是一......
  • 2023最新中级难度Flask框架面试题,包含答案。刷题必备!记录一下。
    好记性不如烂笔头内容来自面试宝典-中级难度Flask框架面试题合集问:Flask是什么?它有哪些特点?Flask是一个用Python编写的轻量级Web应用程序框架。它由ArminRonacher在2010年4月1日创建,原本只是一个愚人节玩笑,后来由于其简单、灵活和强大的特性而被广泛采用。以下是Flask......
  • Go 语言实现读取 pdf 文件内容
    本篇介绍一个如何在go语言环境下,如何解析/读取pdf文件内容从而进行一些业务逻辑。本篇将会介绍两种方案,可以按自己的需求进行对比和最终选择。1.背景最近在帮朋友做一个小的程序,帮他减少一些人工繁琐的工作,将一些机器可以做的事情交给机器,提高效率他效率。需求也相对简......
  • nodejs学习05——mongoose
    简介Mongoose是一个对象文档模型库,官网http://www.mongoosejs.net/作用:方便使用代码操作mongodb数据库初体验//1.安装mongoose//2.导入mongooseconstmongoose=require('mongoose');//设置strictQuery为truemongoose.set('strictQuery',true);//3.连接......
  • JDBC快速入门:看我如何用JDBC数据库连接池,轻松解决大量并发请求问题!
    我们已经知道JDBC是Java语言中用来规范客户端程序如何访问数据库的应用程序接口,也是大多数Java开发者与数据库打交道的必备工具。但是,你是否知道,JDBC在处理大量并发请求时,可能会遇到一些问题?这就是我们今天要讨论的主题——JDBC数据库连接池。<br>首先,让我们来了解一下什么是数据......
  • Golang合并、通过逗号拆分字符串
    Golang开发常用函数将逗号分隔的字符串转换为数组,将数组slice转为逗号分隔的string字符串,以及strings的其他函数。一、Go开发中最常用函数1.将数组slice转为逗号分隔的string字符串strings.Join(a[]string,sepstring)string或者strings.Join(str_arr,",")示例代码:......
  • JDBC快速入门:看我如何用JDBC数据库连接池,轻松解决大量并发请求问题!
    我们已经知道JDBC是Java语言中用来规范客户端程序如何访问数据库的应用程序接口,也是大多数Java开发者与数据库打交道的必备工具。但是,你是否知道,JDBC在处理大量并发请求时,可能会遇到一些问题?这就是我们今天要讨论的主题——JDBC数据库连接池。首先,让我们来了解一下什么是数据库连......
  • golang对map排序
    golang中map元素是随机无序的,所以在对maprange遍历的时候也是随机的,不像php中是按顺序。所以如果想按顺序取map中的值,可以采用以下方式:import("fmt""sort")funcmain(){m:=make(map[int]string)m[1]="a"m[2]="c"m[0]="b"......
  • go-carbon v2.3.1 发布,轻量级、语义化、对开发者友好的 Golang 时间处理库
    carbon是一个轻量级、语义化、对开发者友好的golang时间处理库,支持链式调用。目前已被awesome-go收录,如果您觉得不错,请给个star吧github.com/golang-module/carbongitee.com/golang-module/carbon安装使用Golang版本大于等于1.16//使用github库goget-ugithu......