golang 知识点总结

golang 基础

gopath 是什么意思

Gopath 是 Go 语言中用来管理代码包路径和依赖关系的一种机制。在 Go 语言中,一个代码包通常位于 $GOPATH/src/ 目录下的一个子目录中,并且使用其导入路径来标识。例如,如果你的 $GOPATH 是 /home/go,那么一个叫做 "example" 的代码包将会位于 /home/go/src/example 目录下。Gopath 的设计目的是为了方便 Go 语言的开发和组织管理,让不同的开发者和团队可以轻松地共享和复用代码。

GO111MODULE是什么

GO111MODULE GO111MODULE 有三个值:off, on 和 auto(默认值)。

GO111MODULE=off,go 命令行将不会支持 module 功能,寻找依赖包的方式将会沿用旧版本那种通过 vendor 目录或者 GOPATH 模式来查找。 GO111MODULE=on,go 命令行会使用 modules,而一点也不会去 GOPATH 目录下查找。 GO111MODULE=auto,默认值,go 命令行将会根据当前目录来决定是否启用 module 功能。这种情况下可以分为两种情形: 当前目录在 GOPATH/src 之外且该目录包含 go.mod 文件 当前文件在包含 go.mod 文件的目录下面。 当 modules 功能启用时,依赖包的存放位置变更为 $GOPATH/pkg,允许同一个 package 多个版本并存,且多个项目可以共享缓存的 module。

golang 包内的init函数是什么意思

在Golang中,每个包可以有一个或多个init函数,用于初始化包级别的变量,执行复杂的初始化操作或者执行与程序其他部分的交互操作。init函数是一种特殊的函数,在程序运行过程中会自动被调用。

init函数的作用比较广泛,可以用来初始化一些全局参数、创建数据库连接池、读取配置文件等等。当程序启动时,所有的init函数都会被自动执行,执行顺序是按照包导入的依赖关系逆序执行。即最后导入的包的init函数最先执行,依次向前执行,最先导入的包的init函数最后执行。

func main() {
    // 调用init函数
    foo.Init()
}

在大多数情况下,我们并不需要显式地调用init函数,它会自动执行。如果我们希望在程序执行期间再次调用特定的init函数,可以使用函数名的方式调用它,如以下代码所示:

需要注意的是,init函数不能被显式调用,否则会导致编译错误。init函数的命名和参数列表都是固定的,具体格式为:

func init() {

    // 函数体

}

golang的指针


