Go 语言入门很简单 -- 14. Go 并发初识 #私藏项目实操分享#

简介: Go 语言入门很简单 -- 14. Go 并发初识 #私藏项目实操分享#

大型程序通常由许多较小的子程序组成。  例如,Web 服务器处理来自 Web 浏览器的请求并提供 HTML 网页作为响应。  每个请求都像一个小程序一样被处理。对于像这样的程序,最理想的是能够 在同一时间运行它们的小型组件(在  网络服务器的情况下,处理多个请求)。同时在一个以上的任务上取得进展 被称为并发性。

image.png

线程

线程是操作系统为您提供的一项功能,可让您并行运行程序的各个部分。 假设您的程序由两个主要部分组成,第 1 部分和第 2 部分,并且您编写的代码使得第 1 部分在 线程一 上运行,而第 2 部分在 线程二 上运行。 在这种情况下,程序的两个部分将同时并行运行; 下图说明了它的外观:

image.png

现代软件中真正独立的线程数量与程序需要执行的并发软件数量之间存在差距。 在现代软件中,您可能需要数千个程序同时独立运行,即使您的操作系统可能只提供四个线程!

什么叫并发

并发指在同一时间内可以执行多个任务。并发编程含义比较广泛,包含多线程编程、多进程编程及分布式程序等。

在 Go 中,并发意味着您的程序能够将自身切割成更小的部分,然后能够在不同时间运行不同的独立部分,目标是根据可用资源的数量尽快执行所有任务。

Go 中使用 goroutineschannel 来支持并发。

Goroutines

一个 goroutine 是一个能够与其他函数同时运行的函数。与其他函数同时运行。要创建一个 goroutine 我们使用关键字 go,后面跟着一个函数调用:

package main
import "fmt"
func f(n int) {
    for i := 0; i < 10; i++ {
        fmt.Println(n, ":", i)
    }
}
func main() {
    go f(0)
    var input string
    fmt.Scanln(&input)
}

该程序由两个 goroutine 组成。 第一个 goroutine 是隐式的,是主函数本身。 当我们调用 go f(0) 时会创建第二个 goroutine。 通常,当我们调用一个函数时,我们的程序会执行函数中的所有语句,然后返回到调用后的下一行。 使用  goroutine,我们立即返回到下一行,而不是等待函数完成。 这就是调用 Scanln 函数的原因;  没有它,程序将在有机会打印所有数字之前退出。

Goroutines 是轻量级的,我们可以轻松地创建数以千计的 Goroutines。 我们可以通过这样做来修改我们的程序,以运行 10 个 goroutines:

func main() {
    for i := 0; i < 10; i++ {
        go f(i)
    }
    var input string
    fmt.Scanln(&input)
}

你可能已经注意到,当你运行这个程序时,它似乎是按顺序而不是同时运行 goroutine。 让我们使用 time.Sleeprand.Intn 为函数添加一些延迟:

package main
import (
    "fmt"
    "math/rand"
    "time"
)
func f(n int) {
    for i := 0; i < 10; i++ {
        fmt.Println(n, ":", i)
        amt := time.Duration(rand.Intn(250))
        time.Sleep(time.Millisecond * amt)
    }
}
func main() {
    for i := 0; i < 10; i++ {
        go f(i)
    }
    var input string
    fmt.Scanln(&input)
}

f 打印出从0到10的数字,在每个数字之后等待 0到250毫秒之间。这些程序现在应该同时运行。

Channels

通道为两个 goroutine 提供了一种通信方式,并使它们的执行同步。下面是一个使用通道的示例程序:

该程序将永远打印 “ping”(按回车键停止)。 通道类型用关键字 chan 表示,后跟通道上传递的事物的类型(在这种情况下,我们传递的是字符串)。 <-(左箭头)运算符用于在通道中发送和接收消息。

package main
import (
    "fmt"
    "time"
)
func pinger(c chan string) {
    for i := 0; ; i++ {
        c <- "ping"
    }
}
func printer(c chan string) {
    for {
        msg := <-c
        fmt.Println(msg)
        time.Sleep(time.Second * 1)
    }
}
func main() {
    var c chan string = make(chan string)
    go pinger(c)
    go printer(c)
    var input string
    fmt.Scanln(&input)
}

c <- "ping" 表示着发送 “ping”。

msg := <- c 表示接收一个消息并把这个消息保存到 msg 中。

fmt 一行中也可以被这样写: fmt.Println(<-c),这样的话 msg := <- c 就可以删掉了。

使用这样的通道可以同步两个 goroutine。 当 pinger 尝试在通道上发送消息时,它将等待 printer 准备好接收消息。 (这被称为阻塞)让我们向程序添加另一个发送者,看看会发生什么。 添加这个功能:

package main
import (
    "fmt"
    "time"
)
func pinger(c chan string) {
    for i := 0; ; i++ {
        c <- "ping"
    }
}
func ponger(c chan string) {
    for i := 0; ; i++ {
        c <- "pong"
    }
}
func printer(c chan string) {
    for {
        msg := <-c
        fmt.Println(msg)
        time.Sleep(time.Second * 1)
    }
}
func main() {
    var c chan string = make(chan string)
    go pinger(c)
    go ponger(c)
    go printer(c)
    var input string
    fmt.Scanln(&input)
}

该程序现在将轮流打印“ping”和“pong”。

通道方向

我们可以在通道类型上指定一个方向,从而将其限制为发送或接收。 例如 pinger 的函数签名可以改成这样:

func pinger(c chan<- string)

此时,c 只能发送,如果尝试从 c 接收的话会导致编译出错。同样的我们可以更改 printer :

