Go 并发模型—Goroutines

前言

Goroutines 是 Go 语言主要的并发原语。它看起来非常像线程,但是相比于线程它的创建和管理成本很低。Go 在运行时将 goroutine 有效地调度到真实的线程上,以避免浪费资源,因此您可以轻松地创建大量的 goroutine(例如每个请求一个 goroutine),并且您可以编写简单的,命令式的阻塞代码。因此,Go 的网络代码往往比其它语言中的等效代码更直接,更容易理解(这点从下文中的示例代码可以看出)。

对我来说,goroutine 是将 Go 这门语言与其它语言区分开来的一个主要特征。这就是为什么大家更喜欢用 Go 来编写需要并发的代码。在下面讨论更多关于 goroutine 之前,我们先了解一些历史,这样你就能理解为什么你想要它们了。

基于 fork 和线程

fork_thread.jpeg

高性能服务器需要同时处理来自多个客户端的请求。有很多方法可以设计一个服务端架构来处理这个问题。最容易想到的就是让一个主进程在循环中调用 accept,然后调用 fork 来创建一个处理请求的子进程。这篇 Beej’s Guide to Network Programming 指南中提到了这种方式。

在网络编程中,fork 是一个很好的模式,因为你可以专注于网络而不是服务器架构。但是它很难按照这种模式编写出一个高效的服务器,现在应该没有人在实践中使用这种方式了。

fork 同时也存在很多问题,首先第一个是成本: Linux 上的 fork 调用看起来很快,但它会将你所有的内存标记为 copy-on-write。每次写入 copy-on-write 页面都会导致一个小的页面错误,这是一个很难测量的小延迟,进程之间的上下文切换也很昂贵。

另一个问题是规模: 很难在大量子进程中协调共享资源(如 CPU、内存、数据库连接等)的使用。如果流量激增,并且创建了太多进程,那么它们将相互争夺 CPU。但是如果限制创建的进程数量,那么在 CPU 空闲时,大量缓慢的客户端可能会阻塞每个人的正常使用,这时使用超时机制会有所帮助(无论服务器架构如何,超时设置都是很必要的)。

通过使用线程而不是进程,上面这些问题在一定程度上能得到缓解。创建线程比创建进程更“便宜”,因为它共享内存和大多数其它资源。在共享地址空间中,线程之间的通信也相对容易,使用信号量和其它结构来管理共享资源,然而,线程仍然有很大的成本,如果你为每个连接创建一个新线程,你会遇到扩展问题。与进程一样,你此时需要限制正在运行的线程的数量,以避免严重的 CPU 争用,并且需要使慢速请求超时。创建一个新线程仍然需要时间,尽管可以通过使用线程池在请求之间回收线程来缓解这一问题。

无论你是使用进程还是线程,你仍然有一个难以回答的问题: **你应该创建多少个线程?**如果您允许无限数量的线程,客户端可能会用完所有的内存和 CPU,而流量

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在 Go 语言中,你可以使用 goroutine 和 channel 来实现并发模型goroutine 是 Go 语言中用于实现并发的轻量级线程。它是通过关键字 `go` 启动的,比线程更加轻量。 channel 是 Go 语言中的一种数据结构,用于在 goroutine 之间传递数据。通过 channel,一个 goroutine 可以将数据发送给另一个 goroutine。 下面是一个简单的例子,展示了如何使用 goroutine 和 channel 在 Go 语言中实现并发模型: ``` package main import ( "fmt" ) func printNumbers(c chan int) { for i := 0; i < 10; i++ { c <- i } close(c) } func main() { c := make(chan int) go printNumbers(c) for number := range c { fmt.Println(number) } } ``` 在这个例子中,我们有两个 goroutine:`main` 和 `printNumbers`。`main` goroutine 启动了一个新的 `printNumbers` goroutine,并创建了一个 channel,用于在两个 goroutine 之间传递数据。`printNumbers` goroutine 循环打印 0 到 9,并将这些数字通过 channel 传递给 `main` goroutine。最后,`main` goroutine 使用 `range` 语句来遍历 channel 中的数据,并将它们打印出来。 ### 回答2: 使用Go语言编写并发模型的一个示例是使用goroutine和channel。 ```go package main import ( "fmt" "sync" ) func main() { // 创建一个无缓冲的channel ch := make(chan int) // 使用sync.WaitGroup来等待所有goroutine完成 var wg sync.WaitGroup // 启动5个goroutine来处理任务 for i := 0; i < 5; i++ { wg.Add(1) // 增加等待组的计数 go func(id int) { defer wg.Done() // 减少等待组的计数 // 将任务结果发送到channel ch <- id * 2 }(i) } // 等待所有goroutine完成 wg.Wait() // 关闭channel,防止死锁 close(ch) // 从channel中读取所有结果并打印 for result := range ch { fmt.Println(result) } } ``` 该示例中,我们使用`make(chan int)`来创建一个无缓冲的channel,用于在不同的goroutine之间传递任务结果。然后使用`sync.WaitGroup`来等待所有的goroutine完成。`wg.Add(1)`和`wg.Done()`分别用于增加和减少等待组的计数。 在`for`循环中,我们使用`go func(id int) { ... }(i)`启动了5个goroutine来处理任务。每个goroutine都会计算`id`的两倍,并将结果发送到channel中。在之后的循环中,我们使用`range ch`来从channel中读取所有的结果,并打印出来。 在最后,我们关闭了channel,以防止死锁发生。这是因为在循环中使用`range`来读取channel时,当channel被关闭时,循环会自动退出。 通过使用goroutine和channel,我们可以实现并发处理任务的模型,从而提高程序的性能和效率。 ### 回答3: 使用Go语言编写并发模型可以使用Go内置的并发原语,例如goroutine和channel。以下是一个简单的示例: ```go package main import ( "fmt" ) func main() { // 创建一个无缓冲的channel ch := make(chan string) // 启动一个并发goroutine go printHello(ch) // 在主goroutine中接收从channel中传递过来的消息 msg := <-ch fmt.Println(msg) } func printHello(ch chan string) { // 在该函数内部,使用channel将消息发送给主goroutine ch <- "Hello, world!" } ``` 在这个示例中,我们使用`make`函数创建了一个无缓冲的channel。然后,我们启动了一个并发goroutine,通过channel向主goroutine发送消息。在主goroutine中,我们使用`<-`操作符接收从channel传递过来的消息,并将其打印输出。 在这个简单的示例中,我们展示了如何使用goroutine和channel实现并发模型。由于Go语言的并发原语设计简单且易于使用,因此可以方便地实现各种并发模型

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值