Go基础知识

news2024/11/18 6:19:09

目标

  • 简单介绍一下 GO 语言的诞生背景,使用场景,目前使用方案
  • 简单介绍一下 GO的使用,GO的基础语法,简单过一下一些GO的语言例子
  • 着重介绍一下GO的特性,为什么大家都使用GO语言,GO的内存结构、为什么都说GO快
  • 论述清楚为什么大家都在开始使用GO

1 Go语言概述

1. 1Go语言的诞生背景

超线程技术和CPU多核化的普及为并行计算提供了技术支持和编程需求,程序的并发度有了极大的提升。
但是反观编程语言领域却没有什么大的动作,在多核 CPU 高效安全的协作方面,主流语言(C、C++、Java)能做的并不是很多。
Google工程师为了解决多核 CPU 高效安全地协作的问题,同时提高开发效率,便开始开发了Go语言。
Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。

  • 静态强类型:静态强类型语言是指在编译阶段就确定每个变量的数据类型的编程语言,并且这个数据类型在后续的代码中是不允许改变的。
  • 编译型:代码在执行前需要经过一个转换过程,也就是编译。编译器会将源代码(人类可读的高级指令)转换成目标代码(机器可执行的低级指令),并生成一个可执行文件。这个可执行文件可以在没有源代码的情况下运行。Java是一种既编译又解释的语言。
    在这里插入图片描述

1.2 Go语言的使用场景

Go在诞生之初,就以出色的性能和高效的并发处理能力,被广泛应用在以下的场景:

  1. 服务器端开发:Go语言非常适合用于构建大型、高性能的服务器端应用程序。它的并发处理能力和高效的I/O操作使其成为处理高并发请求的理想选择。越来越多的公司开始拥抱Go语言。
  2. 分布式系统:Go语言具有对并发的天然支持,其内置的goroutine和channel使得并发编程变得简单而高效。这使得Go语言成为构建分布式系统的理想选择,可以方便地实现并发的任务分发和数据同步。
  3. 云计算:Go语言具有快速编译、高效执行和低资源消耗等特点,非常适合用于构建云计算平台、容器编排工具、云存储等。许多知名的云计算项目,如Docker、Kubernetes等,都是使用Go语言开发的。
  4. 数据库和存储系统、系统编程、DevOps工具等

2 Go语言基础

2.1 Go语言的基础语法

以一个经典的Hello World示例:

// 指定包名
package main

// 导包
import "fmt"

// 函数定义 函数名(参数) 返回值
func main() {
    // 使用标准库进行打印
    fmt.Println("Hello, World!")
    // 结束分句不需要 ;
}

与Java相比有几点差异:

  1. 不需要分号:在Go语言中,一行代码的结束不需要分号,这与Java和许多其他C系列语言不同。
  2. 强制大括号:在Java中,如果if,for等语句的主体只有一行代码,可以省略大括号,但在Go语言 > 中,无论主体部分有多少行代码,都必须使用大括号。
  3. 缩进风格:Go语言采用的是Tab键进行缩进,而Java则是使用四个空格。
  4. 错误处理:Go语言没有异常处理,而是通过多值返回和错误接口进行错误处理,这与Java的try-> catch-finally异常处理方式不同。
  5. 变量声明:Go语言的变量声明方式也与Java不同,Go语言使用 var 关键字声明变量,同时Go语言还支持 “:=” 形式的短变量声明和初始化。
  6. 公有和私有:Go语言中,首字母大写的函数、变量是公有的,首字母小写的是私有的,而在Java中,公有和私有由关键字public和private表示。
  7. 类型声明:在Go语言中,类型声明放在变量名之后,而在Java中,类型声明放在变量名之前。
  8. 资源管理:Go语言使用defer关键字进行资源管理,使得资源的释放操作可以紧跟在资源的获取操作之后,而不需要去关心何时进行资源的释放。

2.2 Go语言的基本数据类型

// 布尔类型
// var : go关键字,用来声明变量,类型可加可不加,会进行类型推导
// 若不需要指定变量类型,也可以指定为:b := true,表示声明变量且赋值
var b bool = true
cBool := true
// 数字类型
var i int = 10
var f float32 = 12.34
// 字符串类型
var s string = "Hello Go"
var arr1 [5]int // 声明了一个int类型的数组
arr2 := [3]int{1,2,3} // 声明了一个长度为3的int数组
arr3 := [...]int{2, 4, 6, 8, 10} // 声明了一个长度为5的int数组,元素是2, 4, 6, 8, 10