func printer(c <-chan string)

没有这些限制的通道称为双向通道。 可以将双向通道传递给采用仅发送或仅接收通道的函数,但反之则不然。

Select

Go 有一个名为 select 的特殊语句,它的工作方式类似于 switch ,但只适用于通道:

package main
import (
    "fmt"
    "time"
)
func main() {
    c1 := make(chan string)
    c2 := make(chan string)
    go func() {
        for {
            c1 <- "from c1 "
            time.Sleep(2 * time.Second)
        }
    }()
    go func() {
        for {
            c2 <- "from c2 "
            time.Sleep(3 * time.Second)
        }
    }()
    go func() {
        for {
            select {
            case msg1 := <-c1:
                fmt.Println(msg1)
            case msg2 := <-c2:
                fmt.Println(msg2)
            }
        }
    }()
    var input string
    fmt.Scanln(&input)
}

该程序每 2 秒打印一次“from c1”,每 3 秒打印一次“from c2”。 select 选择第一个准备好的通道并从它接收(或发送到它)。 如果多个通道准备就绪,则它会随机选择要接收的通道。 如果没有通道准备好,则语句阻塞,直到一个通道可用。

$ go run main.go
from c2
from c1
from c1
from c2
from c1
from c2
from c1
from c1
from c2
from c1
from c2
from c1
from c1
from c2

select 语句通常用于实现超时:

select {
case msg1 := <- c1:
    fmt.Println("Message 1", msg1)
case msg2 := <- c2:
    fmt.Println("Message 2", msg2)
case <- time.After(time.Second):
    fmt.Println("timeout")
}

time.After 创建一个通道,在给定的持续时间之后将在其上发送当前时间。 (我们对时间不感兴趣,所以我们没有将它存储在变量中)我们还可以指定一个默认情况:

select {
case msg1 := <- c1:
    fmt.Println("Message 1", msg1)
case msg2 := <- c2:
    fmt.Println("Message 2", msg2)
case <- time.After(time.Second):
    fmt.Println("timeout")
    default:
    fmt.Println("nothing ready")
}

默认情况下,如果没有任何一个通道准备好。

缓冲通道

也可以在创建通道时将第二个参数传递给 make 函数:

c := make(chan int, 1)

这将创建一个容量为 1 的缓冲通道。通常通道是同步的; 通道的双方将等待,直到另一方准备就绪。

缓冲通道是异步的; 除非通道已满,否则发送或接收消息不会等待。

 

相关文章
|
9天前
|
算法 Java Go
【GoGin】(1)上手Go Gin 基于Go语言开发的Web框架,本文介绍了各种路由的配置信息;包含各场景下请求参数的基本传入接收
gin 框架中采用的路优酷是基于httprouter做的是一个高性能的 HTTP 请求路由器,适用于 Go 语言。它的设计目标是提供高效的路由匹配和低内存占用,特别适合需要高性能和简单路由的应用场景。
92 4
|
24天前
|
Linux Go iOS开发
Go语言100个实战案例-进阶与部署篇:使用Go打包生成可执行文件
本文详解Go语言打包与跨平台编译技巧,涵盖`go build`命令、多平台构建、二进制优化及资源嵌入(embed),助你将项目编译为无依赖的独立可执行文件,轻松实现高效分发与部署。
|
9天前
|
存储 安全 Java
【Golang】(4)Go里面的指针如何?函数与方法怎么不一样?带你了解Go不同于其他高级语言的语法
结构体可以存储一组不同类型的数据,是一种符合类型。Go抛弃了类与继承,同时也抛弃了构造方法,刻意弱化了面向对象的功能,Go并非是一个传统OOP的语言,但是Go依旧有着OOP的影子,通过结构体和方法也可以模拟出一个类。
58 1
|
2月前
|
Cloud Native Go API
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
285 0
|
2月前
|
Cloud Native Java Go
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
199 0
|
2月前
|
Cloud Native Java 中间件
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
165 0
|
2月前
|
Cloud Native Java Go
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
252 0
|
8月前
|
存储 缓存 安全
Go 语言中的 Sync.Map 详解:并发安全的 Map 实现
`sync.Map` 是 Go 语言中用于并发安全操作的 Map 实现,适用于读多写少的场景。它通过两个底层 Map(`read` 和 `dirty`)实现读写分离,提供高效的读性能。主要方法包括 `Store`、`Load`、`Delete` 等。在大量写入时性能可能下降,需谨慎选择使用场景。
|
11月前
|
存储 负载均衡 监控
如何利用Go语言的高效性、并发支持、简洁性和跨平台性等优势,通过合理设计架构、实现负载均衡、构建容错机制、建立监控体系、优化数据存储及实施服务治理等步骤,打造稳定可靠的服务架构。
在数字化时代,构建高可靠性服务架构至关重要。本文探讨了如何利用Go语言的高效性、并发支持、简洁性和跨平台性等优势,通过合理设计架构、实现负载均衡、构建容错机制、建立监控体系、优化数据存储及实施服务治理等步骤,打造稳定可靠的服务架构。
246 1
|
11月前
|
Go 调度 开发者
探索Go语言中的并发模式:goroutine与channel
在本文中,我们将深入探讨Go语言中的核心并发特性——goroutine和channel。不同于传统的并发模型,Go语言的并发机制以其简洁性和高效性著称。本文将通过实际代码示例,展示如何利用goroutine实现轻量级的并发执行,以及如何通过channel安全地在goroutine之间传递数据。摘要部分将概述这些概念,并提示读者本文将提供哪些具体的技术洞见。

热门文章

最新文章