Go 语言入门(三)并发

写在前面

在学习 Go 语言之前,我自己是有一定的 Java 和 C++ 基础的,这篇文章主要是基于A tour of Go编写的,主要是希望记录一下自己的学习历程,加深自己的理解

Go 程

「Go 程goroutine」:由 Go 运行时管理的轻量级线程

运行「Go 程」很简单,只要执行下面代码:

  1. go f(x, y, z)

就会启动一个新的 Go 程并执行f(x, y, z)fxyz的运算发生在当前的 Go 程中,而f的执行发生在新的 Go 程中。

「Go 程」在相同的地址空间中运行,因此在访问共享的内存时必须进行同步sync包提供了这种能力,不过在 Go 中并不经常用到,我们用得比较多的是信道

信道

「信道」是带有类型的管道,你可以通过它用信道操作符<-来发送或者接收值:

  1. ch <- v // 将 v 发送至信道 ch
  2. v := <-ch // 从信道 ch 接受值并赋予 v

可以看到,操作符<-是一个箭头,实际上就是表示数据的流向。它有些类似于队列,对于发送至信道的值是先进先出的。

使用信道

和「切片」以及「映射」一样,在使用信道之前,我们必须先初始化一个信道:

  1. ch := make(chan int)

默认情况下,发送和接收操作在另一端准备好之前都会阻塞。这使得 Go 程可以在没有显式的锁或竞态变量的情况下进行同步。

以下示例对切片中的数进行求和,将任务分配给两个 Go 程。一旦两个 Go 程完成了它们的计算,它就能算出最终的结果:

  1. package main
  2. import "fmt"
  3. func sum(s []int, c chan int) {
  4. sum := 0
  5. for _, v := range s {
  6. sum += v
  7. }
  8. c <- sum // 将和送入 c
  9. }
  10. func main() {
  11. s := []int{7, 2, 8, -9, 4, 0}
  12. c := make(chan int)
  13. go sum(s[:len(s)/2], c)
  14. go sum(s[len(s)/2:], c)
  15. x, y := <-c, <-c // 从 c 中接收
  16. fmt.Println(x, y, x+y)
  17. // 执行结果: -5 17 12
  18. }

带缓冲的信道

信道是带缓冲的,也就是我们可以指定信道的缓冲区长度:仅当信道的缓冲区填满后,「发送方」向其发送数据时会阻塞。当缓冲区为空时,「接受方」会阻塞。

使用make函数便能够在创建信道的同时指定缓冲区长度:

  1. // 创建一个缓冲区长度为 2 的信道
  2. c := make(chan int, 2)

range 和 close

「发送者」可通过close关闭一个信道来表示没有需要发送的值了。

「接收者」可以通过为接收表达式分配第二个参数来测试信道是否被关闭:

  1. v, ok := <-ch

和「映射」以及接口的「类型断言」相似,如果信道已经关闭,ok会被设为false

作为「接受者」,我们可以使用for i := range c来不断从信道c接受信息,知道它被关闭。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. // 向信道中输入斐波那契数列
  6. func fibonacci(n int, c chan int) {
  7. x, y := 0, 1
  8. for i := 0; i < n; i++ {
  9. c <- x
  10. x, y = y, x+y
  11. }
  12. // 输入完毕后关闭信道
  13. close(c)
  14. }
  15. func main() {
  16. c := make(chan int, 10)
  17. go fibonacci(cap(c), c)
  18. // 只有在信道关闭后才会停止循环
  19. for i := range c {
  20. fmt.Println(i)
  21. }
  22. }

注意: 信道与文件不同,通常情况下无需关闭它们。只有在必须告诉接收者不再有需要发送的值时才有必要关闭,例如终止一个 range 循环。

select 语句

「select 语句」:使一个 Go 程可以等待多个通信操作。

select会阻塞到某个分支可以继续执行为止,这时就会执行该分支;如果多个分支都准备好时,会随机选择一个执行。

