Go语言并发(一)——goroutine与Waitgroup

简介: Go语言并发(一)——goroutine与Waitgroup

协程

前言

在很多语言中都会提到并发的概念, 例如python, Java,C++等等,一般来说 都会

使用多线程或多进程来 实现并发调度,但是多线程/进程 一般会耗费大量内存, 而在go

语言中我们可以使用协程来达到并发调度的目的,

协程的简介

协程是轻量级的线程(或者说是用户态的线程),与常用的线程不同的是,它本身的调度不由操作系统

来完成,而是由go语言本身所带的调度器来完成,进而减少了上下文切换的开销,这也是go语言性能提升的原因。

协程的简单使用

与其他语言不同的是,go语言中协程的实现非常简单,只需要关键字go就可以实现一个协程,如下:

package main
import "fmt"
func synchello() {
  fmt.Println("hello world")
}
func main() {
  go synchello()
}

在这里我们就利用了go关键字实现了一个简单的协程,但是上面的代码没有任何的输出,

因为虽然我们利用go关键字已经创建出了子协程,但是由于我们的父协程(main函数)已经

结束了,所以子协程也就无法执行了,所以如果我们想让子协程执行,那么我们就需要让父

协程在子协程结束之前先不要结束,如下:

package main
import (
  "fmt"
  "time"
)
func synchello() {
  fmt.Println("hello world")
}
func main() {
  go synchello()
  time.Sleep(10 * time.Second)
}

这样我们就可以看到子协程的输出了,但是这种方法并不是最佳的比如下面这个代码:

package main
import (
  "fmt"
  "time"
)
func synchello() {
  fmt.Println("hello world")
}
func main() {
  for i := 0; i <= 20; i++ {
    go func() {
      fmt.Println(i)
    }()
  }
  time.Sleep(30 * time.Second)
}

输出结果为:

10
15
21
21
21
21
21
21
21
21
21
21
21
21
21
21
21
21
21
21
21

可以看到,我们并没有按照我们期望的顺序输出,事实上在并发编程上,我们使用Sleep

函数并不是一种高明的手段,Go中给我们提供了很多并发控制的手段,例如:

  • channel: 管道,用于协程之间的通信
  • Context: 上下文,用于协程之间的通信
  • WaitGroup: 信号量
  • Mutex: 互斥锁
  • RWMutex: 读写互斥锁
    我们可以利用它们来实现我们所想要的并发控制

WaitGrouop(信号量)

WaitGroup的介绍及使用

什么是Waitgroup

WaitGroup是Go语言中一个并发控制工具,它由sync包提供,WairGroup即等待执行,

我们可以利用它轻易的实现等待一组协程的效果

WaitGroup的结构

Waitgroup向外暴露了三个方法:

  • Add:该方法指明了需要我们等待线程的数量
func (wg *WaitGroup) Add(delta int)
  • Done:该方法表示一个线程已经执行完毕,当所有的线程都执行完毕之后,Wait方法
    才会返回
func (wg *WaitGroup) Done()
  • Wait:该方法表示等待所有的线程执行完毕
func (wg *WaitGroup) Wait()

WaitGroup的使用

WaitGroup的使用非常简单,它的内部实现主要是基于计数器+信号量,程序刚开始时

调用Add来初始化计数,每当一个协程执行完毕后调用Done,这时计数就-1,直到减为

0,在这期间Wait会一直阻塞主协程直到全部计数都减为0,此时主协程才会被唤醒

我们来看一个简单示例:

package main
import (
  "fmt"
  "sync"
)
func main() {
  var wait sync.WaitGroup
  wait.Add(1)
  go func() {
    defer wait.Done()
    fmt.Println(1)
  }()
  wait.Wait()
  fmt.Println(2)
}

输出结果为:

1
2

我们还可以来看一下接下来的这个样例:

package main
import (
  "fmt"
  "sync"
)
func main() {
  var mainwait sync.WaitGroup
  var wait sync.WaitGroup
  mainwait.Add(10)
  for i := 0; i < 10; i++ {
    defer wait.Done()
    defer mainwait.Done()
    func(i int) {
      fmt.Println(i)
    }(i)
    wait.Wait()   //等循环执行完毕
  }
  mainwait.Wait()
}

执行结果为:

0
1
2
3
4
5
6
7
8
9

WaitGroup通常会用于动态调整协程的数量,比如我们在事先就已经知道了协程的数量

又或者是我们在运行过程中需要去动态的调整,而我们在使用Waitgroup时也不应该

复制它的值,如果我们想将其作为函数参数进行传递的时候,需要传递指针而不是复制它的值

值。倘若使用复制的值,计数完全无法作用到真正的WaitGroup上,这可能会导致主协程一直

阻塞等待,程序将无法正常运行。

示例:

package main
import (
  "fmt"
  "sync"
)
func main() {
  var wait sync.WaitGroup
  wait.Add(1)
  hello(&wait)
  wait.Wait()
  fmt.Println("end")
}
func hello(wait *sync.WaitGroup) {
  fmt.Println("hello")
  wait.Done()
}

当计数变为负数,或者计数数量大于子协程数量时,将会引发panic.

相关文章
|
7天前
|
Go
Go 语言循环语句
在不少实际问题中有许多具有规律性的重复操作,因此在程序中就需要重复执行某些语句。
17 1
|
6天前
|
存储 安全 Go
Go to Learn Go之并发
Go to Learn Go之并发
19 8
|
6天前
|
Go 开发者
探索Go语言的并发之美
在Go语言的世界里,"并发"不仅仅是一个特性,它是一种哲学。本文将带你领略Go语言中goroutine和channel的魔力,揭示如何通过Go的并发机制来构建高效、可靠的系统。我们将通过一个简单的示例,展示如何利用Go的并发特性来解决实际问题,让你的程序像Go一样,轻盈而强大。
|
7天前
|
JSON Go API
使用Go语言和Gin框架构建RESTful API:GET与POST请求示例
使用Go语言和Gin框架构建RESTful API:GET与POST请求示例
|
7天前
|
Go
go语言创建字典
go语言创建字典
|
8天前
|
NoSQL Go API
go语言操作Redis
go语言操作Redis
|
8天前
|
Unix Go
go语言获取当前时间戳
go语言获取当前时间戳
|
7天前
|
Go
Go 语言接口
Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。 接口可以让我们将不同的类型绑定到一组公共的方法上,从而实现多态和灵活的设计。
|
8天前
|
存储 Go
go语言字符串变小写
go语言字符串变小写
|
4月前
|
Go
go语言并发编程(五) ——Context
go语言并发编程(五) ——Context
下一篇
无影云桌面