您现在的位置是:首页 >学无止境 >Go(二):包管理、通道、协程并发、互斥锁基础网站首页学无止境

Go(二):包管理、通道、协程并发、互斥锁基础

Prosper Lee 2023-07-08 00:00:03
简介Go(二):包管理、通道、协程并发、互斥锁基础

生成包管理文件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 ...
	}
}
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。