下面的例子能够演示并说明一些 select 的使用场景:

  1. package main
  2. import "fmt"
  3. func fibonacci(c, quit chan int) {
  4. x, y := 0, 1
  5. for {
  6. select {
  7. // 实际上这个 case 一直在不断执行直到 return
  8. case c <- x:
  9. x, y = y, x+y
  10. // 这里时 quit 信道的接收方
  11. // 直到下面的 go 程中 for 循环执行完毕后,quit 才不为空,才能够执行这个 case
  12. case <-quit:
  13. fmt.Println("quit")
  14. return
  15. }
  16. }
  17. }
  18. func main() {
  19. // 新建两个信道
  20. c := make(chan int)
  21. quit := make(chan int)
  22. // 启动一个 go 程
  23. go func() {
  24. for i := 0; i < 10; i++ {
  25. // 当 c 缓冲区为空时,接收方(也就是这里)会阻塞
  26. fmt.Println(<-c)
  27. }
  28. quit <- 0
  29. }()
  30. fibonacci(c, quit)
  31. }

上面的例子中,我们可以修改 go 程中 for 循环的循环次数,多试几次就可以明白select语句的执行情况了。

上面我们只为select语句指定了两个 case,实际上我们还可以像switch语句一样为它设置默认值default,当 select 中的其它分支都没有准备好时,default 分支就会执行:

  1. select {
  2. case i := <-c:
  3. // 使用 i
  4. default:
  5. // 从 c 中接收会阻塞时执行
  6. }

互斥锁: sync.Mutex

从上面可以看到,通过「信道」我们可以方便的在各个 Go 程之间进行通信。但有时,我们希望同一时间只有一个 Go 程能够访问某个共享的变量,这就是互斥(mutual exclusion),我们通常使用互斥锁(Mutex)这一数据结构来提供这种机制。

Go 标准库中提供了sync.Mutex互斥锁类型及其两个方法:Lock()Unlock()来实现「互斥」。

和 Java 中一样,我们在代码执行前调用Lock(),在代码执行结束后调用Unlock()来保证代码的互斥执行。参加下面代码的Inc()方法

我们可以用defer语句来保证互斥锁一定会被解锁,参见下面的Value()方法:

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. "time"
  6. )
  7. // SafeCounter 的并发使用是安全的。
  8. type SafeCounter struct {
  9. v map[string]int
  10. mux sync.Mutex
  11. }
  12. // Inc 增加给定 key 的计数器的值。
  13. func (c *SafeCounter) Inc(key string) {
  14. c.mux.Lock()
  15. // Lock 之后同一时刻只有一个 goroutine 能访问 c.v
  16. c.v[key]++
  17. c.mux.Unlock()
  18. }
  19. // Value 返回给定 key 的计数器的当前值。
  20. func (c *SafeCounter) Value(key string) int {
  21. c.mux.Lock()
  22. // Lock 之后同一时刻只有一个 goroutine 能访问 c.v
  23. defer c.mux.Unlock()
  24. return c.v[key]
  25. }
  26. func main() {
  27. c := SafeCounter{v: make(map[string]int)}
  28. for i := 0; i < 1000; i++ {
  29. go c.Inc("somekey")
  30. }
  31. time.Sleep(time.Second)
  32. fmt.Println(c.Value("somekey"))
  33. }