func Test_a(t *testing.T) {
	var a int = 20 /* 声明实际变量 */
	var ip *int    /* 声明指针变量 */

	ip = &a /* 指针变量的存储地址 */

	fmt.Printf("a 变量的地址是: %x\n", &a)

	/* 指针变量的存储地址 */
	fmt.Printf("ip 变量储存的内容: %x\n", ip)

	/* 获取指针指向的数据 */
    /* 这个 "*" 这会 就是指针运算符号 */
	fmt.Printf("*ip 变量的值: %d\n", *ip) 
    // &ip 是一个取地址符号,表示获取变量 ip 的内存地址。& 符号是 Go 语言的取地址符号,它用于获取变量的内存地址。
	fmt.Printf("&ip 变量的值: %x\n", &ip)

	//对星号*的总结
	//在我们目前所学到的语法中,星号*主要有三种用途:
	//表示乘法,例如int a = 3, b = 5, c;  c = a * b;,这是最容易理解的。
	//表示定义一个指针变量,以和普通变量区分开,例如int a = 100;  int *p = &a;。
	//表示获取指针指向的数据,是一种间接操作,例如int a, b, *p = &a;  *p = 100;  b = *p;。	
   
  === RUN   Test_a
a 变量的地址是: c00008a150
ip 变量储存的内容: c00008a150
*ip 变量的值: 20
&ip 变量的值: c000088028
--- PASS: Test_a (0.00s)
PASS

参考 C语言中指针变量只能存储地址?_百度知道 (baidu.com)

指针变量里面存放的值(或者内容)是一个地址(c00008a150),一般变量存放的是数据本身,而指针变量存放的是数据的地址。

golang的指针变量与C的指针变量的区别

以下是使用 Go 和 C 语言声明、初始化和使用指针变量的示例代码:

Go 语言示例代码:

package main

import "fmt"


func main() {

    i := 10

    var p *int

    p = &i

    fmt.Println("i =", i)

    fmt.Println("p =", p)

    fmt.Println("*p =", *p)

}

上述代码中,使用 var p *int 声明了一个指针变量,使用 p = &i 将变量 i 的地址赋值给指针变量 p,最后使用 *p 来访问 p 指向的变量 i。

C 语言示例代码:


#include <stdio.h>

int main() {
    int i = 10;
    int *p;
    p = &i;

    printf("i = %d\n", i);

    printf("p = %p\n", p);

    printf("*p = %d\n", *p);

    return 0;

}

上述代码中,使用 int *p 声明了一个指针变量,使用 p = &i 将变量 i 的地址赋值给指针变量 p,最后使用 *p 来访问 p 指向的变量 i。注意,使用 %p 格式符打印指针变量的地址。

接口,方法,函数

以下是一个简单的例子:

方法:

type Rectangle struct {
  width, height float64
}
​
func (r Rectangle) Area() float64 {
  return r.width * r.height
}
​
func main() {
  r := Rectangle{10, 5}
  fmt.Println("Area: ", r.Area()) // 输出:Area: 50
}

在这个例子中,我们定义了一个矩形类型 Rectangle,然后定义了一个方法 Area() 用于计算矩形的面积。在 main 函数中,我们创建了一个矩形实例 r 并调用了该实例的 Area() 方法来计算矩形的面积。

方法的声明和函数类似,他们的区别是:方法在定义的时候,会在func和方法名之间增加一个参数,这个参数就是接收者,这样我们定义的这个方法就和接收者绑定在了一起,称之为这个接收者的方法。

函数:

func Add(x, y int) int {
   return x + y
}



func main() {
    sum := Add(3, 5)
    fmt.Println("The sum is: ", sum) // 输出:The sum is: 8
}

在这个例子中,我们定义了一个 Add 函数来计算两个整数的和。在 main 函数中,我们调用了 Add 函数并将结果存储在变量 sum 中,然后将其打印出来。

函数名称是小写开头的add,所以它的作用域只属于所声明的包内使用,不能被其他包使用。

如果我们把函数名以大写字母开头,该函数的作用域就大了可以被其他包调用。这也是Go语言中大小写的用处。比如Java中,就有专门的关键字来声明作用域private、protect、public等。

接口:


type Animal interface {
    Speak() string
}

type Cat struct {}

func (c Cat) Speak() string {
    return "Meow"
}

type Dog struct {}

func (d Dog) Speak() string {
    return "Woof"
}

func main() {
    animals := []Animal{Cat{}, Dog{}}
    for _, animal := range animals {
        fmt.Println(animal.Speak())
    }

}

在这个例子中,我们定义了一个 Animal 接口,其中包含一个 Speak 方法。然后我们实现了 Cat 和 Dog 两个结构体,它们都实现了 Animal 接口中的 Speak 方法。在 main 函数中,我们创建了一个 Animal 接口类型的切片并将 Cat 和 Dog 实例添加到其中,然后循环遍历该切片并调用每个实例的 Speak 方法,输出它们的声音。

方法需要一个结构体, 函数传入一个结构体对象,则这个方法就是这个结构体的方法。 结构体+方法=类

接口的实现也是一样的, 需要一个结构体。然后实现方法,在方法中传入对应的结构体,则这个结构体与方法就是 接口的实现。

总之一句话,

结构体+ 方法 就是类

golang的接受者是什么意思?

在 Golang 中,方法需要接收一个接收者(receiver),它相当于方法的上下文对象,决定了该方法是属于哪个类型的。接收者可以是值类型或指针类型,它们决定了方法在调用时对原始对象的影响。

具体来说,接收者可以是指向结构体的指针或值,也可以是引用类型(如 map 和 slice )的指针或值等。

接收者的语法定义如下:

func (接收者变量 接收者类型) 方法名(参数列表) 返回值列表 {

    // 方法实现

}

其中,接收者变量是任意合法的标识符(通常使用首字母小写),接收者类型是一个类型字面量,方法名和参数列表与普通函数类似,返回值列表也是可选的。

例如,下面是一个使用指针类型作为接收者的方法定义:

type Rectangle struct {
    length int
    width  int
}

func (r *Rectangle) Area() int {
    return r.length * r.width
}

在这个例子中,我们定义了一个 Rectangle 类型结构体,并定义了一种计算矩形面积的方法 Area(),方法接收一个指向 Rectangle 类型的指针作为接收者。因此,在调用 Area() 方法时,需要使用指针来操作原始 Rectangle 实例。如果接收者类型是 Rectangle 本身而不是指针类型,则该方法将无法修改原始实例的值。

可以参考:Go语言之函数方法_baby神的技术博客_51CTO博客

golang 有全局变量吗?

在 Golang 中,虽然没有真正意义上的全局变量,但可以使用包级别变量来实现类似的功能。

包级别变量是定义在包级别的变量,可以在整个包内的任何地方使用。它们在包的初始化期间被初始化,并保留整个程序生命周期。可以在任何函数内部访问包级别变量,但不能在函数内部定义包级别变量。

定义包级别变量时,通常需要使用 var 关键字声明变量,如下所示:

package mypackage

var GlobalVar string = "Hello, world!"

func MyFunc() {

    fmt.Println(GlobalVar)

}

在这个例子中,我们定义了一个名为 GlobalVar 的包级别变量,并将其初始化为字符串 Hello, world!。然后我们定义了一个名为 MyFunc 的函数,该函数可以访问和使用 GlobalVar 变量。

需要注意的是,全局变量容易导致命名冲突和隐藏,因此需要慎重使用。一般来说,程序员应该尽量避免全局变量并优先考虑使用局部变量或函数参数等封装性更好的方式来传递信息。

golang里面一个接口 有多个实现的时候怎么办 ?

在 Golang 中,一个接口可以被多个结构体实现。使用接口的主要目的是让代码更具可用性,使得一个函数或方法能够接受不同的实体类型的参数而不必考虑它们的具体类型。

如果一个接口有多个实现,那么在使用这个接口的函数或方法时,需要根据实际情况选择正确的实现。可以通过以下方式进行实现:

1. 根据实际情况选择接口实现

在使用接口的函数或方法时,可以根据需要的实际情况选择正确的实现。例如,如果有一个接口 Runner,有两个结构体实现了该接口,即 RunnerA 和 RunnerB,可以使用以下代码来根据实际情况选择:

var runner runner.Runner



if condition {

    runner = &RunnerA{}

} else {

    runner = &RunnerB{}

}



runner.Run()

使用 if 条件语句可以根据实际情况选择正确的接口实现。

2. 通过类型断言进行选择

另一种方式是通过类型断言来选择接口实现。如果一个接口有多个实现,可以使用类型断言来确定需要的实现。例如,如果有一个接口 Runner,有两个结构体实现了该接口,即 RunnerA 和 RunnerB,可以使用以下代码来根据实际情况进行选择:

func Run(r runner.Runner) {

    if ra, ok := r.(*RunnerA); ok {

        ra.RunnerA()

    } else if rb, ok := r.(*RunnerB); ok {

        rb.RunnerB()

    }

}

在这个例子中,我们定义了一个函数 Run,它接受一个 Runner 接口类型的参数 r。然后我们使用类型断言来确定传递给函数的实际对象类型,并根据实际类型调用相应的方法 RunnerA 或 RunnerB。

需要注意的是,如果一个接口有多个实现,应该在选择接口实现时使用遵循良好编程实践,尽量使用多态的方式,避免使用 if-else 语句等条件语句。因为这样会增加代码的复杂度和维护成本。

3、使用配置文件

golang 的驼峰规则

参考:golang的命名规范及大小写的访问权限_go 结构体中小写字段不能接收参数吗_思维的深度的博客-CSDN博客

  可以简单的理解成,首字母大写是公有的,首字母小写是私有的

Golang的驼峰规则主要分为两种:小驼峰和大驼峰。

小驼峰规则:除首个单词外,其他单词的首字母大写。例如:firstName、lastName。

大驼峰规则:所有单词首字母都大写。例如:FirstName、LastName。

Golang中的驼峰规则适用于变量、函数、结构体、接口等标识符的命名。根据Golang的语法规则,使用大写字母开头的标识符会被其他包访问,而使用小写字母开头的标识符只能在当前包中访问。

在使用驼峰规则时,应该注意以下几点:

1. 变量、函数、结构体等标识符应该尽量使用有意义的名称,方便其他程序员理解。

2. 不建议使用缩写或简写形式的名称,这样容易增加代码的歧义和难度。

3. 全局变量和常量应该使用大驼峰规则命名,局部变量和函数参数应该使用小驼峰规则命名。

4. 遵循一定的命名规则和风格,保证代码的易读性和一致性,例如:使用名词作为变量名,使用动词作为函数名等。

总之,Golang的驼峰规则可以提高代码的可读性和可维护性,是值得推荐使用的一种命名规范。

golang的middleware

golang的middleware是什么意思?

在Golang中,middleware(中间件)是一种模式,它允许将请求在到达应用程序的目标处理程序之前或之后经过预处理或后处理。它通常用于实现应用程序中的功能,如身份验证、日志记录、缓存和错误处理等。中间件可以被串起来,形成一个处理请求的管道。在Golang中,middleware通常是通过函数包装器的形式实现的,可以在HTTP请求处理过程中对请求/响应进行修改或添加。

package main

import (
	"log"
	"net/http"
	"time"
)

func loggingMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		log.Printf("Incoming request %s %s", r.Method, r.URL.Path)

		next.ServeHTTP(w, r)

		log.Printf("Completed request in %v", time.Since(start))
	})
}

