并发编程是当前软件领域中不可忽视的一个关键概念。随着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语言为你提供了答案!的更多相关文章

  1. Memcached笔记——(四)应对高并发攻击【转】

    http://snowolf.iteye.com/blog/1677495 近半个月过得很痛苦,主要是产品上线后,引来无数机器用户恶意攻击,不停的刷新产品各个服务入口,制造垃圾数据,消耗资源.他们的最 ...

  2. SpringCloud应对高并发的思路

    一.Eureka的高可用性 Eureka下面的服务实例默认每隔30秒会发送一个HTTP心跳给Eureka,来告诉Eureka服务还活着,每个服务实例每隔30秒也会通过HTTP请求向Eureka获取服务 ...

  3. 《即时消息技术剖析与实战》学习笔记10——IM系统如何应对高并发

    一.IM 系统的高并发场景 IM 系统中,高并发多见于直播互动场景.比如直播间,在直播过程中,观众会给主播打赏.送礼.发送弹幕等,尤其是明星直播间,几十万.上百万人的规模一点也不稀奇.近期随着武汉新型 ...

  4. Memcached笔记——(四)应对高并发攻击

    近半个月过得很痛苦,主要是产品上线后,引来无数机器用户恶意攻击,不停的刷新产品各个服务入口,制造垃圾数据,消耗资源.他们的最好成绩,1秒钟可以并发6次,赶在Database入库前,Cache进行Mis ...

  5. Linux下配置tomcat+apr+native应对高并发

    摘要:在慢速网络上Tomcat线程数开到300以上的水平,不配APR,基本上300个线程狠快就会用满,以后的请求就只好等待.但是配上APR之后,Tomcat将以JNI的形式调用Apache HTTP服 ...

  6. 【Redis】1、Jedis对管道、事务以及Watch的操作来应对高并发

    对于一个互联网平台来说,高并发是经常会遇到的场景.最有代表性的比如秒杀和抢购.高并发会出现三个特点: 1.高并发读取 2.高并发写入(一致性) 3.出现超卖问题 前端如何应对? 1.缓存静态数据,例如 ...

  7. (转)Linux下配置tomcat+apr+native应对高并发

    摘要:在慢速网络上Tomcat线程数开到300以上的水平,不配APR,基本上300个线程狠快就会用满,以后的请求就只好等待.但是配上APR之后,Tomcat将以JNI的形式调用Apache HTTP服 ...

  8. 想玩转JAVA高并发,这些概念你必须懂!

    我们在找工作时,经常在招聘信息上看到有这么一条:有构建大型互联网服务及高并发等经验,你第一时间想到的是媒体常说的双十一吗?带着问题,我们一起思考技术…. 高并发高并发 它是互联网分布式系统架构设计中必 ...

  9. 想玩转JAVA高并发,这些概念你必须懂

    高并发高并发 它是互联网分布式系统架构设计中必须考虑的因素之一,通常是指,保证系统能够同时并行化处理海量请求 同步和异步 同步:发送一个请求,等待返回,然后再发送下一个请求.提交请求 -> 等待 ...

  10. 基于tomcat为了应对高并发模型实现webserver

    在博客上,一个简单的AIOweb来样加工.查看AIO异步处理,依靠操作系统完成IO操作Proactor处理模型确实很强大,它可以实现高并发.高响应server一个很好的选择,但在tomcat中间con ...

随机推荐

  1. Intrusion Detection Using Convolutional Neural Networks for Representation Learning 笔记

    Intrusion Detection Using Convolutional Neural Networks for Representation Learning 2.2 实验数据的预处理 为了确 ...

  2. Http协议之libcurl实现

    一.libcurl简介 libcurl是一个跨平台的网络协议库,支持http, https, ftp, gopher, telnet, dict, file, 和ldap 协议.libcurl同样支持 ...

  3. c语言代码练习6

    //输入三个数字,依次按照从大到小输出#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int main() { int a = 0 ...

  4. Python面向对象——Mixin机制、重载、多态与鸭子类型、绑定与非绑定方法、Python常见的内置函数

    文章目录 内容回顾 Mixin机制 1.什么是Mixin 2.Mixin来源 3.定义及优点 4.在python中的应用 5.在Django项目中的应用 重载(在子类派生的新方法中如何重用父类的功能) ...

  5. Ubuntu上解决快捷键与idea快捷键冲突

    Ubuntu上解决快捷键与idea快捷键冲突 一.ubuntu 本身系统导致,需要修改 ubuntu 快捷键 解决方案: 设置 按钮→系统设置→硬件选项区域中的"键盘"→切换到&q ...

  6. 用AI打造一个属于自己的歌手,让她C位霸气出道

    ​ 一.前言 今天玩儿点儿特别的,AI大行其道的今天,还没玩过AI模型的程序员绝对不是个好厨子.我本人比较喜欢音乐,但是一直没有出道,很是遗憾.那么今天,我就使用AI模型亲手打造一个堪比真人的歌手,让 ...

  7. C++11 列表初始化都做了什么?

    类的成员变量的初始化细节 首先,来看两个问题: 类的构造函数中,成员变量的列表初始化是如何实现的? 为什么列表初始化效率上优于在构造函数中为成员变量赋值? (后文中,将 "在构造函数中为成员 ...

  8. [C++]线段树 区间修改 单点查询

    线段树 区间修改 单点查询 请先阅读上一篇Bolg 算法思想 由于是区间修改 那就把下放的每一个线段给套上一层标记 来表达增加的值 单点查询就把那些标记穿起来就行了 当然 还要加上那原来的值 来举个例 ...

  9. 使用TS进行Vue-Router的Meta类型扩展

    目录 1.前言 2.解决 1.前言 使用Vue-Router时,会将一些字段信息附加到路由的Meta对象里面,比如图标icon,标题,权限等,如下: { path: '/billboard/board ...

  10. IoC容器趣谈

    今天我们来谈谈Spring的内核之一--IoC容器 大家可能会有这样的疑问: "这玩意为啥要叫容器呢?好奇怪" "容器不是装东西的吗?难道IoC容器也是用来装什么东西的? ...