GO语言-10了解Go并发的使用(上)

简介: GO语言的学习与记录,第十篇了解Go语言并发的使用(上),围绕的使用go关键字开启协程,学习协程之间数据传输,了解阻塞式通道和非阻塞式通道,学习如何取通道的值还有怎么遍历通道。最后两个案例助于理解。
初心是记录和总结,自己学习Go语言的历程。如果能帮助到你,这是我的荣幸。

并发是属于比较难的内容,我还不足以把握到其精髓,只能分享一些基本使用和注意事项。

如何开启并发

很简单,只要在方法前使用go关键字,看示例代码。这里使用了匿名函数,匿名函数可以在方法中直接调用执行,通过{}后加上(),也可以传入参数,只需要func(参数)和最后的括号里加入传入(传入参数)对应即可。

package main

import "fmt"

func main() {
   go func() {
      fmt.Println("并发程序")
   }()
   fmt.Println("主程序")
}

程序运行后,会输出主程序,发现并没有打印并发程序。这是因为在Go中,主程序也算是一个并发的主协程,而主协程执行完毕后,会将其他的子协程(通过go关键字开启的)强制结束。这里出现了协程两个字,这是在go中特有的描述并发的,协程就是通过go关键字开启的,英文名叫:goroutine

主协程和子协程如何协调

这个协调是指:主协程能不能等我自己开启的协程结束后才结束。并发本身就是脱离了程序定义的顺序,它并不按照程序语句的顺序执行,而是通过计算机资源调度,和CPU有关系。那么我们如何进行控制呢?

睡眠

睡眠的方式非常简单粗暴,强制该程序‘睡眠’,暂时停止执行。这样CPU就会执行其他协程了。
使用方法:

time.Sleep(d Duration)

Duration是指时间间隔,常用的有

  • time.Hour 一小时
  • time.Minute 一分
  • time.Second 一秒

使用WaitGroup

WaitGroup等待goroutine的集合完成。主程序调用Add来设置等待的gor例行程序的数量。然后每个goroutine运行并在完成时调用Done。同时,Wait可用于阻塞直到所有goroutine完成。

通过这段话我们可以捕获三个点

  • 在主程序中调用WaitGroup的Add方法添加字子协程的数量
  • 每个子协程运行完成时调用Done方法
  • 主程序通过使用Wait方法等待子协程运行结束
// 定义WaitGroup对象
var wg sync.WaitGroup

func main() {
   wg.Add(1) // 添加一个子协程
   go func() {
      fmt.Println("并发程序")
      wg.Done() // 子协程结束后调用Done
   }()
   fmt.Println("主程序")
   wg.Wait() // 等待子协程运行结束,主协程才结束
}

数据传递 - 通道

并发执行时第一个重要的点:数据传递

模拟A协程和B协程,双方进行数据传输,在go语言中,协程数据的传输推荐的是使用通道,这里出现了一个新的数据类型:chan,使用它只需要关心的是:传输什么类型的数据,所以它的定义方法是:
var chan1 int = make(chan int)或者是简易法:chan1 := make(chan int)

通道的传递和接收语法

// 定义了一个传输数值型的通道
chan1 := make(chan int)

// 从通道中接收内容
x := <-chan1

// 往通道中传入一个值:1
chan1 <- 1

阻塞式通道

chan1 := make(chan int)

这种没有指定缓冲区的通道,默认是阻塞式通道。

理解阻塞:通道的使用有发送方和接收方两个角色。

  • 发送方发送数据时,当接收方没有准备好接收数据时,通道对于发送方是阻塞的。
  • 接收方,当通道中数据为空的时候,通道对于接收方是阻塞的。

看见一个非常形象的例子:京东快递送货上门,当没有暂存站点时,它会打电话给你,如果你方便接收,那它会给你送过来,如果你不能接收,则不会给你送过来。

常见错误,阻塞式通道兼任两个角色:

阻塞式非常注重发送方和接收方这两个角色,同个协程 不允许兼任两个身份
func main() {
   chan1 := make(chan int)
   // 接收
   x := <-chan1
   //发送
   chan1 <- 1
   fmt.Println(x)
}

改正,接收方改为新的协程;Ps:这里涉及到资源抢夺的问题,因为并没有使用WaitGroup

