goroutine学习

goroutine 为什么比java中的线程更加轻量?

两种并发机制在资源消耗、调度方式、创建和管理上有明显的差异:

  1. 堆栈大小:

    • Goroutine 的初始堆栈大小相对较小,通常在几 KB(2KB或4KB)左右,而且可以根据需要动态伸缩。这意味着在同样的内存条件下,可以创建更多的 goroutine。
    • Java 线程的堆栈大小通常较大,可能从几百 KB 到几 MB 不等,这取决于 JVM 的配置和操作系统。Java 线程的堆栈不会动态伸缩,因此每个线程都会占用更多的内存资源。
  2. 调度:

    • Goroutine 是由 Go 运行时(runtime)管理的,使用的是 M:N 调度模型(多个 goroutine 可以映射到多个或单个操作系统线程)。Go 运行时包含自己的调度器,它可以在用户态进行调度,避免了内核态与用户态之间的频繁切换,这使得创建和切换 goroutine 的成本比线程低得多。
    • Java 线程通常是直接映射到操作系统原生线程(1:1 模型),由操作系统内核进行调度。这意味着线程的创建和切换需要进行用户态和内核态之间的切换,这比用户态的调度更耗时。
  3. 创建和管理:

    • Goroutine 的创建和管理是由 Go 语言的运行时直接处理的,这使得它们的创建和销毁非常快速和高效。
    • Java 线程 的创建和管理则是由 JVM 和操作系统共同处理的,这通常比 goroutine 更复杂,因此也更耗费资源。
  4. 上下文切换:

    • Goroutine 之间的上下文切换通常比 Java 线程之间的上下文切换要快,因为它们需要保存和恢复的状态更少。

1、创建goroutine:

go func() {

    // 业务逻辑

}()

2、goroutine中发生了panic怎么办:

(1)使用recover函数来恢复程序的执行。

(2)recover函数只有在defer函数中调用才能起作用,它会捕获当前goroutine中的panic,并返回panic所传递的值。
  注意:recover函数只能在同一个goroutine中恢复panic,不能跨越多个goroutine。

go func() {

    defer func() {

        if err := recover(); err != nil {

            fmt.println(err)

        }

    }()

}()

3、协程间通信如何实现:

(1)channel 是一种用于协程间通信和同步的重要机制。

         当我们创建一个 channel 时,实际上是在内存中分配了一个 channel 对象,包括一个队列和一些控制信息,用于协程之间的数据传输。

(2)channel 对象是什么时候被销毁的?

         当没有任何协程在使用一个 channel 时,该 channel 会被垃圾回收器回收。
        也就是说,当所有使用该 channel 的协程都已经结束或退出时,该 channel 对象才会被销毁。

4、goroutine中使用channel,channel阻塞了怎么办:

       往已满的channel中发送数据 或 从一个空channel中接收数据时,channel 会阻塞当前 goroutine,等待操作的另一方执行相应的操作后才会继续执行。

       如果 channel 阻塞了,可以考虑以下几种方式:

    (1)、使用非阻塞式的 channel 操作:可以使用 select 语句进行非阻塞的 channel 操作。通过在 select 语句中使用 default 分支或者带有超时的 case 分支,可以在 channel 阻塞时执行其他操作或者超时退出。

    (2)、使用带缓冲的 channel:带缓冲的 channel 可以在一定程度上避免 channel 阻塞的问题。通过给 channel 设置缓冲区,可以在 channel 不满或不空时发送或接收数据,并避免阻塞。

    (3)、调整 goroutine 的数量:减少 goroutine 的数量或者增加并发操作的数量,可以在一定程度上避免 channel 阻塞的问题。

     扩展一:带超时的case分支:

// 在下面的代码中,使用 <-time.After(time.Second) 创建一个定时器,它将在 1 秒后向一个无缓冲的 channel 发送一个时间值。

// 因此,在 select 语句中,如果 1 秒内没有任何其他 channel 发生变化,就会执行超时操作。

// 需要注意的是,time.After 函数返回的是一个 channel,因此在 select 语句中需要使用 <- 符号来接收其返回值

