- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
Go协程为并发编程提供了强大的工具,结合轻量级、高效的特点,为开发者带来了独特的编程体验。本文深入探讨了Go协程的基本原理、同步机制、高级用法及其性能与最佳实践,旨在为读者提供全面、深入的理解和应用指导.
关注公众号【TechLeadCloud】,分享互联网架构、云服务技术的全维度知识。作者拥有10+年互联网服务架构、AI产品研发经验、团队管理经验,同济本复旦硕,复旦机器人智能实验室成员,阿里云认证的资深架构师,项目管理专业人士,上亿营收AI产品研发负责人.
Go协程(goroutine)是Go语言中的并发执行单元,它比传统的线程轻量得多,并且是Go语言并发模型中的核心组成部分。在Go中,你可以同时运行成千上万的goroutine,而不用担心常规操作系统线程带来的开销.
Go协程是与其他函数或方法并行运行的函数或方法。你可以认为它类似于轻量级的线程。其主要优势在于它的启动和停止开销非常小,相比于传统的线程来说,可以更有效地实现并发.
package main
import (
"fmt"
"time"
)
func sayHello() {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println("Hello!")
}
}
func main() {
go sayHello() // 启动一个Go协程
for i := 0; i < 5; i++ {
time.Sleep(150 * time.Millisecond)
fmt.Println("Hi!")
}
}
输出
Hi!
Hello!
Hi!
Hello!
Hello!
Hi!
Hello!
Hi!
Hello!
处理过程: 在上面的代码中,我们定义了一个 sayHello 函数,它在一个循环中打印“Hello!”五次。在 main 函数中,我们使用 go 关键字启动了 sayHello 作为一个goroutine。此后,我们又在 main 中打印“Hi!”五次。因为 sayHello 是一个goroutine,所以它会与 main 中的循环并行执行。因此,输出中“Hello!”和“Hi!”的打印顺序可能会变化.
示例代码:
package main
import (
"fmt"
"time"
)
func worker(id int, ch chan int) {
for {
fmt.Printf("Worker %d received data: %d\n", id, <-ch)
}
}
func main() {
ch := make(chan int)
for i := 0; i < 3; i++ {
go worker(i, ch) // 启动三个Go协程
}
for i := 0; i < 10; i++ {
ch <- i
time.Sleep(100 * time.Millisecond)
}
}
输出:
Worker 0 received data: 0
Worker 1 received data: 1
Worker 2 received data: 2
Worker 0 received data: 3
...
处理过程: 在这个示例中,我们启动了三个工作goroutine来从同一个通道接收数据。在 main 函数中,我们发送数据到通道。每当通道中有数据时,其中一个工作goroutine会接收并处理它。由于goroutines是并发运行的,所以哪个goroutine接收数据是不确定的.
总的来说,Go协程为开发者提供了一个高效、灵活且安全的并发模型。与此同时,Go的标准库提供了丰富的工具和包,进一步简化了并发程序的开发过程.
在Go中,协程是构建并发程序的基础。创建协程非常简单,并且使用 go 关键字就可以启动。让我们探索一些基本用法和与之相关的示例.
启动一个Go协程只需使用 go 关键字,后跟一个函数调用。这个函数即可以是匿名的,也可以是预定义的.
示例代码:
package main
import (
"fmt"
"time"
)
func printNumbers() {
for i := 1; i <= 5; i++ {
time.Sleep(200 * time.Millisecond)
fmt.Println(i)
}
}
func main() {
go printNumbers() // 启动一个Go协程
time.Sleep(1 * time.Second)
fmt.Println("End of main function")
}
输出:
1
2
3
4
5
End of main function
处理过程: 在这个示例中,我们定义了一个 printNumbers 函数,它会简单地打印数字1到5。在 main 函数中,我们使用 go 关键字启动了这个函数作为一个新的Go协程。主函数与Go协程并行执行。为确保主函数等待Go协程执行完成,我们使主函数休眠了1秒钟.
除了启动预定义的函数,你还可以使用匿名函数直接启动Go协程.
示例代码:
package main
import (
"fmt"
"time"
)
func main() {
go func() {
fmt.Println("This is a goroutine!")
time.Sleep(500 * time.Millisecond)
}()
fmt.Println("This is the main function!")
time.Sleep(1 * time.Second)
}
输出:
This is the main function!
This is a goroutine!
处理过程: 在这个示例中,我们在 main 函数中直接使用了一个匿名函数来创建Go协程。在匿名函数中,我们简单地打印了一条消息并使其休眠了500毫秒。主函数先打印其消息,然后等待1秒来确保Go协程有足够的时间完成执行.
值得注意的是,如果主函数(main)结束,所有的Go协程都会被立即终止,不论它们的执行状态如何.
示例代码:
package main
import (
"fmt"
"time"
)
func main() {
go func() {
time.Sleep(500 * time.Millisecond)
fmt.Println("This will not print!")
}()
}
处理过程: 在上面的代码中,Go协程在打印消息前休眠了500毫秒。但由于主函数在此期间已经结束,所以Go协程也被终止,因此我们不会看到任何输出.
总结,Go协程的基本使用非常简单和直观,但需要注意确保主函数在所有Go协程执行完毕之前不会结束.
在并发编程中,同步是确保多个协程能够有效、安全地共享资源或协同工作的关键。Go提供了几种原语,帮助我们实现这一目标.
通道是Go中用于在协程之间传递数据和同步执行的主要方式。它们提供了一种在一个协程中发送数据,并在另一个协程中接收数据的机制.
示例代码:
package main
import "fmt"
func sendData(ch chan string) {
ch <- "Hello from goroutine!"
}
func main() {
messageChannel := make(chan string)
go sendData(messageChannel) // 启动一个Go协程发送数据
message := <-messageChannel
fmt.Println(message)
}
输出:
Hello from goroutine!
处理过程: 我们创建了一个名为 messageChannel 的通道。然后启动了一个Go协程 sendData ,将字符串 "Hello from goroutine!" 发送到这个通道。在主函数中,我们从通道接收这个消息并打印它.
sync.WaitGroup
sync.WaitGroup 是一个等待一组协程完成的结构。你可以增加一个计数来表示应等待的协程数量,并在每个协程完成时减少计数.
示例代码:
package main
import (
"fmt"
"sync"
"time"
)
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)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("All workers completed.")
}
输出:
Worker 1 starting
Worker 2 starting
Worker 3 starting
Worker 4 starting
Worker 5 starting
Worker 1 done
Worker 2 done
Worker 3 done
Worker 4 done
Worker 5 done
All workers completed.
处理过程: 我们定义了一个名为 worker 的函数,它模拟一个需要一秒钟才能完成的工作任务。在这个函数中,我们使用 defer wg.Done() 来确保在函数退出时减少 WaitGroup 的计数。在 main 函数中,我们启动了5个这样的工作协程,每启动一个,我们就使用 wg.Add(1) 来增加计数。 wg.Wait() 则会阻塞,直到所有工作协程都通知 WaitGroup 它们已完成.
sync.Mutex
) 当多个协程需要访问共享资源时(例如,更新一个共享变量),使用互斥锁可以确保同时只有一个协程能访问资源,防止数据竞态.
示例代码:
package main
import (
"fmt"
"sync"
)
var counter int
var lock sync.Mutex
func increment() {
lock.Lock()
counter++
lock.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}
wg.Wait()
fmt.Println("Final Counter:", counter)
}
输出:
Final Counter: 1000
处理过程: 我们有一个全局变量 counter ,我们希望在多个Go协程中并发地增加它。为了确保每次只有一个Go协程能够更新 counter ,我们使用了互斥锁 lock 来同步访问.
这些是Go协程同步机制的一些基本方法。正确地使用它们可以帮助你编写更安全、更高效的并发程序.
Go协程的高级用法涉及更复杂的并发模式、错误处理和协程控制。我们将探索一些常见的高级用法和它们的具体应用示例.
select
) select 语句是Go中处理多个通道的方法。它允许你等待多个通道操作,执行其中一个可以进行的操作.
示例代码:
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "Data from channel 1"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "Data from channel 2"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}
}
输出:
Data from channel 1
Data from channel 2
处理过程: 我们创建了两个通道 ch1 和 ch2 。两个Go协程分别向这两个通道发送数据,但它们的休眠时间不同。在 select 语句中,我们等待两个通道中的任何一个准备好数据,然后进行处理。由于 ch1 的数据先到达,因此它的消息首先被打印.
使用 select ,我们可以轻松实现对通道操作的超时处理.
示例代码:
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
go func() {
time.Sleep(3 * time.Second)
ch <- "Data from goroutine"
}()
select {
case data := <-ch:
fmt.Println(data)
case <-time.After(2 * time.Second):
fmt.Println("Timeout after 2 seconds")
}
}
输出:
Timeout after 2 seconds
处理过程: Go协程会休眠3秒钟后再向 ch 发送数据。在 select 语句中,我们等待这个通道的数据或2秒的超时。由于Go协程在超时之前没有发送数据,因此超时的消息被打印.
context
进行协程控制 context 包允许我们共享跨多个协程的取消信号、超时和其他设置.
示例代码:
package main
import (
"context"
"fmt"
"time"
)
func work(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("Received cancel signal, stopping the work")
return
default:
fmt.Println("Still working...")
time.Sleep(1 * time.Second)
}
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
go work(ctx)
time.Sleep(5 * time.Second)
}
输出:
Still working...
Still working...
Still working...
Received cancel signal, stopping the work
处理过程: 在这个示例中,我们创建了一个带有3秒超时的 context 。Go协程 work 会持续工作,直到接收到取消信号或超时。经过3秒后, context 的超时被触发,Go协程接收到了取消信号并停止工作.
这些高级用法为Go协程提供了强大的功能,使得复杂的并发模式和控制成为可能。掌握这些高级技巧可以帮助你编写更健壮、更高效的Go并发程序.
Go协程为并发编程提供了轻量级的解决方案。但为了充分利用其性能优势并避免常见的陷阱,了解一些最佳实践和性能考虑因素是很有必要的.
虽然Go协程是轻量级的,但无节制地创建大量的Go协程可能会导致内存耗尽或调度开销增大.
示例代码:
package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d started\n", id)
}
func main() {
var wg sync.WaitGroup
numWorkers := 1000
for i := 1; i <= numWorkers; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("All workers done")
}
输出:
Worker 1 started
Worker 2 started
...
Worker 1000 started
All workers done
处理过程: 这个示例创建了1000个工作Go协程。尽管这个数字可能不会导致问题,但如果不加限制地创建更多的Go协程,可能会导致问题.
多个Go协程可能会同时访问共享资源,导致不确定的结果。使用互斥锁(Mutex)或其他同步机制来确保数据的一致性.
示例代码:
package main
import (
"fmt"
"sync"
)
var (
counter int
mu sync.Mutex
)
func increment(wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
counter++
mu.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go increment(&wg)
}
wg.Wait()
fmt.Println("Final counter value:", counter)
}
输出:
Final counter value: 1000
处理过程: 我们使用 sync.Mutex 确保在增加计数器时的互斥访问。这确保了并发访问时的数据一致性.
工作池模式是创建固定数量的Go协程来执行任务的方法,避免过度创建Go协程。任务通过通道发送.
示例代码:
package main
import (
"fmt"
"sync"
)
func worker(tasks <-chan int, wg *sync.WaitGroup) {
defer wg.Done()
for task := range tasks {
fmt.Printf("Worker processed task %d\n", task)
}
}
func main() {
var wg sync.WaitGroup
tasks := make(chan int, 100)
// Start 5 workers.
for i := 0; i < 5; i++ {
wg.Add(1)
go worker(tasks, &wg)
}
// Send 100 tasks.
for i := 1; i <= 100; i++ {
tasks <- i
}
close(tasks)
wg.Wait()
}
输出:
Worker processed task 1
Worker processed task 2
...
Worker processed task 100
处理过程: 我们创建了5个工作Go协程,它们从 tasks 通道中接收任务。这种模式可以控制并发数并重复使用Go协程.
遵循这些最佳实践不仅可以使你的Go协程代码更加健壮,而且还可以更有效地利用系统资源,提高程序的整体性能.
随着计算技术的进步,并发和并行成为了现代软件开发中的关键元素。Go语言作为一个现代编程语言,通过其内置的 goroutine 为开发者提供了一种简洁而强大的并发编程模式。但正如我们在前面的章节中所看到的,理解其工作原理、同步机制、高级用法及性能与最佳实践是至关重要的.
从本文中,我们不仅了解了Go协程的基础知识和工作原理,还探讨了一些关于如何最大限度地发挥其性能的高级主题。关键的洞察包括:
channel
机制中,这也是避免许多并发问题的关键。 最后,虽然Go提供了强大的工具和机制来处理并发,但真正的艺术在于如何正确地使用它们。正如我们在软件工程中经常看到的那样,工具只是手段,真正的力量在于了解它们的工作原理并正确地应用它们.
希望本文为您提供了关于Go协程的深入、全面的认识,并为您的并发编程之旅提供了有价值的洞见和指导。正如在云服务、互联网服务架构和其他复杂的系统中经常可以看到的那样,真正掌握并发是提高性能、扩展性和响应速度的关键.
关注公众号【TechLeadCloud】,分享互联网架构、云服务技术的全维度知识。作者拥有10+年互联网服务架构、AI产品研发经验、团队管理经验,同济本复旦硕,复旦机器人智能实验室成员,阿里云认证的资深架构师,项目管理专业人士,上亿营收AI产品研发负责人.
如有帮助,请多关注 个人微信公众号:【TechLeadCloud】分享AI与云服务研发的全维度知识,谈谈我作为TechLead对技术的独特洞察。 TeahLead KrisChang,10+年的互联网和人工智能从业经验,10年+技术和业务团队管理经验,同济软件工程本科,复旦工程管理硕士,阿里云认证云服务资深架构师,上亿营收AI产品业务负责人.
最后此篇关于Go协程揭秘:轻量、并发与性能的完美结合的文章就讲到这里了,如果你想了解更多关于Go协程揭秘:轻量、并发与性能的完美结合的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
前言 Vue和Vite之父尤雨溪宣布成立公司 VoidZero,目前已经融资3200万。这篇文章欧阳将带你了解VoidZero是如何改变javascript的世界! 关注公众号:【前端欧阳】,给自
前言 TimerQueue 是.NET中实现定时任务的核心组件,它是一个定时任务的管理器,负责存储和调度定时任务。它被用于实现很多 .NET 中的定时任务,比如 System.Threadin
网站服务器这类问题我相信很多朋友讲过,但为什么他和网站优化还有关系呢?那么请您读读我写的没有文笔的文章,看看是否有所认同。 是的,服务器其实也是一个优化网站的最大杀手也是最大帮手,现今万网与阿里云
这个问题在这里已经有了答案: How does XPath deal with XML namespaces? (2 个回答) 5年前关闭。 我有这个 XML
在我们工作中无数次点击鼠标的时候,你有思考过鼠标是怎么工作的吗?在购买鼠标的时候你是怎么挑选的呢?看着那些标称的数据,你是否茫然过?那么如果小编现在说,其实每个鼠标都是一台优秀的“照相机”,你会相信
在 this question海报询问如何在一行中执行以下操作: sub my_sub { my $ref_array = shift; my @array = @$ref_array
我正在尝试了解 Javascript Lambda 方法,但我仍然对非常灵活的定义方式和变量范围感到非常困惑。 例如我正在研究以下插件的代码,除了代码中还有其他部分我不太清楚,我感兴趣的是代码是如何组
谁能帮我揭开以下表达式的神秘面纱: ++[[]][+[]]+[+[]] 我的理解从左到右: ++[[]]:不确定这将评估什么以及如何评估。 [+[]]:+[] 将首先执行,一元运算符将尝试将 [] 转
在 python 中,可以在多个进程之间共享 ctypes 对象。但是我注意到分配这些对象似乎非常昂贵。 考虑以下代码: from multiprocessing import sharedctype
我目前对 Glassfish 3.1.2.2 处理 EJB 的方式感到困惑。 我有一个 OSGi 项目,它由许多 OSGi 包(jar)组成。此外,还有一些 WAR,包括 Tapestry Web 应
这些天我在玩线程库并尝试实现一些功能。其中一个教程说要运行程序使用: gcc -lpthread -lrt -lc -lm project1.c scheduler.c -o out 首先我需要深入了
如 app.secret_key未设置,Flask 将不允许您设置或访问 session 字典。 这就是flask user guide has to say在这个问题上。 我对 Web 开发很陌生,
Ruby on Rails 新手问题... 考虑以下代码(在 View 中): | | 在 ERB 标签中调用的一些方法对我来说就像魔法一样,我正试图揭开它们的神秘面纱。如果我不了解代码
HTTPS 是建立在 SSL/TLS 传输层安全协议之上的一种 HTTP 协议,相当于 HTTPS = HTTP + SSL/TLS。第一篇文章 “HTTPS - 通俗易懂的阐述 HTTPS 协
本周新 Xcode 3.2.1 中包含的自述文件内容如下: 静态代码分析通过“构建”菜单下的“构建和分析”选项或通过自定义build设置完全集成在 Xcode IDE 中 GCC 4.2 是 10.6
是的,我知道。关于 NSOperation 世界有很多问题和答案,但我仍然有一些疑问。我会尝试用两部分的问题来解释我的疑虑。它们相互关联。 在 SO 帖子中 nsoperationqueue-and-
运行后,我看到我的应用程序在 TaskMgr 中占用了 3.5Gb 我在 Windbg 中看到的内容有点令人困惑: 0:022> !address -summary ProcessParametrs
我是一名优秀的程序员,十分优秀!