GO语言基础教程22——并发

简介: 串行:我们都是先读小学,小学毕业后再读初中,读完初中再读高中。并发:同一时间段内执行多个任务(你在用微信和两个女朋友聊天)。并行:同一时刻执行多个任务(你和你朋友都在用微信和女朋友聊天)。

GO语言基础教程22——并发


GO语言基础教程22——并发


基本概念


串行、并发与并行


串行:我们都是先读小学,小学毕业后再读初中,读完初中再读高中。


并发:同一时间段内执行多个任务(你在用微信和两个女朋友聊天)。


并行:同一时刻执行多个任务(你和你朋友都在用微信和女朋友聊天)。


进程、线程和协程


进程(process):程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。


线程(thread):操作系统基于进程开启的轻量级进程,是操作系统调度执行的最小单位。


协程(coroutine):非操作系统提供而是由用户自行创建和控制的用户态‘线程’,比线程更轻量级。


goroutine


Goroutine 是 Go 程序中最基本的并发执行单元。每一个 Go 程序都至少包含一个 goroutine——main goroutine,当 Go 程序启动时它会自动创建。


在Go语言编程中你不需要去自己写进程、线程、协程,你的技能包里只有一个技能——goroutine,当你需要让某个任务并发执行的时候,你只需要把这个任务包装成一个函数,开启一个 goroutine 去执行这个函数就可以了,就是这么简单粗暴。


go关键字


Go语言中使用 goroutine 非常简单,只需要在函数或方法调用前加上go关键字就可以创建一个 goroutine ,从而让该函数或方法在新创建的 goroutine 中执行。

go toGo()  // 创建一个新的 goroutine 运行函数toGo

匿名函数也支持使用go关键字创建 goroutine 去执行。

go func(){
  // ...
}()


启动单个goroutine

package main
import (
  "fmt"
  "time"
)
func hello() {
  fmt.Println("hello")
}
func main() {
  go hello()
  fmt.Println("你好")
  time.Sleep(time.Second)//不加这句的话会只输出你好
}

在 Go 程序启动时,Go 程序就会为 main 函数创建一个默认的 goroutine 。在上面的代码中我们在 main 函数中使用 go 关键字创建了另外一个 goroutine 去执行 hello 函数,而此时 main goroutine 还在继续往下执行,我们的程序中此时存在两个并发执行的 goroutine。当 main 函数结束时整个程序也就结束了,同时 main goroutine 也结束了,所有由 main goroutine 创建的 goroutine 也会一同退出。也就是说我们的 main 函数退出太快,另外一个 goroutine 中的函数还未执行完程序就退出了,导致未打印出“hello”。


启动多个goroutine

package main
import (
  "fmt"
  "sync"
)
//开启计数器多次执行上面的代码会发现每次终端上打印数字的顺序都不一致。这是因为10个 goroutine 是并发执行的,而 goroutine 的调度是随机的。
## channel
通道(channel)是用来传递数据的一个数据结构。
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 `<-` 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
var wg sync.WaitGroup
func hello(i int) {
  defer wg.Done() // goroutine结束就登记-1
  fmt.Println("hello", i)
}
func main() {
  for i := 0; i < 10; i++ {
    wg.Add(1) // 启动一个goroutine就登记+1
    go hello(i)
  }
  wg.Wait() // 等待所有登记的goroutine都结束
}

多次执行上面的代码会发现每次终端上打印数字的顺序都不一致。这是因为10个 goroutine 是并发执行的,而 goroutine 的调度是随机的。


channel


通道(channel)是用来传递数据的一个数据结构。

var 变量名称 chan 元素类型

通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。


channel零值


未初始化的通道类型变量其默认零值是nil

var ch chan int
fmt.Println(ch) // <nil>


初始化channel


声明的通道类型变量需要使用内置的make函数初始化之后才能使用。具体格式如下:

make(chan 元素类型, [缓冲大小])


channel操作


通道共有发送(send)、接收(receive)和关闭(close)三种操作。而发送和接收操作都使用<-符号。


创建一个通道

ch := make(chan int)

发送

ch <- 100// 把100送到ch中

接收

x := <- ch // 从ch中接收值并赋值给变量x
<-ch       // 从ch中接收值,忽略结果

关闭

close(ch)


无缓冲区通道


无缓冲的通道又称为阻塞的通道。我们来看一下如下代码片段。

func main() {
  ch := make(chan int)
  ch <- 10
  fmt.Println("发送成功")
}

该段代码在执行时会报错,因为我们使用ch := make(chan int)创建的是无缓冲的通道,无缓冲的通道只有在有接收方能够接收值的时候才能发送成功,否则会一直处于等待发送的阶段。


解决办法


开启一个goroutine接收值。

func recv(c chan int) {
  ret := <-c
  fmt.Println("接收成功", ret)
}
func main() {
  ch := make(chan int)
  go recv(ch) // 创建一个 goroutine 从通道接收值
  ch <- 10
  fmt.Println("发送成功")
}

使用无缓冲通道进行通信将导致发送和接收的 goroutine 同步化。因此,无缓冲通道也被称为同步通道


有缓冲区通道


还有另外一种解决上面问题的方法,那就是使用有缓冲区的通道。我们可以在使用 make 函数初始化通道时,可以为其指定通道的容量,例如:

func main() {
  ch := make(chan int, 1) // 创建一个容量为1的有缓冲区通道
  ch <- 10
  fmt.Println("发送成功")
}