Go 语言入门(三)并发的更多相关文章

  1. go语言入门(三)

    条件语句 go语言的条件语句结构如下: go语言的条件语句和其他语言类似.简单列举下: 1.if 语句,布尔表达式不需要括号 if 布尔表达式 { /* 在布尔表达式为 true 时执行 */ } 2 ...

  2. C/C++编程笔记:C语言入门知识点(三),请收藏C语言最全笔记!

    今天我们继续来学习C语言的入门知识点,第一课:C/C++编程笔记:C语言入门知识点(二),请收藏C语言最全笔记! 21. 输入 & 输出 当我们提到输入时,这意味着要向程序填充一些数据.输入可 ...

  3. Go语言基础之并发

    并发是编程里面一个非常重要的概念,Go语言在语言层面天生支持并发,这也是Go语言流行的一个很重要的原因. Go语言中的并发编程 并发与并行 并发:同一时间段内执行多个任务(你在用微信和两个女朋友聊天) ...

  4. 《JavaScript语言入门教程》记录整理:入门和数据类型

    目录 入门篇 js介绍 历史 基本语法 数据类型 概述 null 和 undefined 数值 字符串 对象 函数 数组 本系列基于阮一峰老师的<JavaScrip语言入门教程>或< ...

  5. GO 语言入门(一)

    GO 语言入门(一) 本文写于 2020 年 1 月 18 日 Go 由 Google 工程师 Robert Griesemer,Rob Pike 和 Ken Thompson 设计的一门编程语言,第 ...

  6. 【原创】NIO框架入门(三):iOS与MINA2、Netty4的跨平台UDP双向通信实战

    前言 本文将演示一个iOS客户端程序,通过UDP协议与两个典型的NIO框架服务端,实现跨平台双向通信的完整Demo.服务端将分别用MINA2和Netty4进行实现,而通信时服务端你只需选其一就行了.同 ...

  7. c语言入门教程 / c语言入门经典书籍

    用C语言开始编写代码初级:C语言入门必备(以下两本书任选一本即可) C语言是作为从事实际编程工作的程序员的一种工具而出现的,本阶段的学习最主要的目的就是尽快掌握如何用c语言编写程序的技能.对c语言的数 ...

  8. 【转】c语言入门教程 / c语言入门经典书籍

    用C语言开始编写代码 初级:C语言入门必备 (以下两本书任选一本即可) C语言是作为从事实际编程工作的程序员的一种工具而出现的,本阶段的学习最主要的目的就是尽快掌握如何用c语言编写程序的技能.对c语言 ...

  9. Swift语言入门之旅

    Swift语言入门之旅  学习一门新的计算机语言,传统来说都是从编写一个在屏幕上打印"Hello world"的程序開始的.那在 Swift,我们使用一句话来实现它: printl ...

随机推荐

  1. Windows10 图标变白修复

    Windows10 图标变白修复 本文作者:天析 作者邮箱:2200475850@qq.com 发布时间: Tue, 16 Jul 2019 10:54:00 +0800 这种问题多半是ico缓存造成 ...

  2. linux-修改树莓派分辨率

    直接在树莓派下编辑 使用命令行来编辑配置文件 sudo nano /boot/config.txt 并在config.txt文件的最后加上以下代码即可 max_usb_current=1 hdmi_g ...

  3. 三:MySQL系列之SQL查询

    本篇主要介绍使用SQL查询数据库的操作,包括条件查询.排序.聚合函数.分组.分页.连接查询.自关联.子查询等命令操作. 首先我们先创建一个数据库.数据表.插入字段: --------这部分在上篇以及介 ...

  4. sudo 权限的管理

    一.sudo执行命令的流程将当前用户切换到超级用户下,或切换到指定的用户下,然后以超级用户或其指定切换到的用户身份执行命令,执行完成后,直接退回到当前用户.具体工作过程如下:当用户执行sudo时,系统 ...

  5. C#中流Stream的使用-学习

    概念 提供字节序列的一般视图.这是一个抽象类. 子类: Derived Microsoft.JScript.COMCharStream System.Data.OracleClient.OracleB ...

  6. python自动化

    自动化测试一些问题 什么是自动化测试? 自动化测试,顾名思义,自动完成测试工作.通过一些自动化测试工具或自己造轮子实现模拟之前人工点点/写写的工作并验证其结果完成整个测试过程,这样的测试过程,便是自动 ...

  7. 源码安装缺少configure文件

    源代码中没有configure的软件安装方法 今天下载了一个旧版的GeoIP软件包,解压以后发现代码包中没有configure文件,现在这这里记录一下安装遇到的问题 网上大部分GeoIP下载地址已经失 ...

  8. PHP搭建大文件切割分块上传功能示例

    转载:https://www.jb51.net/article/101931.htm 背景 在网站开发中,文件上传是很常见的一个功能.相信很多人都会遇到这种情况,想传一个文件上去,然后网页提示“该文件 ...

  9. unordered_map初用

    unordered_map,顾名思义,就是无序map,STL内部实现了Hash 所以使用时可以当做STL的Hash表使用,时间复杂度可做到O(1)查询 在C++11前,使用unordered_map要 ...

  10. 第五章 CSS美化网页元素

    一.span标签:能让某几个文字或者某个词语凸显出来 <p> 今天是11月份的<span>第一天</span>,地铁卡不打折了 </p> 二.字体风格 ...