func main() {
   chan1 := make(chan int)
   go func() {
      // 接收
      x := <-chan1
      fmt.Println(x)
   }()
   //发送
   chan1 <- 1
}

常见错误,接收未准备好就发送数据:

先有接收状态才能往通道发送内容!这是阻塞式通道的原则。
func main() {
   chan1 := make(chan int)

   //发送
   chan1 <- 1

   go func() {
      // 接收
      x := <-chan1
      fmt.Println(x)
   }()
}

改正:把go协程放在发送注释上方即可。

常见错误,往里存了2个数

// 接收一个

chan1 := make(chan int)

chan1 <- 1
chan1 <- 2

新的输入无法在通道非空的情况下传入,即传送数据时,通道必须是空的。

非阻塞式通道

chan1 := make(chan int,1)

相比阻塞式通道,这里就多指明了一个参数,这个参数是一个数值型,表示的是缓存区,数据在通道中传输时可以保存1个,拿上面常见错误的最后一个例子来说:

// 接收一个

chan1 := make(chan int,1)

chan1 <- 1
chan1 <- 2

这里我们缓冲了一个,被接收了一个,所以再放值的时候不会报错。

非阻塞通道还有这些特点:

  • 只要缓存区运行,没有接收方也不会报错。
  • 因为缓存区的存在,打破了接收方和发送方的限制,在一个协程中发送和接收是被允许的,甚至不讲顺序。

这两点可以自己去尝试一下。

遍历通道的内容

方法一

使用无穷循环,配上判断;无穷循环不断的去执行获取chan中的内容,它会返回两个值,第一个值是实际值,第二个值是通道是否关闭,若通道没关闭则进行相应的判断

for {
   v, ok := <-chan1
   fmt.Println(v)
   fmt.Println(ok)
}

方法二

不用关心通道是否关闭,通道关闭时自动退出循环。

for v := range chan1 { // 优雅的从通道循环取值
   fmt.Println(v)
}

【重点】 方法一和方法二的取舍:

  • 方法一 不需要告知通道关闭,因为通道在垃圾回收的机制内。
  • 方法二 需要告诉通道关闭来控制退出循环

综合例子一:传输数字,直到...

往通道里传输数字,直到传到10位置
package main

import (
   "fmt"
   "sync"
)

var wg sync.WaitGroup

func main() {
   var chan1 chan int = make(chan int, 10)
   i := 0

   wg.Add(1)
   go func() {
      for v := range chan1 { //如果通道关闭会自动退出循环,如果没有告知通道关闭,会报错
         fmt.Println(v)
      }
      defer wg.Done()
   }()

   for {
      if i == 10 {
         // 告知通道关闭了
         close(chan1)
         break
      } else {
         chan1 <- i
         i++
      }
   }

   wg.Wait()
}

综合例子二:筛选偶数

package main

import (
   "fmt"
   "sync"
)

var wg sync.WaitGroup

func main() {
   var in chan []int = make(chan []int, 10)
   var out chan []int = make(chan []int, 10)
   x := make([]int, 0)
   x = append(x, 1, 2, 3, 4, 5, 6, 7)
   out <- x
   close(out) // 关闭通道

   wg.Add(1)
   go odd(in, out)

   for v := range in { //同样需要告诉通道关闭了
      fmt.Println(v)
   }
   //time.Sleep(time.Second * 2000)
   wg.Wait()
}

func odd(in chan<- []int, out <-chan []int) {
   tmp := make([]int, 0)
   for res := range out { // 用range需要告诉通道关闭了
      //fmt.Println(res)
      for _, value := range res {
         if value%2 == 0 {
            //fmt.Println(value)
            tmp = append(tmp, value)
         }
      }
   }
   in <- tmp
   close(in)
   wg.Done()
}