func main() {
	handleRequest := func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello, World!"))
	}
	http.Handle("/", loggingMiddleware(http.HandlerFunc(handleRequest)))
	http.ListenAndServe(":8080", nil)
}

这个示例中定义了一个loggingMiddleware中间件,在处理每个HTTP请求之前记录请求的方法和URL,处理完请求并记录处理时间。handleRequest处理程序实现返回一个hello world消息。最后,通过将handleRequest包装在loggingMiddleware函数里,将该处理程序设置为HTTP处理程序并启动HTTP服务器。

golang的middleware与Spring的AOP编程有几个区别

1. 实现方式不同。Golang的middleware主要是通过HTTP处理函数包装器的形式实现的,而Spring的AOP编程则使用切面来包装方法或类。

2. 对象范围不同。在Spring中,AOP可以应用于任何对象,包括bean和切面本身。而在Golang中,middleware是针对HTTP请求的。

3. 范围限制不同。在Golang中,middleware只能针对HTTP请求,而Spring的AOP编程可以针对任何方法或类。

4. 实现目的不同。Golang的middleware通常用于HTTP请求的前置或后置处理,如身份验证、日志记录、缓存和错误处理等。而Spring的AOP编程则更广泛地应用于横切关注点的处理,如异常处理、事务控制、权限验证等。