只要通道的容量大于零,那么该通道就属于有缓冲的通道,通道的容量表示通道中最大能存放的元素数量。当通道内已有元素数达到最大容量后,再向通道执行发送操作就会阻塞,除非有从通道执行接收操作。就像你小区的快递柜只有那么个多格子,格子满了就装不下了,就阻塞了,等到别人取走一个快递员就能往里面放一个。


遍历通道


通常我们会选择使用for range循环从通道中接收值,当通道被关闭后,会在通道内的所有值被接收完毕后会自动退出循环。上面那个示例我们使用for range改写后会很简洁。

func f3(ch chan int) {
  for v := range ch {
    fmt.Println(v)
  }
}


单向通道

<- chan int // 只接收通道,只能接收不能发送
chan <- int // 只发送通道,只能发送不能接收


相关文章
|
1天前
|
存储 监控 算法
内网监控系统之 Go 语言布隆过滤器算法深度剖析
在数字化时代,内网监控系统对企业和组织的信息安全至关重要。布隆过滤器(Bloom Filter)作为一种高效的数据结构,能够快速判断元素是否存在于集合中,适用于内网监控中的恶意IP和违规域名筛选。本文介绍其原理、优势及Go语言实现,提升系统性能与响应速度,保障信息安全。
17 5
|
11天前
|
算法 安全 Go
Go语言中的加密和解密是如何实现的?
Go语言通过标准库中的`crypto`包提供丰富的加密和解密功能,包括对称加密(如AES)、非对称加密(如RSA、ECDSA)及散列函数(如SHA256)。`encoding/base64`包则用于Base64编码与解码。开发者可根据需求选择合适的算法和密钥,使用这些包进行加密操作。示例代码展示了如何使用`crypto/aes`包实现对称加密。加密和解密操作涉及敏感数据处理,需格外注意安全性。
33 14
|
11天前
|
Go 数据库
Go语言中的包(package)是如何组织的?
在Go语言中,包是代码组织和管理的基本单元,用于集合相关函数、类型和变量,便于复用和维护。包通过目录结构、文件命名、初始化函数(`init`)及导出规则来管理命名空间和依赖关系。合理的包组织能提高代码的可读性、可维护性和可复用性,减少耦合度。例如,`stringutils`包提供字符串处理函数,主程序导入使用这些函数,使代码结构清晰易懂。
50 11
|
11天前
|
存储 安全 Go
Go语言中的map数据结构是如何实现的?
Go 语言中的 `map` 是基于哈希表实现的键值对数据结构,支持快速查找、插入和删除操作。其原理涉及哈希函数、桶(Bucket)、动态扩容和哈希冲突处理等关键机制,平均时间复杂度为 O(1)。为了确保线程安全,Go 提供了 `sync.Map` 类型,通过分段锁实现并发访问的安全性。示例代码展示了如何使用自定义结构体和切片模拟 `map` 功能,以及如何使用 `sync.Map` 进行线程安全的操作。
|
15天前
|
监控 安全 算法
深度剖析核心科技:Go 语言赋能局域网管理监控软件进阶之旅
在局域网管理监控中,跳表作为一种高效的数据结构,能显著提升流量索引和查询效率。基于Go语言的跳表实现,通过随机化索引层生成、插入和搜索功能,在高并发场景下展现卓越性能。跳表将查询时间复杂度优化至O(log n),助力实时监控异常流量,保障网络安全与稳定。示例代码展示了其在实际应用中的精妙之处。
37 9
|
25天前
|
算法 安全 Go
Go 语言中实现 RSA 加解密、签名验证算法
随着互联网的发展,安全需求日益增长。非对称加密算法RSA成为密码学中的重要代表。本文介绍如何使用Go语言和[forgoer/openssl](https://github.com/forgoer/openssl)库简化RSA加解密操作,包括秘钥生成、加解密及签名验证。该库还支持AES、DES等常用算法,安装简便,代码示例清晰易懂。
58 12
|
28天前
|
监控 算法 安全
解锁企业计算机监控的关键:基于 Go 语言的精准洞察算法
企业计算机监控在数字化浪潮下至关重要,旨在保障信息资产安全与高效运营。利用Go语言的并发编程和系统交互能力,通过进程监控、网络行为分析及应用程序使用记录等手段,实时掌握计算机运行状态。具体实现包括获取进程信息、解析网络数据包、记录应用使用时长等,确保企业信息安全合规,提升工作效率。本文转载自:[VIPShare](https://www.vipshare.com)。
32 1
|
1月前
|
开发框架 Go 计算机视觉
纯Go语言开发人脸检测、瞳孔/眼睛定位与面部特征检测插件-助力GoFly快速开发框架
开发纯go插件的原因是因为目前 Go 生态系统中几乎所有现有的人脸检测解决方案都是纯粹绑定到一些 C/C++ 库,如 OpenCV 或 dlib,但通过 cgo 调用 C 程序会引入巨大的延迟,并在性能方面产生显著的权衡。此外,在许多情况下,在各种平台上安装 OpenCV 是很麻烦的。使用纯Go开发的插件不仅在开发时方便,在项目部署和项目维护也能省很多时间精力。
|
2月前
|
Go 数据安全/隐私保护 开发者
Go语言开发
【10月更文挑战第26天】Go语言开发
50 3
|
2月前
|
Java 程序员 Go
Go语言的开发
【10月更文挑战第25天】Go语言的开发
44 3