// 切片会自动扩容
var mySlice []int //声明一个int切片
mySlice = []int{2, 3, 5, 7, 11, 13} // 创建包含6个元素的切片
mySlice = []int{2, 3, 5, 7, 11, 13}[1:4] // 创建包含原切片元素1-4的切片

// map类型
var m map[string]int

m["key1"] = 1

Go语言提供了一种简单、直接、灵活的方式来处理数据类型,可以根据实际需要来构造出复杂的数据类型。

2.3 Go语言的控制结构

if else 结构
package main

import"fmt"
func main() {
   var a int = 100
   if a < 20 {
      fmt.Printf("a 小于 20\n" )
   } else {
      fmt.Printf("a 不小于 20\n" )
   }
}
循环结构只有for循环,没有while关键字:
// 标准的for循环语句,初始化变量、循环判断条件,后处理,这三个部分都可以不指定
for i := 0; i < 10; i++ {
   fmt.Println(i)
}

// 类似while循环的使用
i := 0
for i < 10 {
    fmt.Println(i)
    i++
}

// 无限循环
for {
    fmt.Println("This loop will run forever.")
}

2.4 Go语言的函数

package main

import "fmt"

// 定义函数 函数名(参数A, 参数B) 返回值
func add(x int, y int) int {
   return x + y
}

func main() {
   // 调用函数
   fmt.Println(add(42, 13))
}

2.5 Go语言的错误处理方式

package main

import (
    "fmt"
    "errors"
)

// 我们定义了一个f1函数,该函数在参数等于42时返回一个错误,否则返回参数加3的结果。
func f1(arg int) (int, error) {
    if arg == 42 {
        return -1, errors.New("can't work with 42")
    }
    return arg + 3, nil
}

func main() {
    // 对f1函数的返回值进行了错误处理,如果返回错误,打印"f1 failed:“,否则打印"f1 worked:”。
    for _, i := range []int{7, 42} {
        if r, e := f1(i); e != nil {
            fmt.Println("f1 failed:", e)
        } else {
            fmt.Println("f1 worked:", r)
        }
    }
}

2.7 Go语言的面向对象编程

2.7.1 结构体(Structs)

Go语言没有“类”这个概念,但可以通过结构体实现类似的功能。
在Go语言中,结构体是一种复合的数据类型,可以包含零个或多个任意类型的值。我们可以把结构体看作是类的一种简化形式。

type Person struct {
    Name string
    Age  int
}

func main() {
    p := Person{Name: "Alice", Age: 20}
    fmt.Println(p.Name) // 输出: Alice
}
2.7.2 方法(Methods)

在Go语言中,可以给任意类型(包括结构体)定义方法。方法的定义形式和函数类似,只是在函数名前多了一个接收者参数,接收者可以是任意类型的变量。这样我们就可以在这个变量上调用这个方法。

type Rectangle struct {
    Width, Height float64
}