虽然Golang的middleware和Spring的AOP编程有一些不同之处,但两者都提供了在应用中处理横切关注点的有效方法,使开发人员使用更少的代码实现更高的可维护性和可重用性。

golang 有哪些集合?

1. 切片(slice)

切片是一种动态数组类型,它可以动态增加和缩小长度,并且可以与底层数组共享内存。切片不需要像数组一样指定长度,而是以初始设置的长度扩充。可以通过len()和cap()函数分别获取切片的长度和底层数组的容量。

定义方式如下:

var s []int // 定义一个整型切片,未分配内存空间

s = make([]int, 5) // 分配长度为5的切片


或者可以简化为下面的一句话:

s := make([]int, 5) // 定义一个长度为5的整型切片

1. 映射(map)

映射是一种无序的key-value对集合,Go语言中使用map关键字定义映射。在映射中,每个key必须是唯一的,并且必须是可比较类型。同时value可以是任意类型。

定义方式如下:

var m map[string]int // 定义一个字符串到整型的映射

m = make(map[string]int) // 初始化映射

或者可以简化为下面的一句话:

m := make(map[string]int) // 定义一个字符串到整型的映射

channel怎么使用?

1. 在使用 Channel 时,需要注意避免死锁问题。当发送或接收数据的一方没有准备好时,会阻塞等待对方,如果双方都在等待,就会发生死锁。

