yyz notes yyz notes
首页
  • RBAC权限设计
  • 架构图标设计
  • 账号体系
  • python基础
  • python高级
  • python模块
  • python设计模式
  • python数据结构与算法
  • django
  • django-DRF
  • flask
  • 直接设计开源pip包
  • 直接设计开源项目
  • python示例题/脚本
  • python面试题
  • golang基础
  • golang高级
  • golang常用组件
  • gin框架
  • es6
  • javascript
  • react
  • vue
  • TypeScript
  • mysql
  • redis
  • minio
  • elasticsearch
  • mongodb
  • 消息队列
  • 自动化测试
  • 操作系统

    • linux
    • windows
  • nginx
  • docker
  • k8s
  • git
  • ldap
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

益章

可乐鸡翅
首页
  • RBAC权限设计
  • 架构图标设计
  • 账号体系
  • python基础
  • python高级
  • python模块
  • python设计模式
  • python数据结构与算法
  • django
  • django-DRF
  • flask
  • 直接设计开源pip包
  • 直接设计开源项目
  • python示例题/脚本
  • python面试题
  • golang基础
  • golang高级
  • golang常用组件
  • gin框架
  • es6
  • javascript
  • react
  • vue
  • TypeScript
  • mysql
  • redis
  • minio
  • elasticsearch
  • mongodb
  • 消息队列
  • 自动化测试
  • 操作系统

    • linux
    • windows
  • nginx
  • docker
  • k8s
  • git
  • ldap
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • golang基础

    • 初识golang

    • golang基础语法

    • golang流程控制

    • golang函数

    • golang内置函数

    • golang包

    • golang错误异常处理

    • golang面向对象(结构体)

    • golang文件处理

    • golang并发编程简介

    • golang并发编程-协程

      • go中的协程和管道介绍
      • 协程入门案例
      • goroutine的使用
        • 1. 创建协程 goroutine
          • _使用普通函数创建 goroutine_
          • _格式_
          • 例子
          • _使用匿名函数创建goroutine_
          • 格式
          • 例子
        • 2. 启动多个协程
          • 创建多个协程使用闭包问题
        • 3. 主死从随
        • 4. 使用WaitGroup解决主死从随
          • WaitGroup的作用
          • 主要方法
          • 案例
          • Add\Done\Wait
          • 如果防止忘记计数器减1操作,结合defer关键字使用:
          • 可以最开始在知道协程次数的情况下先Add操作
        • 5. 锁(多个协程操作同一数据)
          • 当多个协程操纵同一数据会出现问题
          • 案例
          • 问题出现的原因:(图解为其中一种可能性)
          • 解决问题
          • Mutex 互斥锁
          • RWMutex 读写锁
        • 6. _死锁、活锁和饥饿概述_
      • channel通道(goroutine之间通信的管道)
      • defer+recover机制处理错误
    • golang网络编程

    • 反射 reflect

  • golang高级

  • 常用组件

  • gin

  • golang
  • golang基础
  • golang并发编程-协程
YiZhang-You
2023-05-24
目录

goroutine的使用

在编写 Socket 网络程序时,需要提前准备一个线程池为每一个 Socket 的收发包分配一个线程。开发人员需要在线程数量和 CPU 数量间建立一个对应关系,以保证每个任务能及时地被分配到 CPU 上进行处理,同时避免多个任务频繁地在线程间切换执行而损失效率。

虽然,线程池为逻辑编写者提供了线程分配的抽象机制。但是,如果面对随时随地可能发生的并发和线程处理需求,线程池就不是非常直观和方便了。能否有一种机制:使用者分配足够多的任务,系统能自动帮助使用者把任务分配到 CPU 上,让这些任务尽量并发运作。这种机制在 Go语言中被称为goroutine。

goroutine 是 Go语言中的轻量级线程实现,由 Go 运行时(runtime)管理。Go 程序会智能地将 goroutine 中的任务合理地分配给每个 CPU。