select {

case <-ch1:

    // 处理 ch1 的数据

case <-ch2:

    // 处理 ch2 的数据

    // <-time.After(time.Second) 创建一个定时器,它将在 1 秒后向一个无缓冲的 channel 发送一个时间值。

case <-time.After(time.Second):

    // 在 1 秒后执行超时操作

}

5、goroutine是什么时候销毁的:

(1)启动一个 goroutine 时,实际上是在创建一个新的协程并在其中执行指定的函数,与主协程并发地执行。
         当函数执行结束后,该 goroutine 也就自然而然地被销毁了。
(2)需要注意的是,goroutine 的生命周期与其所在的程序进程相关联,它们在执行结束后会自动退出并被 Go 运行时系统回收。
         也就是说,只要程序进程没有结束,goroutine 就会一直存在,除非它自己调用了 return、panic 或 recover 函数,或者被调度器强制终止。
         在一般情况下,goroutine 在执行结束后就会自动退出,并被 Go 运行时系统回收。

6、goroutine 和 消息中间件 使用场景有什么区别:

(1)它俩都是在编写并发程序时常用的工具,但是使用场景有所不同。
(2)Goroutine 适用于处理大量短时间的任务,比如网络请求、IO 操作等。
(3)消息中间件是一种用于分布式系统中消息传递的工具,可以在不同的进程、主机甚至不同的系统之间传递消息,适用于大规模分布式系统中的任务调度、数据传输等场景。

因此,Goroutine 适用于单机并发场景,而消息中间件适用于分布式系统中的任务调度和消息传递场景。
当需要处理的任务量很大时,可以考虑使用消息中间件来进行任务调度和消息传递,而不是仅仅使用 Goroutine 来实现并发。

7、goroutine中调用远程服务,失败了怎么办:

(1)返回错误信息并重试:可以使用循环或递归等方式进行重试,直到成功或达到最大重试次数为止。注意:在进行重试时需要考虑到错误的类型和重试的间隔时间等因素,以免重复出现相同的错误。
(2)通过日志记录错误:在发生错误时,可以将错误信息记录到日志中,方便后续排查问题。
(3)抛出异常并结束程序:如果错误是无法处理的,比如服务端已经停止了服务,可以抛出异常并结束程序。在捕获异常时,可以使用recover 函数来捕获异常并进行处理。

  无论采用哪种方式来处理错误,在进行远程服务调用时,一定要做好异常处理和错误处理,以确保程序的稳定性和健壮性。

8、go语言recover知识点:


(1)仅在 defer 函数中有效:recover 函数只能在 defer 函数中使用。
(2)捕获 panic:recover 函数只能捕获到当前 goroutine 中的 panic。当 panic 被捕获后,程序会从 panic 处开始恢复执行。
(3)返回值:当 recover 函数捕获到一个 panic 时,它会返回传递给 panic 函数的值。如果 recover 函数没有捕获到任何 panic,则返回 nil。
(4)阻止程序崩溃:recover 函数的主要目的是阻止程序崩溃。当程序发生 panic 时,通过使用 recover 函数可以避免程序终止。

package main

import "fmt"