2. 可以通过设置缓存区来避免阻塞。例如,创建带有缓存区的 Channel ch := make(chan int, bufferSize),当缓存区已满时,会阻塞发送方,而接收方可以继续接收数据。

示例代码:

package main

import (
    "fmt"
)

func main() {
    ch := make(chan int)
    go func() {
        ch <- 1
        ch <- 2
        ch <- 3
        close(ch)
    }()
    for val := range ch {
        fmt.Println(val)
    }
}

这个例子创建了一个 Channel ch,并在一个 goroutine 中向 Channel 中发送了三个整数。然后使用 range 遍历 Channel,输出接收到的数据。在发送完所有数据后关闭 Channel,这样遍历循环就会自动停止。

golang 怎么 获取channel的值?

在 Golang 中,可以通过以下方式获取 Channel 的值:

1. 使用 range 关键字遍历 Channel,例如:

for val := range ch {

    fmt.Println(val)

}

2. 使用 select 语句获取 Channel 的值,例如:

select {

case val := <-ch:

    fmt.Println(val)

default:

    fmt.Println("Channel is empty")

}

3. 使用 <- 运算符获取 Channel 的值,例如:

val := <-ch

fmt.Println(val)

需要注意的是,在使用 <- 运算符获取 Channel 的值时,如果 Channel 是空的,程序会阻塞等待,直到有数据可用。如果需要避免这种阻塞,可以使用 select 语句或者设置缓冲区。

这几种获取 Channel 值的方式有以下区别:

1. range 关键字遍历 Channel:使用 range 遍历 Channel 时,会一直等待直到 Channel 关闭。如果 Channel 中没有数据,遍历会阻塞等待数据到来。这种方式适合用于生产者-消费者模型中的消费者角色。

2. select 语句获取 Channel 值:select 语句可以同时监听多个 Channel,如果多个 Channel 中有数据可用,会随机选择一个 Channel 进行处理。如果所有的 Channel 都没有数据可用,可以设置 default 分支执行非阻塞操作。这种方式适合用于处理多个 Channel 的场景。

3. <- 运算符获取 Channel 值:使用 <- 运算符获取 Channel 值时,如果 Channel 中没有数据可用,会阻塞等待数据到来。这种方式适合用于生产者-消费者模型中的消费者角色。

需要根据具体的场景和需求选择合适的方式。如果只需要等待 Channel 中的数据,可以使用 <- 运算符获取数据;如果需要监听多个 Channel,可以使用 select 语句进行处理;如果需要遍历 Channel 中的所有数据,可以使用 range 关键字遍历 Channel。

goroutine

简单描述

Goroutine是Go语言中一种轻量级的线程模型,可以让程序在单个线程上并行执行多个任务,这些任务并发地运行,并且能够在一个非常高的层面上认为它们在同时执行。线程间的切换为每个goroutine提供了更低的开销,从而实现了高并发的特性。

