包管理、协程并发基础
- 生成包管理文件`go-mod`
- 第一步(初始化创建包管理文件)
- 第二步(导入包)
- 常用命令
- 导入远程包(示例:`gin`)
- 第一步(导入包)
- 第二步(安装包)
- 第三步(示例代码)
- 进程、线程、协程
- 进程
- 线程
- 协程
- 协程 `go ...`
- 协程示例一
- 协程示例二
- 通道`channel`
- 通过通道传递消息
- 通道等待
- 通道遍历
- `WaitGroup`
- 互斥锁`Mutex`
- `未加锁`协程程序
- 加了`互斥锁`的协程程序
- `runtime`
- `runtime.Gosched()`
- `runtime.Goexit()`
- `runtime.GOMAXPROCS(n)` `runtime.NumCPU()`
- `select`
- `time.NewTimer`
- `time.NewTicker`
- 定时器和停止定时器的示例
- 并发之原子操作`sync/atomic`
生成包管理文件go-mod
第一步(初始化创建包管理文件)
$ go mod init go-app # go mod init 项目路径
/* go-app/go.mod */
module go-app
go 1.20
第二步(导入包)
/* go-app/main.go */
package main
import "go-app/services"
func main() {
services.Login("Lee", "123456") // Lee 123456
services.GetUserInfoById("1024") // 1024 Lee
}
/* go-app/services/login.go */
package services
import "fmt"
func Login(username string, password string) {
fmt.Println(username, password)
}
/* go-app/services/user.go */
package services
import "fmt"
func GetUserInfoById(id string) {
fmt.Println(id, "Lee")
}
常用命令
$ go run # 编译并运行一个Go程序。
$ go build # 编译一个Go程序,生成可执行文件。
$ go test # 运行Go程序的测试用例。
$ go get # 从远程仓库下载并安装Go包。
$ go install # 编译并安装一个Go程序。
$ go fmt # 格式化Go源码。
$ go doc # 查看Go包的文档。
$ go mod # 管理Go模块(依赖管理)。
$ go env # 查看Go环境变量。
$ go version # 查看Go版本信息
导入远程包(示例:gin
)
查询使用第三方包的链接地址如下:
https://pkg.go.dev/
第一步(导入包)
import "github.com/gin-gonic/gin"
第二步(安装包)
$ go get -u github.com/gin-gonic/gin
# 解决无法安装问题
$ go env # 查看
# 修改如下
$ go env -w GO111MODULE=on
$ go env -w GOPROXY=https://goproxy.cn,direct
module go-app
go 1.20
require (
github.com/bytedance/sonic v1.8.7 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.9.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.12.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.3 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.7 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.8.0 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
第三步(示例代码)
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
进程、线程、协程
进程
本质上是一个独立执行的程序,进程是操作系统进行资源分配和调度的基本概念,操作系统进行资源分配和调度的一个独立单位。
线程
是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个进程中可以并发多个线程,每条线程执行不同的任务,切换受系统控制。
协程
又称为微线程,是一种用户态的轻量级线程,协程不像线程和进程需要进行系统内核上的上下文切换,协程的上下文切换是由用户自己决定的,有自己的上下文,所以说是轻量级的线程,也称之为用户级别的线程就叫协程,一个线程可以多个协程,线程进程都是同步机制,而协程则是异步。
协程 go ...
在一个函数的前边加上一个
go
关键字,代表了新开辟了一个子协程。当程序启动时,
main
函数执行将会被作为主协程,当主协程执行完毕后会自动销毁所有子协程。
协程示例一
package main
import (
"fmt"
"io/ioutil"
"net/http"
"time"
)
func request(method string, url string) {
req, err := http.NewRequest(method, url, nil)
if err != nil {
fmt.Println(err.Error())
return
}
res, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println(err.Error())
return
}
defer res.Body.Close()
b, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println(url, string(b))
}
请求同步进行,后请求等待最先请求出结果后出结果
func main() {
request("GET", "https://www.google.com/")
request("GET", "https://www.baidu.com/")
}
请求异步进行,先请求成功的先出结果
func main() {
go request("GET", "https://www.google.com/")
go request("GET", "https://www.baidu.com/")
time.Sleep(time.Millisecond * 60000)
}
协程示例二
非协程
形式
package main
import (
"fmt"
"net/http"
"time"
)
var links = []string{
"https://www.baidu.com/",
"https://www.jd.com/",
"https://www.taobao.com/",
"https://www.abcd.com/",
"https://www.sogou.com/",
"https://www.csdn.net/",
}
func main() {
// 0 https://www.baidu.com/ Success
// 1 https://www.jd.com/ Success
// 2 https://www.taobao.com/ Success
// 3 https://www.abcd.com/ Fail
// 4 https://www.sogou.com/ Success
// 5 https://www.csdn.net/ Success
for i, link := range links {
_, err := http.Get(link)
if err == nil {
fmt.Println(i, link, "Success")
} else {
fmt.Println(i, link, "Fail")
}
}
}
协程
形式
package main
import (
"fmt"
"net/http"
"time"
)
var links = []string{
"https://www.baidu.com/",
"https://www.jd.com/",
"https://www.taobao.com/",
"https://www.abcd.com/",
"https://www.sogou.com/",
"https://www.csdn.net/",
}
func main() {
// 3 https://www.abcd.com/ Fail
// 2 https://www.taobao.com/ Success
// 1 https://www.jd.com/ Success
// 0 https://www.baidu.com/ Success
// 4 https://www.sogou.com/ Success
// 5 https://www.csdn.net/ Success
for i, link := range links {
go func(i int, link string) {
_, err := http.Get(link)
if err == nil {
fmt.Println(i, link, "Success")
} else {
fmt.Println(i, link, "Fail")
}
}(i, link)
}
time.Sleep(time.Millisecond * 10000)
}
通道channel
通道是协程之间的通道,可以让协程之间相互通信。
通过通道传递消息
以下程序主线程会等待子线程发送消息,当接收到第一条消息后立即在主协程中执行接收数据程序,随后主线程执行完毕,故而只打印出一条消息
package main
import (
"fmt"
"net/http"
)
var links = []string{
"https://www.baidu.com/",
"https://www.jd.com/",
"https://www.taobao.com/",
"https://www.abcd.com/",
"https://www.sogou.com/",
"https://www.csdn.net/",
}
func main() {
// 字符串类型通道
msg := make(chan string)
for i, link := range links {
// 协程
go func(i int, link string, msg chan string) {
_, err := http.Get(link)
if err == nil {
// 发送消息
msg <- fmt.Sprintln(i, link, "Success")
} else {
// 发送消息
msg <- fmt.Sprintln(i, link, "Fail")
}
}(i, link, msg)
}
// 接收消息
data := <-msg
// 打印接收到的消息
fmt.Println(data)
}
通道等待
当主协程接收不到消息时,程序会一直停滞在接收消息过程中,故而能输出所有子线程输出的打印结果,或者可定义多个接收消息变量
package main
import (
"fmt"
"net/http"
)
var links = []string{
"https://www.baidu.com/",
"https://www.jd.com/",
"https://www.taobao.com/",
"https://www.abcd.com/",
"https://www.sogou.com/",
"https://www.csdn.net/",
}
func main() {
// 字符串类型通道
msg := make(chan string)
for i, link := range links {
// 协程
go func(i int, link string, msg chan string) {
_, err := http.Get(link)
if err == nil {
fmt.Println(i, link, "Success")
} else {
fmt.Println(i, link, "Fail")
}
}(i, link, msg)
}
// 接收消息
data := <-msg
// 打印接收到的消息
fmt.Println(data)
}
package main
import (
"fmt"
"net/http"
)
var links = []string{
"https://www.baidu.com/",
"https://www.jd.com/",
"https://www.taobao.com/",
"https://www.abcd.com/",
"https://www.sogou.com/",
"https://www.csdn.net/",
}
func main() {
// 字符串类型通道
msg := make(chan string)
for i, link := range links {
// 协程
go func(i int, link string, msg chan string) {
_, err := http.Get(link)
if err == nil {
// 发送消息
msg <- fmt.Sprintln(i, link, "Success")
} else {
// 发送消息
msg <- fmt.Sprintln(i, link, "Fail")
}
}(i, link, msg)
}
// 打印接收到的消息
fmt.Println(0, "--->", <-msg) // 0 ---> 3 https://www.abcd.com/ Fail
fmt.Println(1, "--->", <-msg) // 1 ---> 1 https://www.jd.com/ Success
fmt.Println(2, "--->", <-msg) // 2 ---> 2 https://www.taobao.com/ Success
fmt.Println(3, "--->", <-msg) // 3 ---> 4 https://www.sogou.com/ Success
fmt.Println(4, "--->", <-msg) // 4 ---> 0 https://www.baidu.com/ Success
fmt.Println(5, "--->", <-msg) // 5 ---> 5 https://www.csdn.net/ Success
}
通道遍历
package main
import "fmt"
func main() {
i := make(chan int)
go func() {
for j := 0; j < 5; j++ {
i <- j
}
close(i) // 关闭通道
}()
//for {
// v, ok := <-i
// if ok {
// fmt.Println(v, ok)
// } else {
// break
// }
//}
for j := range i {
fmt.Println(j)
}
//for j := 0; j < 10; j++ {
// fmt.Println(<-i)
//}
}
WaitGroup
类似前端的
axios
拦截器实现加载状态的程序,当所有请求完成后才关闭加载状态
package main
import (
"fmt"
"net/http"
"sync"
)
// 定义WaitGroup变量
var wg = sync.WaitGroup{}
var links = []string{
"https://www.baidu.com/",
"https://www.jd.com/",
"https://www.taobao.com/",
"https://www.abcd.com/",
"https://www.sogou.com/",
"https://www.csdn.net/",
}
func main() {
for i, link := range links {
// 协程
go func(i int, link string) {
// 每次完成请求后-1
defer wg.Done()
_, err := http.Get(link)
if err == nil {
fmt.Println(i, link, "Success")
} else {
fmt.Println(i, link, "Fail")
}
}(i, link)
// 每次执行协程+1
wg.Add(1)
}
// 等待
wg.Wait()
}
互斥锁Mutex
未加锁
协程程序
异步并发执行
package main
import (
"fmt"
"sync"
"time"
)
var i = 0
var wg sync.WaitGroup
func add() {
defer wg.Done()
i++
fmt.Printf("add->%v;", i)
time.Sleep(time.Second * 2)
}
func sub() {
defer wg.Done()
i--
fmt.Printf("sub->%v;", i)
}
/*
未加锁:(异步并发执行)
结果1:add->1;add->1;sub->0;sub->0;add->1;sub->0;add->1;sub->0;add->2;sub->-1;-1
结果2:add->1;sub->0;sub->-1;add->0;sub->-1;add->0;add->1;sub->1;add->2;sub->0;0
*/
func main() {
for j := 0; j < 5; j++ {
wg.Add(1)
go add()
wg.Add(1)
go sub()
}
wg.Wait()
fmt.Println(i)
}
加了互斥锁
的协程程序
并发程序下利用互斥锁实现同步依次执行,等待解锁后在向下执行
package main
import (
"fmt"
"sync"
"time"
)
var i = 0
var wg sync.WaitGroup
var mutex sync.Mutex
func add() {
defer wg.Done()
mutex.Lock()
i++
fmt.Printf("add->%v;", i)
time.Sleep(time.Second * 2)
mutex.Unlock()
}
func sub() {
mutex.Lock()
defer wg.Done()
i--
fmt.Printf("sub->%v;", i)
mutex.Unlock()
}
/*
加锁后:(并发程序下利用互斥锁实现同步依次执行,等待解锁后在向下执行)
结果1:add->1;sub->0;sub->-1;add->0;sub->-1;add->0;add->1;sub->0;sub->-1;add->0;0
结果2:add->1;sub->0;add->1;sub->0;add->1;add->2;sub->1;sub->0;add->1;sub->0;0
结果3:add->1;sub->0;sub->-1;add->0;sub->-1;add->0;sub->-1;add->0;sub->-1;add->0;0
*/
func main() {
for j := 0; j < 5; j++ {
wg.Add(1)
go add()
wg.Add(1)
go sub()
}
wg.Wait()
fmt.Println(i)
}
runtime
runtime.Gosched()
用于让出当前 Goroutine 的执行权限,让其他 Goroutine 有机会运行。这个函数的作用是让 Goroutine 主动让出 CPU 时间片,以便让其他 Goroutine 运行
不使用runtime.Gosched()
package main
import (
"fmt"
"time"
)
func printNum(msg string, n int) {
for i := 0; i < n; i++ {
fmt.Print(msg, i, " ")
}
}
// 结果
// a0 a1 a2 b0 b1 b2 b3 b4 Done
// b0 b1 b2 b3 b4 a0 a1 a2 Done
func main() {
go printNum("a", 3)
go printNum("b", 5)
time.Sleep(1 * time.Second) // 主 Goroutine 等待 1 秒钟
fmt.Println("Done")
}
使用runtime.Gosched()
package main
import (
"fmt"
"runtime"
"time"
)
func printNum(msg string, n int) {
for i := 0; i < n; i++ {
fmt.Print(msg, i, " ")
runtime.Gosched() // 让出执行权限
}
}
// 结果
// a0 b0 a1 a2 b1 b2 b3 b4 Done
// b0 b1 b2 b3 b4 a0 a1 a2 Done
// a0 b0 b1 a1 b2 a2 b3 b4 Done
// a0 b0 a1 b1 a2 b2 b3 b4 Done
func main() {
go printNum("a", 3)
go printNum("b", 5)
time.Sleep(1 * time.Second) // 主 Goroutine 等待 1 秒钟
fmt.Println("Done")
}
使用runtime.Gosched()
目的
使用 runtime.Gosched() 函数可以让两个 Goroutine 更加公平地竞争 CPU 资源
runtime.Goexit()
用于立即终止当前 goroutine 的执行, 不会影响其他的 goroutine,也不会导致整个程序的退出。可以将其理解为一个强制性的 return 语句,用于从当前 goroutine 中立即返回
package main
import (
"fmt"
"runtime"
"time"
)
func printNumA() {
for i := 0; i < 10; i++ {
fmt.Print("A", i, " ")
if i >= 5 {
runtime.Goexit()
}
}
}
func printNumB() {
for i := 0; i < 10; i++ {
fmt.Print("B", i, " ")
}
}
// 结果
// A0 A1 A2 A3 A4 A5 B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 Done
// B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 A0 A1 A2 A3 A4 A5 Done
func main() {
go printNumA()
go printNumB()
time.Sleep(1 * time.Second) // 主 Goroutine 等待 1 秒钟
fmt.Println("Done")
}
runtime.GOMAXPROCS(n)
runtime.NumCPU()
runtime.GOMAXPROCS(n)
设置程序采用CPU核心数,默认为最多核心
runtime.NumCPU()
获取当前CPU核心数
默认
package main
import (
"fmt"
"runtime"
"time"
)
func a() {
for i := 0; i < 20; i++ {
fmt.Print("a")
}
}
func b() {
for i := 0; i < 20; i++ {
fmt.Print("b")
}
}
func main() {
fmt.Print(runtime.NumCPU()) // 12 获取当前CPU核心数
// 默认结果(交替出现)
// 12 bbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaabaaaab
go a()
go b()
time.Sleep(time.Second)
}
单核
package main
import (
"fmt"
"runtime"
"time"
)
func a() {
for i := 0; i < 20; i++ {
fmt.Print("a")
}
}
func b() {
for i := 0; i < 20; i++ {
fmt.Print("b")
}
}
func main() {
fmt.Print(runtime.NumCPU()) // 12 获取当前CPU核心数
// 单核结果(顺序出现)
//runtime.GOMAXPROCS(1) 12 bbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa
runtime.GOMAXPROCS(1) // 设置为单核
go a()
go b()
time.Sleep(time.Second)
}
双核
package main
import (
"fmt"
"runtime"
"time"
)
func a() {
for i := 0; i < 20; i++ {
fmt.Print("a")
}
}
func b() {
for i := 0; i < 20; i++ {
fmt.Print("b")
}
}
func main() {
fmt.Print(runtime.NumCPU()) // 12 获取当前CPU核心数
// 双核结果(交替出现)
// runtime.GOMAXPROCS(2) 12 bbbaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbaaaaa
// runtime.GOMAXPROCS(2) 12 aaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbb
// runtime.GOMAXPROCS(2) 12 aaaaaaabbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaa
runtime.GOMAXPROCS(2) // 设置为双核
go a()
go b()
time.Sleep(time.Second)
}
select
用于监听多个通道的输入和输出,可以实现非阻塞的通信,等待多个通道操作的函数
select 中,每个 case 都必须是一个通道操作。当 select 函数被调用时,它会等待其中的一个 case 操作完成,然后执行该 case 语句块。如果有多个 case 操作同时完成,那么 Go 会随机地选中其中一个
package main
import (
"fmt"
"time"
)
func main() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
c1 <- "Hello"
}()
go func() {
c2 <- "World"
}()
for {
select {
case msg1 := <-c1:
fmt.Println(msg1)
case msg2 := <-c2:
fmt.Println(msg2)
default: // 避免死锁
fmt.Println("default")
}
time.Sleep(time.Second)
}
}
package main
import (
"fmt"
"time"
)
func main() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
c1 <- "Hello"
close(c1) // 避免死锁
}()
go func() {
c2 <- "World"
close(c2) // 避免死锁
}()
for {
select {
case msg1 := <-c1:
fmt.Println(msg1)
case msg2 := <-c2:
fmt.Println(msg2)
}
time.Sleep(time.Second)
}
}
time.NewTimer
用于创建一个新的定时器并返回该定时器,在指定的时间后触发一个事件
package main
import (
"fmt"
"time"
)
func main() {
timer := time.NewTimer(2 * time.Second)
fmt.Println("计时器已启动")
<-timer.C // 阻塞等待计时器的时间到达
fmt.Println("时间到!")
}
停止定时器
package main
import (
"fmt"
"time"
)
/**
* 结果:
* 计时器已启动
* 时间到!
*/
func main() {
timer := time.NewTimer(5 * time.Second)
fmt.Println("计时器已启动")
time.Sleep(1 * time.Second)
if !timer.Stop() {
fmt.Println(<-timer.C)
}
fmt.Println("时间到!")
}
package main
import (
"fmt"
"time"
)
/**
* 结果:
* 计时器已启动
* 2023-05-02 12:06:31.980288 +0800 CST m=+5.001244626
* 时间到!
*/
func main() {
timer := time.NewTimer(5 * time.Second)
fmt.Println("计时器已启动")
time.Sleep(6 * time.Second)
if !timer.Stop() {
fmt.Println(<-timer.C) // 2023-05-02 12:06:31.980288 +0800 CST m=+5.001244626
}
fmt.Println("时间到!")
}
重置定时器
package main
import (
"fmt"
"time"
)
/**
* 结果:
* 计时器已启动
* 2023-05-02 12:11:00.035369 +0800 CST m=+1.001328668
* 时间到!
*/
func main() {
timer := time.NewTimer(5 * time.Second)
fmt.Println("计时器已启动")
timer.Reset(time.Second) // 重置定时器为1s
fmt.Println(<-timer.C)
fmt.Println("时间到!")
}
time.NewTicker
定时器和停止定时器的示例
package main
import (
"fmt"
"time"
)
func main() {
// 创建一个定时器,每隔2秒钟执行一次
ticker := time.NewTicker(time.Second * 2)
count := 0
for t := range ticker.C {
count++
fmt.Println(t)
if count == 3 {
ticker.Stop()
fmt.Println("定时器停止!")
break
}
}
}
并发之原子操作sync/atomic
指不会被中断的一个或一系列操作,即使在并发的情况下也能保证操作的原子性
协程累加累减变量
package main
import (
"fmt"
"sync"
"sync/atomic"
)
var wg sync.WaitGroup
var i int32 = 100
func add() {
defer wg.Done()
atomic.AddInt32(&i, 1)
}
func sub() {
defer wg.Done()
atomic.AddInt32(&i, -1)
}
func main() {
for {
for j := 0; j < 100; i++ {
wg.Add(1)
go add()
wg.Add(1)
go sub()
}
wg.Wait()
fmt.Println(i) // 100 100 100 100 100 100 ...
}
}