Go 程序从 main 包的 main() 函数开始,在程序启动时,Go 程序就会为 main() 函数创建一个默认的 goroutine。

# 1. 创建协程 goroutine

# 使用普通函数创建 goroutine

Go 程序中使用 go 关键字为一个函数创建一个 goroutine。一个函数可以被创建多个 goroutine,一个 goroutine 必定对应一个函数。

# 格式

为一个普通函数创建 goroutine 的写法如下:

go 函数名( 参数列表 )
1
  • 函数名:要调用的函数名。

  • 参数列表:调用函数需要传入的参数。

使用 go 关键字创建 goroutine 时,被调用函数的返回值会被忽略。如果需要在 goroutine 中返回数据,请使用后面介绍的通道(channel)特性,通过通道把数据从 goroutine 中作为返回值传出。

# 例子

package main

import (
	"fmt"
	"strconv"
	"time"
)

func test() {
	for i := 1; i <= 5; i++ {
		fmt.Println("hello golang" + strconv.Itoa(i))
		time.Sleep(time.Second)
	}
}

func main() { // main主线程
	go test() // go test() 开启协程
	for i := 1; i <= 5; i++ {
		fmt.Println("hello msb" + strconv.Itoa(i))
		time.Sleep(time.Second)
	}
}

/*
hello msb1
hello golang1
hello golang2
hello msb2
hello msb3
hello golang3
hello golang4
hello msb4
hello msb5
hello golang5
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 使用匿名函数创建goroutine

go 关键字后也可以为匿名函数或闭包启动 goroutine。

# 格式

使用匿名函数或闭包创建 goroutine 时,除了将函数定义部分写在 go 的后面之外,还需要加上匿名函数的调用参数,格式如下:

go func( 参数列表 ){
    函数体
}( 调用参数列表 )
1
2
3

其中:

  • 参数列表:函数体内的参数变量列表。

  • 函数体:匿名函数的代码。

  • 调用参数列表:启动 goroutine 时,需要向匿名函数传递的调用参数。

# 例子

package main

import (
	"fmt"
	"strconv"
	"time"
)


func main() { // main主线程
	// 方式二: 使用匿名函数
	go func() {
		for i := 1; i <= 5; i++ {
			fmt.Println("hello golang" + strconv.Itoa(i))
			time.Sleep(time.Second)
		}
	}()

	for i := 1; i <= 5; i++ {
		fmt.Println("hello msb" + strconv.Itoa(i))
		time.Sleep(time.Second)
	}
}

/*
hello msb1
hello golang1
hello golang2
hello msb2
hello msb3
hello golang3
hello golang4
hello msb4
hello msb5
hello golang5
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 2. 启动多个协程

# 创建多个协程使用闭包问题

如果用了匿名函数,匿名函数会把使用的变量放到全局作用域中。所有导致输出的i有问题

package main

import (
	"fmt"
	"time"
)

func main() { // main主线程
	// 创建多个协程(这里创建了5个)
	// 问题点一: 如果用了匿名函数,匿名函数会把使用的变量放到全局作用域中。所有导致输出的i有问题
	for i := 1; i <= 5; i++ {
		go func() {
			fmt.Printf("%d \t", i) // 4       6       6       6       6
		}()
	}

	fmt.Println("--------")

	for i := 1; i <= 5; i++ {
		go func(n int) { // 解决方式常用值传递的方式可解决上面的问题
			fmt.Printf("%d \t", n) //  3       4       6       1       6
		}(i)
	}

	// 主死从随: 主线程退出了,则协程即使还没有执行完毕,也会退出
	time.Sleep(time.Second * 3)
}
/*
--------
5       6       2       6       6       3       4       6       1       6
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 3. 主死从随

  1. 如果主线程退出了,则协程即使还没有执行完毕,也会退出

  2. 当然协程也可以在主线程没有退出前,就自己结束了,比如完成了自己的任务

package main

import (
	"fmt"
	"strconv"
	"time"
)

func main() { // main主线程

	go func() {
		for i := 1; i <= 100; i++ {
			fmt.Println("hello golang" + strconv.Itoa(i))
			time.Sleep(time.Second)
		}
	}()

	for i := 1; i <= 2; i++ {
		fmt.Println("hello msb" + strconv.Itoa(i))
		time.Sleep(time.Second)
	}
}

/*
hello golang1
hello msb1
hello golang2
hello msb2  // 主线程结束 协程未执行完成也结束了
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 4. 使用WaitGroup解决主死从随

# WaitGroup的作用

WaitGroup用于等待一组线程的结束。父线程调用Add方法来设定应等待的线程的数量。每个被等待的线程在结束时应调用Done方法。同时,主线程里可以调用Wait方法阻塞至所有线程结束。---》解决主线程在子协程结束后自动结束

计数为0才会正常执行否则报错 fatal error: all goroutines are asleep - deadlock!

# 主要方法

# 案例

# Add\Done\Wait

  • 一、定义WaitGroup

  • 二、每次创建一个协程调用Add()计数加一

  • 三、协程调用完毕计数减一(内部其实就是用的Add(-1))

  • 四、使用Wait阻塞: 主线程一直在阻塞,什么时候wg减为0了,就停止

package main

import (
	"fmt"
	"sync"
)

var wg sync.WaitGroup // 一、定义WaitGroup

func main() { // main主线程
	for i := 1; i <= 5; i++ {
		wg.Add(1) // 二、每次创建一个协程调用Add()计数加一
		go func(n int) {
			fmt.Println(n)
			wg.Done() // 三、协程调用完毕计数减一(内部其实就是用的Add(-1))
		}(i)
	}

	// 四、使用Wait阻塞: 主线程一直在阻塞,什么时候wg减为0了,就停止
	wg.Wait()
	fmt.Println("主线程执行完毕")

}

/*
5
2
4
1
3
主线程执行完毕
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 如果防止忘记计数器减1操作,结合defer关键字使用:

package main

import (
	"fmt"
	"sync"
)

var wg sync.WaitGroup // 一、定义WaitGroup

func main() { // main主线程
	for i := 1; i <= 5; i++ {
		wg.Add(1) // 二、每次创建一个协程调用Add()计数加一
		go func(n int) {
			defer wg.Done()
			fmt.Println(n)
			//wg.Done() // 三、协程调用完毕计数减一(内部其实就是用的Add(-1))
		}(i)
	}

	// 四、使用Wait阻塞: 主线程一直在阻塞,什么时候wg减为0了,就停止
	wg.Wait()
	fmt.Println("主线程执行完毕")

}

/*
5
2
4
1
3
主线程执行完毕
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 可以最开始在知道协程次数的情况下先Add操作

package main

import (
	"fmt"
	"sync"
)

var wg sync.WaitGroup // 一、定义WaitGroup

func main() { // main主线程
	wg.Add(5)
	for i := 1; i <= 5; i++ {
		//wg.Add(1) // 二、每次创建一个协程调用Add()计数加一
		go func(n int) {
			defer wg.Done()
			fmt.Println(n)
			//wg.Done() // 三、协程调用完毕计数减一(内部其实就是用的Add(-1))
		}(i)
	}

	// 四、使用Wait阻塞: 主线程一直在阻塞,什么时候wg减为0了,就停止
	wg.Wait()
	fmt.Println("主线程执行完毕")

}

/*
5
2
4
1
3
主线程执行完毕
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 5. 锁(多个协程操作同一数据)

Go语言互斥锁(sync.Mutex)和读写互斥锁(sync.RWMutex) (opens new window)

# 当多个协程操纵同一数据会出现问题

# 案例

package main

import (
	"fmt"
	"sync"
)

// 定义一个变量:
var totalNum int
var wg1 sync.WaitGroup //只定义无需赋值
func add() {
	defer wg1.Done()
	for i := 0; i < 100000; i++ {
		totalNum = totalNum + 1
	}
}
func sub() {
	defer wg1.Done()
	for i := 0; i < 100000; i++ {
		totalNum = totalNum - 1
	}
}
func main() {
	wg1.Add(2)
	//启动协程
	go add()
	go sub()
	wg1.Wait()
	fmt.Println(totalNum)  // 当多个协程出现数据共享是就会出错
}

/*
19751
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 问题出现的原因:(图解为其中一种可能性)

# 解决问题

有一个机制:确保:一个协程在执行逻辑的时候另外的协程不执行 ----》锁的机制---》加入互斥锁

# Mutex 互斥锁

其中Mutex为互斥锁,Lock()加锁,Unlock()解锁,使用Lock()加锁后,便不能再次对其进行加锁,直到利用Unlock()解锁对其解锁后,才能再次加锁.适用于读写不确定场景,即读写次数没有明显的区别 ----性能、效率相对来说比较低

使用步骤:

  • 定义互斥锁 sync.Mutex

  • 合适位置加锁 Lock

  • 合适位置释放锁(释放锁的位置不正确会导致死锁)Unlock

package main

import (
	"fmt"
	"sync"
)

// 定义一个变量:
var num int
var wg2 sync.WaitGroup //只定义无需赋值
// 一、定义互斥锁
var lock sync.Mutex // 加入互斥锁

func add1() {
	defer wg2.Done()
	for i := 0; i < 100000; i++ {
		lock.Lock() // 二、合适位置加锁
		num = num + 1
		lock.Unlock() // 三、合适位置释放锁(释放锁的位置不正确会导致死锁)
	}
}
func sub1() {
	defer wg2.Done()
	for i := 0; i < 100000; i++ {
		lock.Lock() // 加锁
		num = num - 1
		lock.Unlock() // 释放锁
	}
}
func main() {
	wg2.Add(2)
	//启动协程
	go add1()
	go sub1()
	wg2.Wait()
	fmt.Println(num) // 当多个协程出现数据共享是就会出错
}

/*
0
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# RWMutex 读写锁

RWMutex是一个读写锁,其经常用于读次数远远多于写次数的场景. ---在读的时候,数据之间不产生影响, 写和读之间才会产生影响

package main

import (
	"fmt"
	"sync"
	"time"
)

var wg3 sync.WaitGroup //只定义无需赋值
// 加入读写锁:
var lock3 sync.RWMutex

func read() {
	defer wg3.Done()
	lock3.RLock() //如果只是读数据,那么这个锁不产生影响,但是读写同时发生的时候,就会有影响
	fmt.Println("开始读取数据")
	time.Sleep(time.Second)
	fmt.Println("读取数据成功")
	lock3.RUnlock()
}
func write() {
	defer wg3.Done()
	lock3.Lock()
	fmt.Println("开始修改数据")
	//time.Sleep(time.Second * 10)
	fmt.Println("修改数据成功")
	lock3.Unlock()
}
func main() {
	wg3.Add(6)
	//启动协程 ---> 场合:读多写少
	for i := 0; i < 2; i++ {
		go read()
	}
	go write()
	wg3.Wait()
}
/*
开始修改数据
修改数据成功
开始读取数据
开始读取数据
读取数据成功
读取数据成功
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

# 6. 死锁、活锁和饥饿概述

Go语言死锁、活锁和饥饿概述 (opens new window)

编辑 (opens new window)
协程入门案例
channel通道(goroutine之间通信的管道)

← 协程入门案例 channel通道(goroutine之间通信的管道)→

最近更新
01
配置yun源
05-24
02
linux-配置python虚拟环境
05-24
03
linux文件目录管理
05-24
更多文章>
Theme by Vdoing | Copyright © 2023-2023 yizhang | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式