在Go语言中,每个goroutine都是由Go语言运行时管理的。Goroutine的创建非常简单,只需要使用关键字“go”加上需要并行执行的函数即可,例如:

go func() {

    // do something

}()

这个语句会创建一个新的goroutine去并发地执行函数中的代码,同时主线程也会继续往下执行,不会被阻塞。在多个goroutine之间通信可以使用channel(管道)来实现。

使用goroutine可以大幅提高Go语言程序的性能,因为可以将耗时的任务放到独立的goroutine中执行,使得主线程可以继续执行其他任务,从而提高程序的并发能力和响应速度。

注意点

使用channel在多个goroutine之间通信是Go语言中实现并发的一种重要方式。在使用channel时,需要注意以下几点:

1. 创建channel时需要注意它的类型和缓冲容量。通常情况下,我们使用无缓冲channel来进行同步通信,在需要缓存一定数量的数据时,可以使用有缓冲channel。

2. 对于无缓冲channel的读和写都是阻塞的,需要保证写操作和相应的读操作在不同的goroutine中执行,避免死锁。

3. 对于有缓冲channel,如果缓冲满了,写操作会被阻塞,如果缓冲为空,读操作会被阻塞。需要注意在使用时要及时读取缓冲区中的数据,否则可能导致写操作阻塞。

4. channel是并发安全的,因为只有读写同步时才能使用channel的同步机制。

5. 需要注意channel的关闭操作,关闭channel后,读操作会返回一个默认值并判断是否成功,写操作会导致panic。

 goroutine  与java的异步的区别

1. 实现方式不同。Java中的异步通常是通过线程池和回调函数实现的,每个异步任务都会在独立的线程中执行。Go语言中的goroutine是通过轻量级线程实现的,可以在单个线程中并发执行多个goroutine。

2. 内存管理不同。在Java中,线程的创建和销毁会导致一定的内存开销,需要进行垃圾回收等操作,比较耗费性能。而Go语言的goroutine则可以轻松地在一个线程之间切换,不需要额外的内存管理操作,创建goroutine的开销非常小。

3. 并发性能不同。对于高并发的场景,Go语言的goroutine比Java的异步更加适合,因为Java的线程池会受到线程数量的限制,而Go语言的goroutine在单个线程中可以并发执行大量任务,不会因为线程数量的不足而导致性能下降。

????怎么控制goroutine的数量?

golang的序列化

在 Golang 中,序列化的方式有多种。

1. JSON 序列化:使用 Golang 的 encoding/json 包,将 Go 语言中的结构体编码为 JSON 字符串,或者将 JSON 字符串解码为 Go 结构体。

json 序列化:Golang学习(二十九)序列化和反序列化_golang 序列化_默子昂的博客-CSDN博客

2. Gob 序列化:使用 Golang 的 encoding/gob 包,将 Go 语言中的结构体编码为 Gob 字节流,或者将 Gob 字节流解码为 Go 结构体。

3. XML 序列化:使用 Golang 的 encoding/xml 包,将 Go 语言中的结构体编码为 XML 字符串,或者将 XML 字符串解码为 Go 结构体。

4. Msgpack 序列化:使用 Golang 的 github.com/vmihailenco/msgpack 包,将 Go 语言中的结构体编码为 Msgpack 字节流,或者将 Msgpack 字节流解码为 Go 结构体。

5. Protocol Buffers 序列化:使用 Golang 的 github.com/golang/protobuf 包,将 Go 语言中的结构体编码为 Protocol Buffers 字节流,或者将 Protocol Buffers 字节流解码为 Go 结构体。

6. BSON 序列化:使用 Golang 的 gopkg.in/mgo.v2/bson 包,将 Go 语言中的结构体编码为 BSON 字节流,或者将 BSON 字节流解码为 Go 结构体。

Golang 序列化之 ProtoBuf - 码农教程 (manongjc.com)