func main() {

    defer func() {

        if r := recover(); r != nil {

            fmt.Println("Recovered from:", r)

        }

    }()

    fmt.Println("Starting the program")

    panic("A panic occurred")

    fmt.Println("End of the program"// 这行代码将不会被执行,因为程序在 panic 处恢复执行。

}

// 输出

Starting the program

Recovered from: A panic occurred

请注意,尽管可以使用 recover 来处理异常,但在 Go 中,建议尽可能使用错误值而不是 panic 和 recover,因为错误值通常更容易理解和处理。
panic 和 recover 主要用于处理那些无法预料或难以处理的错误情况。

9、多个goroutine直接怎么保障顺序执行:


可以使用无缓冲的信道来实现同步,可以确保一个goroutine在另一个goroutine完成后才开始执行。

package main

import (

    "fmt"

    "time"

)

func firstTask(done chan bool) {

    fmt.Println("Executing first task...")

    time.Sleep(1 * time.Second)

    // 第一个goroutine完成时,向done1发送一个值

    done <- true

}

func secondTask(done chan bool) {

    fmt.Println("Executing second task...")

    time.Sleep(1 * time.Second)

    // 第二个goroutine在完成时向done2发送一个值

    done <- true

}

func main() {

    // 创建两个无缓冲的信道。

    done1 := make(chan bool)

    done2 := make(chan bool)

    // 当第一个goroutine完成时,它将向done1发送一个值,

    go firstTask(done1)

    // 主goroutine会阻塞等待接收这个值,当接收到这个值后,主goroutine才会继续执行第二个goroutine。

    <-done1

    // 第二个goroutine在完成时向done2发送一个值,

    go secondTask(done2)

    // 主goroutine阻塞等待接收这个值。

    <-done2

    // 当接收到这个值后,主goroutine将继续执行,输出 "All tasks completed."。

    fmt.Println("All tasks completed.")

}

在这个示例中,done1 和 done2 是无缓冲的信道。

当第一个goroutine完成时,它将向done1发送一个值,主goroutine会阻塞等待接收这个值,当接收到这个值后,主goroutine才会继续执行第二个goroutine。

类似地,第二个goroutine在完成时向done2发送一个值,主goroutine阻塞等待接收这个值。当接收到这个值后,主goroutine将继续执行,输出 "All tasks completed."。

10、开启一个goroutine,怎么控制关闭它:

可以使用一个 chan bool 类型的 channel 来控制一个 goroutine 的生命周期,从而实现控制其关闭的功能。
具体实现方式如下:
(1)在主函数或其他需要控制 goroutine 的地方,创建一个 bool 类型的 channel,并将其传递给要启动的 goroutine。
(2)在 goroutine 中,使用一个无限循环来监听这个 channel 的状态,如果 channel 接收到了信号,就退出循环,从而结束 goroutine。
(3)当需要关闭 goroutine 时,向该 channel 发送一个信号,通知 goroutine 退出循环。

func startWorker(stopCh chan bool) {

    for {

        // 在这里执行 goroutine 的任务逻辑

        // 在 goroutine 中,使用一个无限循环来监听这个 channel 的状态,如果 channel 接收到了信号,就退出循环,从而结束 goroutine。

        select {

        case <-stopCh:

            // 收到关闭信号,退出循环

            return

        default:

            // 如果没有收到关闭信号,则继续循环执行任务逻辑

        }

    }

}

func main() {

    // 主函数创建一个 bool 类型的 channel,并将其传递给要启动的 goroutine。

    stopCh := make(chan bool)

    go startWorker(stopCh)

    // 等待一段时间后关闭 goroutine

    time.Sleep(time.Second * 5)

    stopCh <- true

}

在上面的代码中,我们创建了一个 stopCh channel,然后在 startWorker 函数中通过一个无限循环来执行 goroutine 的任务逻辑。

在每次循环开始时,使用 select 语句来监听 stopCh channel 的状态。如果收到了关闭信号,就退出循环并结束 goroutine。
在 main 函数中,我们通过 go startWorker(stopCh) 启动了一个 goroutine。然后在等待 5 秒后,向 stopCh channel 发送了一个信号,通知 goroutine 结束循环并退出。

  • 17
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Golang(又称Go)是一种静态类型,编译型,开源的编程语言,它以简洁、高效、并发和可靠著称。学习Golang主要包括以下几个步骤。 1. 学习基本语法:首先,要理解Golang的基本语法,包括变量、函数、数据类型、运算符、流程控制等。可以通过在线教程、书籍或官方文档来学习。 2. 熟悉标准库:Golang提供了丰富的标准库,包括各种常用功能,如字符串处理、文件操作、网络编程等。熟悉标准库可以帮助我们更快地编写高效的代码。 3. 学习并发编程:Golang天生支持并发编程,通过goroutine和channel可以实现高效的并发操作。学习并发编程可以帮助解决高并发场景下的问题,提升程序性能。 4. 掌握常用框架和工具:Golang有许多优秀的框架和工具,如Gin、Beego、Echo等。这些框架和工具可以帮助提高开发效率,快速搭建应用。 5. 实践项目:通过实践项目来巩固所学知识,可以选择一些小型的项目开始,逐渐挑战更复杂的项目。通过实践不断提升编程技能和解决问题的能力。 6. 加强算法和数据结构的学习:算法和数据结构是编程的基础,深入学习并理解常见的算法和数据结构是提升编程能力的重要一步。 总结起来,学习Golang的顺序应该是先学习基本语法,然后掌握标准库,学习并发编程,掌握常用框架和工具,实践项目,最后加强算法和数据结构的学习。此外,持续积累编程经验和不断学习新技术也是非常重要的。 ### 回答2: 学习Golang的顺序可以分为四个主要步骤。 1. 学习基本语法和数据类型 首先,要学习Golang的基本语法和数据类型。了解如何声明变量、使用条件语句、循环和函数等基本概念。掌握Golang中的数据类型包括整数、浮点数、字符、字符串、数组、切片、映射、结构体等。理解它们的特点和用法是学习Golang的基础。 2. 掌握面向对象编程 Golang支持面向对象编程,要学习如何使用结构体和方法定义和组织代码。学习如何创建结构体类型和使用方法来处理结构体实例。了解封装、继承和多态的概念,并学会如何在Golang中实现它们。 3. 学习并发编程 Golang是为并发编程而生的语言,学习并发编程是使用Golang的重要一步。了解Golang的并发原语,例如goroutine和通道。掌握如何创建和管理goroutine,并学会使用通道进行不同goroutine之间的通信和同步。理解并发编程的基本概念和常见问题,例如竞态条件和死锁,并学习如何解决它们。 4. 深入学习标准库和其它常用包 最后,要深入学习Golang的标准库和其它常用包。Golang拥有丰富的标准库,涵盖了许多领域,包括网络编程、文件处理、数据库操作等。学习如何使用标准库中提供的功能来解决实际问题。此外,还可以学习一些常用的第三方库,以扩展Golang的功能和效率。 总结起来,学习Golang的顺序是:基本语法和数据类型 -> 面向对象编程 -> 并发编程 -> 标准库和其它常用包。通过有序的学习,可以逐步掌握Golang的核心概念和技术,为实际项目的开发做好准备。 ### 回答3: 学习Golang的顺序可以分为以下几个步骤: 1. 学习基本语法:首先要了解Golang的基本语法规则,包括变量、常量、数据类型、函数、控制流等。可以通过阅读官方文档、在线教程或者参考书籍来学习。 2. 理解并发编程:Golang是以并发为核心设计的编程语言,支持轻量级的线程(Goroutine)和通信(Channel)机制。学习并发编程的原理和技巧,包括Goroutine的创建和调度、Channel的使用、互斥锁和条件变量等。 3. 掌握标准库:Golang提供了丰富的标准库,涵盖了文件处理、网络编程、数据解析、加密解密等各个方面。学习并熟悉标准库的使用,可以提高开发效率和代码质量。 4. 学习常用框架和工具:Golang有许多优秀的第三方框架和工具可供使用,如Web开发框架Gin、数据库ORM框架GORM、测试框架Testify等。选择合适的框架和工具,可以提高开发效率和性能。 5. 实践项目:通过实际项目的练习,将所学知识应用到实际中,加深理解和巩固技能。可以选择一些简单但实用的项目,逐渐挑战更复杂的需求。 6. 阅读优秀的开源代码:阅读和理解一些优秀的开源项目代码,可以提高自己的编程水平和技术见识。可以选择一些知名的开源项目,如Docker、Kubernetes等。 总之,学习Golang需要循序渐进,从基础语法开始,逐步深入学习并发编程、标准库、框架和工具的使用,同时注重实践项目和阅读开源代码。通过不断学习和练习,可以掌握Golang的使用技巧,提升自己的编程能力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值