// 助记
//1. chan<- int是一个只能发送的通道,可以发送但是不能接收;
//2. <-chan int是一个只能接收的通道,可以接收但是不能发送。
目录
相关文章
|
8天前
|
监控 算法 Go
Golang深入浅出之-Go语言中的服务熔断、降级与限流策略
【5月更文挑战第4天】本文探讨了分布式系统中保障稳定性的重要策略:服务熔断、降级和限流。服务熔断通过快速失败和暂停故障服务调用来保护系统;服务降级在压力大时提供有限功能以保持整体可用性;限流控制访问频率,防止过载。文中列举了常见问题、解决方案,并提供了Go语言实现示例。合理应用这些策略能增强系统韧性和可用性。
36 0
|
1天前
|
存储 编译器 Go
Go语言学习12-数据的使用
【5月更文挑战第5天】本篇 Huazie 向大家介绍 Go 语言数据的使用,包含赋值语句、常量与变量、可比性与有序性
17 6
Go语言学习12-数据的使用
|
2天前
|
Java Go
一文带你速通go语言指针
Go语言指针入门指南:简述指针用于提升效率,通过地址操作变量。文章作者sharkChili是Java/CSDN专家,维护Java Guide项目。文中介绍指针声明、取值,展示如何通过指针修改变量值及在函数中的应用。通过实例解析如何使用指针优化函数,以实现对原变量的直接修改。作者还邀请读者加入交流群深入探讨,并鼓励关注其公众号“写代码的SharkChili”。
9 0
|
2天前
|
存储 缓存 Java
来聊聊go语言的hashMap
本文介绍了Go语言中的`map`与Java的不同设计思想。作者`sharkChili`是一名Java和Go开发者,同时也是CSDN博客专家及JavaGuide项目的维护者。文章探讨了Go语言`map`的数据结构,包括`count`、`buckets指针`和`bmap`,解释了键值对的存储方式,如何利用内存对齐优化空间使用,并展示了`map`的初始化、插入键值对以及查找数据的源码过程。此外,作者还分享了如何通过汇编查看`map`操作,并鼓励读者深入研究Go的哈希冲突解决和源码。最后,作者提供了一个交流群,供读者讨论相关话题。
10 0
|
3天前
|
Java Go
Go语言学习11-数据初始化
【5月更文挑战第3天】本篇带大家通过内建函数 new 和 make 了解Go语言的数据初始化过程
17 1
Go语言学习11-数据初始化
|
3天前
|
自然语言处理 安全 Java
速通Go语言编译过程
Go语言编译过程详解:从词法分析(生成token)到句法分析(构建语法树),再到语义分析(类型检查、推断、匹配及函数内联)、生成中间码(SSA)和汇编码。最后,通过链接生成可执行文件。作者sharkchili,CSDN Java博客专家,分享技术细节,邀请读者加入交流群。
22 2
|
4天前
|
Java Linux Go
一文带你速通Go语言基础语法
本文是关于Go语言的入门介绍,作者因其简洁高效的特性对Go语言情有独钟。文章首先概述了Go语言的优势,包括快速上手、并发编程简单、设计简洁且功能强大,以及丰富的标准库。接着,文章通过示例展示了如何编写和运行Go代码,包括声明包、导入包和输出语句。此外,还介绍了Go的语法基础,如变量类型(数字、字符串、布尔和复数)、变量赋值、类型转换和默认值。文章还涉及条件分支(if和switch)和循环结构(for)。最后,简要提到了Go函数的定义和多返回值特性,以及一些常见的Go命令。作者计划在后续文章中进一步探讨Go语言的其他方面。
10 0
|
5天前
|
JavaScript 前端开发 Go
Go语言的入门学习
【4月更文挑战第7天】Go语言,通常称为Golang,是由Google设计并开发的一种编程语言,它于2009年公开发布。Go的设计团队主要包括Robert Griesemer、Rob Pike和Ken Thompson,这三位都是计算机科学和软件工程领域的杰出人物。
13 1
|
5天前
|
Go
|
6天前
|
分布式计算 Java Go
Golang深入浅出之-Go语言中的分布式计算框架Apache Beam
【5月更文挑战第6天】Apache Beam是一个统一的编程模型,适用于批处理和流处理,主要支持Java和Python,但也提供实验性的Go SDK。Go SDK的基本概念包括`PTransform`、`PCollection`和`Pipeline`。在使用中,需注意类型转换、窗口和触发器配置、资源管理和错误处理。尽管Go SDK文档有限,生态系统尚不成熟,且性能可能不高,但它仍为分布式计算提供了可移植的解决方案。通过理解和掌握Beam模型,开发者能编写高效的数据处理程序。
134 1