总之,在 Golang 中,序列化方式种类繁多,开发者可以根据自己的需求,选择合适的序列化方式。

golang的runtime

WaitGroup 的作用

`sync.WaitGroup` 是 Go 语言中用于等待一组 Go 协程完成执行的工具。在 Go 中,协程是轻量级的执行单元,可以并发地执行任务。`sync.WaitGroup` 可以确保在所有协程都执行完毕后再继续执行程序的其他部分,它的作用主要包括以下几点:

1. **等待协程完成**:通过调用 `Add` 方法增加计数器,然后每个协程执行完毕时调用 `Done` 方法减少计数器。主程序可以调用 `Wait` 方法来阻塞,直到计数器归零,即所有协程都执行完毕。

2. **同步多个协程**:可以使用 `WaitGroup` 来协调多个协程的执行顺序,确保某些协程在其他协程完成之后再开始执行。

下面是一个简单的示例,演示了如何使用 `WaitGroup`:

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	var wg sync.WaitGroup

	for i := 0; i < 5; i++ {
		wg.Add(1) // 增加计数器
		go worker(i, &wg)
	}

	wg.Wait() // 等待所有协程完成
	fmt.Println("All workers have finished.")
}

func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done() // 在函数结束时减少计数器
	fmt.Printf("Worker %d starting\n", id)
	time.Sleep(time.Second)
	fmt.Printf("Worker %d done\n", id)
}

在这个示例中,`main` 函数启动了 5 个协程,每个协程都会执行 `worker` 函数。在 `worker` 函数中,首先通过 `defer wg.Done()` 来确保在函数执行结束时调用 `Done` 方法来减少计数器。当所有协程执行完毕后,`main` 函数通过调用 `wg.Wait()` 来阻塞等待所有协程完成,然后输出 "All workers have finished."。

如果不使用 

如果不等待协程完成,那么主程序将会立即继续执行,而不管这些协程是否已经执行完毕。这可能会导致程序的不确定行为,因为主程序可能在协程还没有完成时就开始执行与协程相关的操作。

在某些情况下,不等待协程完成可能是有意义的,比如在主程序不关心协程的执行结果,只是希望并发执行一些任务的情况下。但在许多情况下,特别是当协程执行的是重要操作,主程序需要等待协程完成后才能继续执行时,等待协程完成是必要的。

如果主程序不等待协程完成,可能会导致一些问题,比如资源泄漏(如果协程持有某些资源但未及时释放)、未完全执行的任务(如果主程序在协程尚未完成时就继续执行相关操作,可能会导致任务未完成)等。因此,在使用协程时,通常建议在必要时等待协程完成。

在 Go 语言中,`sync` 包提供了三种类型的锁:

1. **Mutex(互斥锁)**:`sync.Mutex` 类型实现了最基本的互斥锁,它只有两个方法:`Lock()` 和 `Unlock()`。当一个 goroutine 调用 `Lock()` 方法时,如果锁没有被其他 goroutine 占用,那么它会立即获取到锁并继续执行,否则它会阻塞直到锁可用。`Unlock()` 方法用于释放锁。互斥锁是排他性的,同一时间只有一个 goroutine 能够持有锁。

2. **RWMutex(读写锁)**:`sync.RWMutex` 类型实现了读写锁,它允许多个 goroutine 同时读取共享资源,但只允许一个 goroutine 写入共享资源。与互斥锁相比,读写锁的性能更好,特别是在读操作远远多于写操作的情况下。

3. **WaitGroup(等待组)**:`sync.WaitGroup` 类型用于等待一组 goroutine 完成其任务。它通过 `Add()` 方法添加一个计数,`Done()` 方法减少一个计数,`Wait()` 方法阻塞直到计数器归零。等待组通常用于等待一组 goroutine 执行完毕后再继续执行其他操作,例如在主 goroutine 中等待所有工作 goroutine 完成后再结束程序。

这三种锁在 Go 语言中都是线程安全的,能够有效地管理并发访问共享资源。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值