goroutine 为什么比java中的线程更加轻量?
两种并发机制在资源消耗、调度方式、创建和管理上有明显的差异:
-
堆栈大小:
- Goroutine 的初始堆栈大小相对较小,通常在几 KB(2KB或4KB)左右,而且可以根据需要动态伸缩。这意味着在同样的内存条件下,可以创建更多的 goroutine。
- Java 线程的堆栈大小通常较大,可能从几百 KB 到几 MB 不等,这取决于 JVM 的配置和操作系统。Java 线程的堆栈不会动态伸缩,因此每个线程都会占用更多的内存资源。
-
调度:
- Goroutine 是由 Go 运行时(runtime)管理的,使用的是 M:N 调度模型(多个 goroutine 可以映射到多个或单个操作系统线程)。Go 运行时包含自己的调度器,它可以在用户态进行调度,避免了内核态与用户态之间的频繁切换,这使得创建和切换 goroutine 的成本比线程低得多。
- Java 线程通常是直接映射到操作系统原生线程(1:1 模型),由操作系统内核进行调度。这意味着线程的创建和切换需要进行用户态和内核态之间的切换,这比用户态的调度更耗时。
-
创建和管理:
- Goroutine 的创建和管理是由 Go 语言的运行时直接处理的,这使得它们的创建和销毁非常快速和高效。
- Java 线程 的创建和管理则是由 JVM 和操作系统共同处理的,这通常比 goroutine 更复杂,因此也更耗费资源。
-
上下文切换:
- Goroutine 之间的上下文切换通常比 Java 线程之间的上下文切换要快,因为它们需要保存和恢复的状态更少。
1、创建goroutine:
|
2、goroutine中发生了panic怎么办:
(1)使用recover函数来恢复程序的执行。
(2)recover函数只有在defer函数中调用才能起作用,它会捕获当前goroutine中的panic,并返回panic所传递的值。
注意:recover函数只能在同一个goroutine中恢复panic,不能跨越多个goroutine。
|
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分支:
|
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 函数可以避免程序终止。
|
请注意,尽管可以使用 recover 来处理异常,但在 Go 中,建议尽可能使用错误值而不是 panic 和 recover,因为错误值通常更容易理解和处理。
panic 和 recover 主要用于处理那些无法预料或难以处理的错误情况。
9、多个goroutine直接怎么保障顺序执行:
可以使用无缓冲的信道来实现同步,可以确保一个goroutine在另一个goroutine完成后才开始执行。
|
在这个示例中,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 退出循环。
|
在上面的代码中,我们创建了一个 stopCh channel,然后在 startWorker 函数中通过一个无限循环来执行 goroutine 的任务逻辑。
在每次循环开始时,使用 select 语句来监听 stopCh channel 的状态。如果收到了关闭信号,就退出循环并结束 goroutine。
在 main 函数中,我们通过 go startWorker(stopCh) 启动了一个 goroutine。然后在等待 5 秒后,向 stopCh channel 发送了一个信号,通知 goroutine 结束循环并退出。