// 为Rectangle定义Area方法
func(r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func main() {
    r := Rectangle{Width: 10, Height: 5}
    fmt.Println(r.Area()) // 输出: 50
}
2.7.3 接口(Interfaces)

在Go语言中,接口是一种类型,它定义了一组方法,但这些方法不包含实现代码。
实现接口的类型(也就是实现了接口中的所有方法的类型),可以被看作是那个接口类型的变量。
这与Java等语言的接口有一定的差异,Go语言的接口更为灵活,实现接口的类型不需要显式声明它实现了哪些接口,只要实现了接口中的所有方法就可以。

type Shape interface {
    Area() float64
}

type Shape2 interface {
    Area() float64
}

type Circle struct {
    Radius float64
}

// Circle实现Shape接口
func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

func main() {
    c := Circle{Radius: 5}
    var s Shape2 = c
    fmt.Println(s.Area()) // 输出: 78.53981633974483
}
2.7.4 继承与组合

Go语言没有明确的继承机制,但可以通过组合的方式达到类似的效果。
在Go语言中,可以在一个结构体中嵌入其他的结构体或接口,被嵌入的结构体或接口就像是该结构体的一部分,该结构体可以直接访问被嵌入的结构体的字段和方法,从而实现了类似继承的功能。

type Person struct {
    Name string
    Age  int
}

type Student struct {
    Person  // 嵌入Person
    School string
}

func main() {
    s := Student{Person: Person{Name: "Alice", Age: 20}, School: "MIT"}
    fmt.Println(s.Name) // 输出: Alice
}
2.7.5 多态

Go语言通过接口实现多态,使用了一种鸭子类型(Duck Typing)的思想来实现接口。
简单来说,鸭子类型就是“如果它走起路来像鸭子,叫起来也像鸭子,那么它就是鸭子”。
当一个类型实现了某个接口的所有方法,那么这个类型的变量就可以被看作是这个接口类型的变量,可以被赋值给这个接口类型的变量。
这样,我们就可以使用接口类型的变量来调用实现类型的方法,实现多态。
定义一个Walker接口。

type Walker interface {
    Walk()
}

然后我们定义了一个Duck类型,并为其实现了Walk()方法:

type Duck struct {}

func(d Duck) Walk() {
    fmt.Println("Duck walks")
}

这样,Duck就被认为实现了Walker接口,即使我们并没有显式声明Duck实现了Walker接口。然后我们就可以把Duck类型的对象赋值给Walker类型的变量,通过这个变量调用Walk()方法:

var w Walker = Duck{}
w.Walk()  // 输出:Duck walks

如果一个类型需要实现多个接口,并且这些接口中有相同的方法,那么这个类型只能提供一个实现,这个实现会被所有调用这个方法的接口共享。

2.8 Go语言的并发模型和Channel

2.8.1 Go语言的Goroutine

Goroutine的创建和运行
Goroutine是Go语言的轻量级线程实现,通过Go关键字就能创建一个Goroutine,它的启动速度和资源消耗都远小于传统的线程。
程序启动时其主函数即在一个特殊的Goroutine中运行,一般称为为主Goroutine。示例代码如下:

go func() {
    fmt.Println("This is a goroutine")
}()

fmt.Println("This is main goroutine")

代码中,创建了一个Goroutine并在其中打印消息,然后在主Goroutine中打印消息。由于Goroutine的调度是由Go运行时进行的,所以两个打印语句的执行顺序是不确定的。

2.8.2 Go语言的Channel

2.8.3.1 Channel的基本概念和作用

Channel是Go语言中的一个核心类型,可以把它看成一个管道,可以通过它发送类型化的数据,在不同Goroutine之间进行通信,Channel是支持并发的。示例代码如下:

ch := make(chan int)
go func() {
    ch <- 1  // 向ch中发送数据
}()
fmt.Println(<-ch)  // 从ch中接收数据

2.8.3.2 Channel的创建和关闭
创建Channel的语法如下:

ch := make(chanint)  // 创建一个整型的Channel
关闭Channel的语法如下:
close(ch)  // 关闭Channel

注意,关闭后的Channel不能再发送数据,但是仍可以接收数据。多次关闭同一个Channel会导致panic。
2.8.3.3 通过Channel进行数据传递和通信
通过Channel发送和接收数据的语法如下:

ch <- 1// 向ch发送一个整数x := <-ch  // 从ch接收一个整数并赋值给x

如果Channel已经关闭,接收操作将立即完成,接收到的值为元素类型的零值。

2.6 Go语言编程实战

2.6.1 Go语言的Channel

生产者消费者。

3. Go语言特性及优势

为什么说Go提高开发效率?

  1. 语法简洁:Go语言的语法非常简洁,只有极少数的关键词,没有复杂的类继承,使得写代码变得更直接、更容易理解。这种简洁性使得学习曲线陡峭,新手更容易上手
  2. 编译速度、启动速度快:Go语言编译的是静态二进制文件,运行时不需要JVM(Java虚拟机)的启动和类加载过程,所以启动速度通常更快。启动速度快意味着在做开发时,调试的速度更快,在Docker等方式部署应用时能够更快run起来。
  3. 跨平台:Go语言编译后的执行文件可以直接在目标操作系统上运行,无需其他依赖,这使得部署应用变得非常简单,提高了开发效率。
  4. 运行占用内存小:Go语言使用静态编译,直接编译成机器语言运行,不需要像Java那样需要JVM来运行,因此在内存占用上会较小。
    在这里插入图片描述

运行Java 服务的内存为598 MB
运行Go的内存为19MB (不严谨对比)

3.1 Go语言的并发模型

3.1.1 GORoutine(协程)
  • 线程分为内核线程和用户态线程,协程是一种用户态线程。
    用户态线程跟内核态线程主要的2点区别:
  1. 管理方式:内核线程由操作系统内核管理和调度,而用户态线程由用户程序自己管理和调度。内核线程可以直接使用操作系统提供的调度算法,而用户态线程需要在用户程序中实现调度算法。
  2. 上下文切换:内核线程的上下文切换涉及到用户态和内核态之间的切换(切换时要校验指令等操作),所以开销比较大。用户态线程的上下文切换只发生在用户态,开销较小。
    Go Routine是Go语言中的一种轻量级用户态线程实现,也是Go语言中并发编程的核心。Go Routine与普通的线程或进程不同,它由Go运行时(Go Runtime)管理。

Go Routine的优点:

  1. 创建和销毁的成本更低:Go Routine的创建和销毁的成本远低于线程和进程,每个GoRoutine的栈初始只有2KB,对比普通的线程栈大小在MB级别。这让我们可以大量地创建Go Routine来处理任务,而不必担心系统资源被耗尽。
  2. 切换的成本更低:Go Runtime可以自动进行Go Routine之间的切换,与线程上下文切换相比,其成本更低。
  3. 更简单的同步机制:Go语言内建了Channel(管道)和Select机制,使得Go Routine之间的同步和通信更为简单高效。
    使用Go语言的go关键字,可以非常简单地启动一个Go Routine来并发执行任务,如:
    go doSomething()
    在这行代码中,doSomething函数将会在一个新的Go Routine中并发执行,而不会阻塞当前的程序执行流程。
3.1.2 Go的并发调度模型

Go语言的调度器是Go运行时系统的一部分,负责管理和调度Go Routine的执行。
Go语言的调度器设计采用了三级模型:G、P、M。

  • G: Go Routine,代表一个待执行的任务。
  • P: Processor,处理器,代表Go语言的调度上下文环境,每个P都有一个本地的Go Routine队列,并且每个P在同一时间只能被一个线程(M)所拥有。
  • M: Machine,代表一个操作系统的线程。
    Go语言的调度器采用了M:N的调度模型,也就是说,M个Go Routine会被N个操作系统线程所调度和执行。
    站在CPU的角度,一个线程可以持久的占用住CPU,Go Routine并发的使用一个线程资源去执行任务,减少线程调度带来的资源消耗。
    在这里插入图片描述

在Go语言的调度模型中:

  • P的数量由GOMAXPROCS环境变量决定,这个变量默认值是机器的CPU核数。P的数量决定了可以同时运行的Go Routine的数量。

  • M的数量没有上限,当所有的P都被占用,且有新的Go Routine需要运行时,Go运行时会创建一个新的M来运行这个Go Routine。

  • 当M在执行G的过程中,如果G发生了阻塞(如等待IO、调用系统调用等),那么M会将自己和G解绑,然后去运行其他的G。当G解除阻塞时,会再次被某个M所绑定并执行。

    • 如果当前的P的本地队列已满,那么运行的G会尝试将新的Goroutine放入全局队列或者其他P的本地队列,然后唤醒一个空闲的P和M组合来执行新的Goroutine。
      下面是具体的工作流程:
  1. 当一个Go程序启动时,会首先创建一个主G和一个主M,然后创建由GOMAXPROCS指定数量的P。主M会获取一个P,然后开始执行主G。
    在这里插入图片描述

  2. 在程序执行过程中,可能会创建新的G。新创建的G会首先被放到当前P的本地G队列中。如果本地G队列已满,就会被放到全局G队列中。

  3. 当一个M的当前G阻塞时(例如等待IO、系统调用、channel操作等),M会将自己和G解绑,然后去P的本地G队列中获取一个新的G来执行。如果本地G队列为空,M会尝试去全局G队列中获取G,或者从其他P的本地G队列中偷取G。如果都获取不到G,M就会阻塞,直到有新的G可执行。

  4. 当一个阻塞的G变为可执行状态时(例如等待的IO完成、系统调用返回、channel收到数据等),Go运行时会创建或者唤醒一个M,然后将G和M绑定,然后M会获取一个P,然后开始执行G。

  5. Go程序退出时,所有的M和P都会被销毁,所有的G都会被停止。
    Go语言的调度器在设计中采取了一些策略,以实现高效的并发性能:

  6. M:N 调度: Go语言的调度器使用了M:N的调度模型,这意味着M个Go协程(Go Routines)会被N个系统级线程(OS threads)调度和执行。这样的设计使得Go语言能够创建大量的协程而不会消耗大量的系统资源。

  7. 工作窃取(Work Stealing):当某个处理器(P)的本地Go协程队列为空时,它会尝试从全局Go协程队列或其他处理器的本地Go协程队列中窃取一半的协程。这种策略使得各个处理器之间的工作负载更加平衡,避免了某个处理器闲置而其他处理器的任务过多的情况。
    在这里插入图片描述

  8. 抢占式调度(Preemptive Scheduling):从Go 1.14版本开始,Go语言的调度器实现了真正的抢占式调度。这种策略能够防止长时间运行的协程阻塞其他协程的执行,保证了系统的响应性。这是通过插入一种称为"抢占请求"的机制实现的。当调度器决定需要抢占一个正在运行的协程时,它会向这个协程的栈插入一个抢占请求。然后,当这个协程下一次执行函数调用时,它会检查这个抢占请求,并主动让出CPU。
    在这里插入图片描述

需要注意的是,Go的抢占式调度仍然需要goroutine的协作。也就是说,如果一个goroutine没有进行任何函数调用(例如,它正在执行一个无函数调用的无限循环),那么它仍然可能无法被抢占。不过在实际情况中,大多数goroutine都会进行函数调用,所以这种情况很少发生。
4. 亲和性调度:Go 1.5版本开始,Go语言的调度器尝试将P绑定到固定的操作系统线程上,以提高缓存的利用率。这种策略可以提高程序的执行效率。
5. 延迟调度:Go语言的调度器在G阻塞时,不会立即进行调度,而是会等待一段时间。如果在这段时间内G变为可执行状态,那么可以避免一次无谓的调度。这种策略可以减少调度的开销。
6. 非阻塞网络IO:Go语言的网络库实现了非阻塞的网络IO操作,当G进行网络IO时,不会阻塞M和P,而是会让出P给其他的G执行。这样可以大大提高网络程序的并发性能。
以上这些策略使得Go语言的调度器可以高效地管理和调度大量的Go Routine,从而实现高性能的并发程序。

3.2 Go语言的内存结构

3.2.1 TCMalloc

TCMalloc是Thread Cache Malloc的简称,Google开发的一个内存分配器。它主要用于C和C++编程语言,是Go内存管理的起源.
Go的内存管理是借鉴了TCMalloc,TCMalloc的核心理念就是分级缓存。

在这里插入图片描述

  • Page
    操作系统对内存管理以页为单位,TCMalloc也是这样,只不过TCMalloc里的Page大小与操作系统里的大小并不一定相等,而是倍数关系。
  • Span
    一组连续的Page被称为Span,比如可以有4个页大小的Span,也可以有8个页大小的Span,Span比Page高一个层级,是为了方便管理一定大小的内存区域,Span是TCMalloc中内存管理的基本单位。
  • ThreadCache
    每个线程各自的Cache,一个Cache包含多个空闲内存块链表,每个链表连接的都是内存块,同一个链表上内存块的大小是相同的,也可以说按内存块大小,给内存块分了个类,这样可以根据申请的内存大小,快速从合适的链表选择空闲内存块。由于每个线程有自己的ThreadCache,所以ThreadCache访问是无锁的。
  • CentralCache
    是所有线程共享的缓存,也是保存的空闲内存块链表,链表的数量与ThreadCache中链表数量相同,当ThreadCache内存块不足时,可以从CentralCache取,当ThreadCache内存块多时,可以放回CentralCache。由于CentralCache是共享的,所以它的访问是要加锁的。
  • PageHeap
    PageHeap是堆内存的抽象,PageHeap存的也是若干链表,链表保存的是Span,当CentralCache没有内存的时,会从PageHeap取,把1个Span拆成若干内存块,添加到对应大小的链表中,当CentralCache内存多的时候,会放回PageHeap。
  • TCMalloc对象分配
    小对象直接从ThreadCache分配,若ThreadCache不够则从CentralCache中获取内存,CentralCache内存不够时会再从PageHeap获取内存,大对象在PageHeap中选择合适的页组成span用于存储数据。
3.2.2 Go的内存管理

在这里插入图片描述

  • mcache
    mcache与TCMalloc中的ThreadCache类似,mcache保存的是各种大小的Span,并按Span class分类,小对象直接从mcache分配内存,它起到了缓存的作用,并且可以无锁访问。但mcache与ThreadCache也有不同点,TCMalloc中是每个线程1个ThreadCache,Go中是每个P拥有1个mcach,因为在Go程序中,当前最多有GOMAXPROCS个线程在运行,所以最多需要GOMAXPROCS个mcache就可以保证各线程对mcache的无锁访问。

  • mcentral
    mcentral与TCMalloc中的CentralCache类似,是所有线程共享的缓存,需要加锁访问,它按Span class对Span分类,串联成链表,当mcache的某个级别Span的内存被分配光时,它会向mcentral申请1个当前级别的Span。但mcentral与CentralCache也有不同点,CentralCache是每个级别的Span有1个链表,mcache是每个级别的Span有2个链表。
    为什么span要分级别?
    在这里插入图片描述

  • mheap
    mheap与TCMalloc中的PageHeap类似,它是堆内存的抽象,把从OS(系统)申请出的内存页组织成Span,并保存起来。当mcentral的Span不够用时会向mheap申请,mheap的Span不够用时会向OS申请,向OS的内存申请是按页来的,然后把申请来的内存页生成Span组织起来,同样也是需要加锁访问的。但mheap与PageHeap也有不同点:mheap把Span组织成了树结构,而不是链表,并且还是2棵树,然后把Span分配到heapArena进行管理,它包含地址映射和span是否包含指针等位图,这样做的主要原因是为了更高效的利用内存:分配、回收和再利用。
    在Go的内存管理模型中,scav和free span都使用了treap作为其主要的数据结构。treap是一种特殊的二叉搜索树,它结合了二叉搜索树和堆的特性。
    在treap中,每个节点都有一个键(key)和一个优先级(priority)。树按照键来排序,但是节点的布局又遵循堆的性质,即每个节点的优先级都高于或等于其子节点的优先级。这样,treap既能像二叉搜索树一样高效地进行查找操作,又能像堆一样高效地进行插入和删除操作。

在Go的内存管理中,free span和scav span的treap的键是span的起始地址,优先级是随机生成的。这样,可以快速地找到一个具有指定起始地址的span,也可以快速地插入和删除span。
使用treap作为数据结构的一个主要优点是它可以高效地支持合并和分割操作。这在内存管理中是非常重要的,因为经常需要将多个小的空闲区域合并成一个大的空闲区域,或者将一个大的空闲区域分割成多个小的空闲区域。

基于以上数据结构,go当中一个对象的创建流程如下:

  1. 当一个 goroutine 需要创建一个对象时,首先会尝试从它自己的 mcache 中分配内存。mcache 是每个 goroutine 独享的小型内存缓冲区,用于存储一些小的、经常使用的对象。
  2. 如果 mcache 中的内存不足,那么 goroutine 会尝试从 mcentral 中获取内存。此操作过程需要进行加锁操作。
  3. 如果 mcentral 中的内存也不足,那么 mcentral 会从 mheap 中申请一大块内存。mheap 是一个全局的大型内存池,是从操作系统直接申请到的内存。此操作过程需要进行加锁操作。
  4. 如果 mheap 中的内存也不足,那么 Go 会向操作系统请求更多的内存。如果操作系统无法提供更多的内存,那么 Go 会抛出内存不足(out of memory)的错误。
  5. 一旦获取到足够的内存,Go 就会在这块内存上创建对象,并返回这个对象的引用。
    对于大对象的创建,Go 语言的处理方式与小对象有所不同。在 Go 语言中,被定义为大对象的具体大小阈值可能会根据不同的实现和平台有所不同。但一般来说,如果一个对象的大小超过了一个阈值(比如32KB),那么它就会被视为一个大对象。
    对于大对象,Go 语言通常会直接在堆(heap)上进行分配。大对象不会经过 mcache 或 mcentral,而是直接从 mheap 中申请内存。这是因为大对象由于其大小,不适合放在 mcache 或 mcentral 中,否则会占用过多的空间和内存碎片。

4 总结

GO是一个语法简洁,天然支持高并发,编译、运行速度快,能够以较小的资源处理较多的请求的语言。学习曲线平缓、语言生态越来越丰富。
在未来我们的服务也可以可以考虑使用GO来作为一种开发语言的考虑,来减少线上的资源使用,提高开发效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1896871.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

小蜜蜂WMS与小蜜蜂WMS对接集成根据条件获取客户信息列表(分页)连通新增客户信息(小蜜蜂读写测试)

小蜜蜂WMS与小蜜蜂WMS对接集成根据条件获取客户信息列表&#xff08;分页&#xff09;连通新增客户信息(小蜜蜂读写测试) 接通系统&#xff1a;小蜜蜂WMS 天津市小蜜蜂计算机技术有限公司&#xff08;acbee&#xff0c;TianJinACBEEComputerTechnologyCo.,Ltd&#xff09;成立于…

如何选择合适的滑动变阻器?

滑动变阻器是可以改变电阻值的电子元件&#xff0c;广泛应用于各种电子设备和电路中。选择合适的滑动变阻器对于保证电路的正常工作和提高设备的性能具有重要意义。以下是选择滑动变阻器时需要考虑的几个关键因素&#xff1a; 1. 电阻范围&#xff1a;滑动变阻器的电阻范围是指…

【基于R语言群体遗传学】-7-遗传变异(genetic variation)

一些新名词 Continuous time: 连续时间&#xff0c;是指不间断流动的时间&#xff0c;不以单位时间形式出现。 Diffusion: 扩散&#xff0c;是指粒子从高浓度区域向低浓度区域的被动净移动。 Discrete time: 离散时间&#xff0c;是指被划分为单位的时间&#xff0c;例如每个…

【总线】AXI4第八课时:介绍AXI的 “原子访问“ :独占访问(Exclusive Access)和锁定访问(Locked Access)

大家好,欢迎来到今天的总线学习时间!如果你对电子设计、特别是FPGA和SoC设计感兴趣&#xff0c;那你绝对不能错过我们今天的主角——AXI4总线。作为ARM公司AMBA总线家族中的佼佼者&#xff0c;AXI4以其高性能和高度可扩展性&#xff0c;成为了现代电子系统中不可或缺的通信桥梁…

空间数据采集与管理:为什么选择ArcGISPro和Python?

你还在为找不到合适的数据而苦恼吗&#xff1f;你还在面对大量数据束手无策&#xff0c;不知如何处理吗&#xff1f;对于从事生产和科研的人员来说&#xff0c;空间数据的采集与管理是地理信息系统&#xff08;GIS&#xff09;和空间分析领域的关键环节。通过准确高效地采集和管…

数据库、创建表、修改表

一、数据库 1、登陆数据库 2、创建数据库zoo 3、修改数据库zoo字符集为gbk 4、选择当前数据库为zoo 5、查看创建数据库zoo信息 6、删除数据库zoo 二、创建表 1、创建一个名称为db_system的数据库 2、在该数据库下创建两张表&#xff0c;具体要求如下 员工表 user…

2024年洗地机哪个牌子好?内行人最建议这4个:清洁力口碑公认都不错

在当代生活中&#xff0c;洗地机可以称得上是一款必备“神器”&#xff0c;劳累的清洁、繁忙的时间、漫天纷飞的宠物毛发&#xff0c;都是家庭清洁面前的一座座大山。而洗地机的出现&#xff0c;完美解决了这些问题&#xff0c;既节约出了很多时间&#xff0c;又达到了很好的清…

检索增强生成RAG系列7--RAG提升之高级阶段

系列5中讲到会讲解3个方面RAG的提升&#xff0c;它们可能与RAG的准确率有关系&#xff0c;但是更多的它们是有其它用途。本期来讲解第三部分&#xff1a;高级阶段。之所以说是高级阶段&#xff0c;可能是不好归一&#xff0c;而且实现起来相对于前面来说可能更为复杂。 目录 1…

在Ubuntu上安装VNC服务器教程

Ubuntu上安装VNC服务器方法&#xff1a;按照root安装TeactVnc&#xff0c;随后运行vncserver输入密码&#xff0c;安装并打开RickVNC客户端&#xff0c;输入服务器的IP&#xff0c;最后连接输入密码即可。 VNC或虚拟网络计算&#xff0c;可让您连接到远程Linux / Unix服务器的…

如何在 Odoo 16 中继承和更新现有邮件模板

在本文中,让我们看看如何在 Odoo 16 中继承和编辑现有邮件模板。我们必须这样做才能对现有模板的内容进行任何调整或更新。让我们考虑一个在会计模块中更新邮件模板的示例。 单击“account.move”模型中的“发送并打印”按钮后,将打开上述向导。在这里,我们将进行更改。从向…

【遇坑笔记】Node.js 开发环境与配置 Visual Studio Code

【遇坑笔记】Node.js 开发环境与配置 Visual Studio Code 前言node.js开发环境配置解决pnpm 不是内部或外部命令的问题&#xff08;pnpm安装教程&#xff09;解决 pnpm : 无法加载文件 C:\Program Files\nodejs\pnpm.ps1&#xff0c;因为在此系统上禁止运行脚本。 vscode 插件开…

植物大战僵尸杂交版V2.1+修改器+融合版

植物大战僵尸杂交版v2.1 新增新植物&#xff0c;全新模式与玩法&#xff01; 内含窗口放大工具与修改器 主播同款游戏&#xff0c;下载使用即可&#xff01; 链接: https://pan.baidu.com/s/1k1BkDDgAtVILAEpHBNsZNw?pwdn81t 提取码: n81t

基于Java中的SSM框架实现野生动物公益保护系统项目【项目源码+论文说明】计算机毕业设计

基于Java中的SSM框架实现野生动物公益保护系统演示 摘要 本系统按照网站系统设计的基本流程&#xff0c;遵循系统开发生命周期法和结构化方法&#xff0c;基于Java语言设计并实现了野生动物公益保护系统。该系统基于浏览器/服务器模式&#xff0c;采用JSP技术&#xff0c;后台…

nginx的匹配及重定向

一、nginx的匹配&#xff1a; nginx中location的优先级和匹配方式&#xff1a; 1.精确匹配&#xff1a;location / 对字符串进行完全匹配&#xff0c;必须完全符合 2.正则匹配&#xff1a;location ^~ ^~ 前缀匹配&#xff0c;以什么为开头 ~区分大小写的匹配 ~* 不区分…

内衣迷你洗衣机什么牌子好?四款亲测表现出色内衣洗衣机安利

相信不少小伙伴都跟我一样。一方面&#xff0c;认为内裤、袜子与大件的上衣、裤子放一块清洗&#xff0c;会感觉很不卫生&#xff0c;而且穿在身上也不安心。但是另一方面&#xff0c;本身又很懒惰&#xff0c;也不想自己用手洗&#xff0c;不但经常会遗漏一些污渍&#xff0c;…

探索智能合约在医疗健康领域的革新应用

随着区块链技术的发展&#xff0c;智能合约作为其重要应用之一&#xff0c;在医疗健康领域展示了巨大的潜力和革新性。智能合约是一种基于区块链的自动化执行协议&#xff0c;它可以在无需中介的情况下执行和验证合同。在医疗健康领域&#xff0c;智能合约不仅简化了数据管理和…

CFS三层内网渗透——外网打点(一)

目录 外网打点 先爆破一下看看有没有啥可进攻路径 尝试那个可疑的路径发现是thinkphp这个框架&#xff0c;同时也知道了版本&#xff0c;那就nday打吧 写入php ​编辑写入php成功&#xff0c;简简单单nday拿下​编辑 蚁剑rce尝试链接 打点成功 外网打点 先爆破一下看看有…

护眼热点:台灯护眼是真的吗?一起来看台灯的功能作用有哪些

如今近视问题日益严峻&#xff0c;尤为引人瞩目的是&#xff0c;高度近视学生群体占比已逼近10%的警戒线&#xff0c;且这一比例伴随着学龄的增长而悄然攀升——从幼儿园6岁孩童中那令人忧虑的1.5%&#xff0c;到高中阶段惊人的17.6%&#xff0c;每一组数据都敲响了保护儿童视力…

Java面试八股之MySQL的pconenct和connect有什么区别

MySQL的pconenct和connect有什么区别 在PHP中&#xff0c;mysql_pconnect() 和 mysql_connect() 是用来建立与MySQL数据库服务器连接的两个函数&#xff0c;它们的主要区别在于连接的持久性。 mysql_connect(): 这个函数每次调用都会尝试创建一个新的数据库连接。 当PHP脚本…

超实用的VS Code插件推荐

VS Code代码编辑器中提供了丰富的插件&#xff0c;满足不同开发者的需求。这里为大家推荐一些强大的VS Code插件&#xff0c;帮助你打造一个个性化的开发环境&#xff0c;让你的编码体验更加舒适和高效。 打开扩展模块&#xff0c;输入安装包名称&#xff0c;回车进行搜索&…