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

并发编程是当前软件领域中不可忽视的一个关键概念。随着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调度模型
可以借鉴一下以下图例,总的来说,我们可以像线程池一样,无论创建了多少协程,都需要将它们放入队列中。然后,剩下的任务就交给调度器来处理。

其中:
- 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语言为你提供了答案!的更多相关文章
- Memcached笔记——(四)应对高并发攻击【转】
http://snowolf.iteye.com/blog/1677495 近半个月过得很痛苦,主要是产品上线后,引来无数机器用户恶意攻击,不停的刷新产品各个服务入口,制造垃圾数据,消耗资源.他们的最 ...
- SpringCloud应对高并发的思路
一.Eureka的高可用性 Eureka下面的服务实例默认每隔30秒会发送一个HTTP心跳给Eureka,来告诉Eureka服务还活着,每个服务实例每隔30秒也会通过HTTP请求向Eureka获取服务 ...
- 《即时消息技术剖析与实战》学习笔记10——IM系统如何应对高并发
一.IM 系统的高并发场景 IM 系统中,高并发多见于直播互动场景.比如直播间,在直播过程中,观众会给主播打赏.送礼.发送弹幕等,尤其是明星直播间,几十万.上百万人的规模一点也不稀奇.近期随着武汉新型 ...
- Memcached笔记——(四)应对高并发攻击
近半个月过得很痛苦,主要是产品上线后,引来无数机器用户恶意攻击,不停的刷新产品各个服务入口,制造垃圾数据,消耗资源.他们的最好成绩,1秒钟可以并发6次,赶在Database入库前,Cache进行Mis ...
- Linux下配置tomcat+apr+native应对高并发
摘要:在慢速网络上Tomcat线程数开到300以上的水平,不配APR,基本上300个线程狠快就会用满,以后的请求就只好等待.但是配上APR之后,Tomcat将以JNI的形式调用Apache HTTP服 ...
- 【Redis】1、Jedis对管道、事务以及Watch的操作来应对高并发
对于一个互联网平台来说,高并发是经常会遇到的场景.最有代表性的比如秒杀和抢购.高并发会出现三个特点: 1.高并发读取 2.高并发写入(一致性) 3.出现超卖问题 前端如何应对? 1.缓存静态数据,例如 ...
- (转)Linux下配置tomcat+apr+native应对高并发
摘要:在慢速网络上Tomcat线程数开到300以上的水平,不配APR,基本上300个线程狠快就会用满,以后的请求就只好等待.但是配上APR之后,Tomcat将以JNI的形式调用Apache HTTP服 ...
- 想玩转JAVA高并发,这些概念你必须懂!
我们在找工作时,经常在招聘信息上看到有这么一条:有构建大型互联网服务及高并发等经验,你第一时间想到的是媒体常说的双十一吗?带着问题,我们一起思考技术…. 高并发高并发 它是互联网分布式系统架构设计中必 ...
- 想玩转JAVA高并发,这些概念你必须懂
高并发高并发 它是互联网分布式系统架构设计中必须考虑的因素之一,通常是指,保证系统能够同时并行化处理海量请求 同步和异步 同步:发送一个请求,等待返回,然后再发送下一个请求.提交请求 -> 等待 ...
- 基于tomcat为了应对高并发模型实现webserver
在博客上,一个简单的AIOweb来样加工.查看AIO异步处理,依靠操作系统完成IO操作Proactor处理模型确实很强大,它可以实现高并发.高响应server一个很好的选择,但在tomcat中间con ...
随机推荐
- SQL Server 使用C#窗体与数据库连接,制作数据库查看器
SQL Server 使用C#窗体与数据库连接,制作数据库查看器 本文中心:讨论C#对SQL Server 的增删改查,使用Treeview制作数据库查看器. SSMS部分:确保SQL Server ...
- 利用python将数据写入CSV文件中
利用python将数据写入CSV文件中 全部代码如下: import csv # 1.创建文件对象 f = open('cav_file.csv', 'w', encoding='utf-8', ne ...
- 《最新出炉》系列初窥篇-Python+Playwright自动化测试-17-处理鼠标悬停
1.简介 有些测试场景或者事件,playwright根本就没有直接提供方法去操作,而且也不可能把各种测试场景都全面覆盖提供方法去操作.比如:就像鼠标悬停,一般测试场景鼠标悬停分两种常见,一种是鼠标悬停 ...
- Learning Hard C# 学习笔记: 6.C#中的接口
目的: 由于C#中的类只能单个继承, 为了满足多重继承(一个子类可以继承多个父类)的需求, 所以产生了接口. 多重继承是指一个类可以从多个父类继承属性和方法.在C#中,只允许单继承,即一个类只能有一个 ...
- 如何用ppt打印9张一面,并且去除边距?
如何用ppt打印9张一面,并且去除边距? 方法其实很简单,答主不要在ppt软件的打印选项里设置[每页打印9张幻灯片],而是使用默认的[每页打印1张幻灯片]. 然后去[打印机属性]里设置,我是 ...
- 每个后端都应该了解的OpenResty入门以及网关安全实战
简介 在官网上对 OpenResty 是这样介绍的(http://openresty.org): "OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成 ...
- Python 作用域:局部作用域、全局作用域和使用 global 关键字
变量只在创建它的区域内可用.这被称为作用域. 局部作用域 在函数内部创建的变量属于该函数的局部作用域,并且只能在该函数内部使用. 示例:在函数内部创建的变量在该函数内部可用: def myfunc() ...
- 字节序:大端和小端(Big endian and Little endian)(转自维基百科)
简介[编辑] 在几乎所有的机器上,多字节对象都被存储为连续的字节序列.例如在C语言中,一个类型为int的变量x地址为0x100,那么其对应地址表达式&x的值为0x100.且x的四个字节将被存储 ...
- shell解析xml文档
需要解析的源文件: /tmp/cameralist : <?xml version="1.0" encoding="UTF-8"?><came ...
- 聊聊 GPU 产品选型那些事
随着人工智能的飞速崛起,随之而来的是算力需求的指数级增加,CPU 已经不足以满足深度学习.大模型计算等场景的海量数据处理需求.GPU 作为一种强大的计算工具,无论是高性能计算.图形渲染还是机器学习领域 ...