二丫讲梵 二丫讲梵
首页
  • 最佳实践
  • 迎刃而解
  • Nginx
  • Php
  • Zabbix
  • AWS
  • Prometheus
  • Grafana
  • CentOS
  • Systemd
  • Docker
  • Rancher
  • Ansible
  • Ldap
  • Gitlab
  • GitHub
  • Etcd
  • Consul
  • RabbitMQ
  • Kafka
  • MySql
  • MongoDB
  • OpenVPN
  • KVM
  • VMware
  • Other
  • ELK
  • K8S
  • LLM
  • Nexus
  • Jenkins
  • 随写编年
  • 家人物语
  • 追忆青春
  • 父亲的朋友圈
  • 电影音乐
  • 效率工具
  • 博客相关
  • Shell
  • 前端实践
  • Vue学习笔记
  • Golang学习笔记
  • Golang编程技巧
  • 学习周刊
  • Obsidian插件周刊
关于
友链
  • 本站索引

    • 分类
    • 标签
    • 归档
  • 本站页面

    • 导航
    • 打赏
  • 我的工具

    • 备忘录清单 (opens new window)
    • json2go (opens new window)
    • gopher (opens new window)
    • 微信MD编辑 (opens new window)
    • 国内镜像 (opens new window)
    • 出口IP查询 (opens new window)
    • 代码高亮工具 (opens new window)
  • 外站页面

    • 开往 (opens new window)
    • ldapdoc (opens new window)
    • HowToStartOpenSource (opens new window)
    • vdoing-template (opens new window)
GitHub (opens new window)

二丫讲梵

行者常至,为者常成
首页
  • 最佳实践
  • 迎刃而解
  • Nginx
  • Php
  • Zabbix
  • AWS
  • Prometheus
  • Grafana
  • CentOS
  • Systemd
  • Docker
  • Rancher
  • Ansible
  • Ldap
  • Gitlab
  • GitHub
  • Etcd
  • Consul
  • RabbitMQ
  • Kafka
  • MySql
  • MongoDB
  • OpenVPN
  • KVM
  • VMware
  • Other
  • ELK
  • K8S
  • LLM
  • Nexus
  • Jenkins
  • 随写编年
  • 家人物语
  • 追忆青春
  • 父亲的朋友圈
  • 电影音乐
  • 效率工具
  • 博客相关
  • Shell
  • 前端实践
  • Vue学习笔记
  • Golang学习笔记
  • Golang编程技巧
  • 学习周刊
  • Obsidian插件周刊
关于
友链
  • 本站索引

    • 分类
    • 标签
    • 归档
  • 本站页面

    • 导航
    • 打赏
  • 我的工具

    • 备忘录清单 (opens new window)
    • json2go (opens new window)
    • gopher (opens new window)
    • 微信MD编辑 (opens new window)
    • 国内镜像 (opens new window)
    • 出口IP查询 (opens new window)
    • 代码高亮工具 (opens new window)
  • 外站页面

    • 开往 (opens new window)
    • ldapdoc (opens new window)
    • HowToStartOpenSource (opens new window)
    • vdoing-template (opens new window)
GitHub (opens new window)
  • Shell编程

  • Go编程笔记

  • 前端编程笔记

  • Go学习笔记

    • 基础部分

      • 基础知识
      • 数据类型
      • fmt的格式化输入输出
      • 运算符
      • 流程控制
      • 认识函数
      • 包的操作
      • 项目工程-在同级目录
      • 指针(pointer)
      • 数组(array)
      • 切片(slice)
      • 字典(map)
      • 结构体(struct)
      • 匿名组合
      • 方法
      • 接口
      • error接口
      • panic
      • recover
      • 字符串操作
      • 字符串转换
      • 正则表达式
      • JSON处理
      • 文件的操作
      • goroutine
        • 1,前言
          • 1,并行和并发。
          • 2,Go 语言并发的优势。
        • 2,goroutine
          • 1,goroutine 是什么
          • 2,创建 goroutine
          • 3,主协程退出,子协程也会退出。
          • 4,runtime 包。
          • 1,Gosched
          • 2,Goexit。
          • 3,GOMAXPROCS
          • 4,sync.WaitGroup
          • 5,多协程导致资源竞争问题。
      • channel
      • select
      • sync-lock锁
      • 网络概述
      • Socket编程
      • 发文件与聊天室
      • http编程
      • 爬虫实战
      • 单元测试了解
    • web框架

    • orm框架

  • Vue-21年学习笔记

  • Vue-22年重学笔记

  • 编程世界
  • Go学习笔记
  • 基础部分
二丫讲梵
2021-07-10
目录

goroutine

文章发布较早,内容可能过时,阅读注意甄别。

# 1,前言

# 1,并行和并发。

  • 并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。

65c600bcaf658755

  • 并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。

33c7114d42fb5432

  • 并行是两个队列同时使用两台咖啡机
  • 并发是两个队列交替使用一台咖啡机

ee2eedc9ebae698b

你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。 你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。 你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。

并发的关键是你有处理多个任务的能力,不一定要同时。 并行的关键是你有同时处理多个任务的能力。 所以我认为它们最关键的点就是:是否是**『同时』**。

http://t.cn/Ai94Gf7N

# 2,Go 语言并发的优势。

有人把 Go 比作 21 世纪的 C 语言,第一是因为 Go 语言设计简单,第二,21 世纪最重要的就是并行程序设计,而Go 从语言层面就支持了并发。同时,并发程序的内存管理有时候是非常复杂的,而 Go 语言提供了自动垃圾回收机制。

Go 语言为并发编程而内置的上层 API 基于 CSP(communicating sequential processes, 顺序通信进程)模型。这就意味着显式锁都是可以避免的,因为 Go 语言通过线程安全的通道发送和接受数据以实现同步,这大大地简化了并发程序的编写。

一般情况下,一个普通的桌面计算机跑十几二十个线程就有点负载过大了,但是同样这台机器却可以轻松地让成百上千甚至过万个 goroutine 进行资源竞争。

# 2,goroutine

# 1,goroutine 是什么

goroutine 是 Go 并行设计的核心。goroutine 说到底其实就是协程,但是它比线程更小,十几个 goroutine 可能体现在底层就是五六个线程,Go 语言内部帮你实现了这些 goroutine 之间的内存共享。执行 goroutine 只需极少的栈内存(大概是 4~5KB),当然会根据相应的数据伸缩。也正因为如此,可同时运行成千上万个并发任务。goroutine 比 thread 更易用、更高效、更轻便。

# 2,创建 goroutine

只需在函数调⽤语句前添加 go 关键字,就可创建并发执⾏单元。开发⼈员无需了解任何执⾏细节,调度器会自动将其安排到合适的系统线程上执行。

在并发编程里,我们通常想讲一个过程切分成几块,然后让每个 goroutine 各自负责一块工作。当一个程序启动时,其主函数即在一个单独的 goroutine 中运行,我们叫它 main goroutine。新的 goroutine 会用 go 语句来创建。

示例:

package main

import (
	"fmt"
	"time"
)

func Newtask() {
	for {
		fmt.Println("this is a Newtask")
		time.Sleep(time.Second) //表示延时1s
	}
}

func main() {

	go Newtask() //新建一个协程,一个任务

	for {
		fmt.Println("this is a main goroutine")
		time.Sleep(time.Second) //表示延时1s
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 3,主协程退出,子协程也会退出。

package main

import (
	"fmt"
	"time"
)

//如果主协程退出了,其他子协程也会跟着退出
func main() {
	go func() {
		i := 0
		for {
			i++
			fmt.Println("子协程 i = ", i)
			time.Sleep(time.Second)
		}
	}() //别忘了()进行调用

	i := 0
	for {
		i++
		fmt.Println("main i = ", i)
		time.Sleep(time.Second)

		if i == 2 {
			break
		}
	}
}
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,runtime 包。

# 1,Gosched

runtime.Gosched() 用于让出 CPU 时间片,让出当前 goroutine 的执行权限,调度器安排其他等待的任务运行,并在下次某个时候从该位置恢复执行。

这就像跑接力赛,A 跑了一会碰到代码 runtime.Gosched() 就把接力棒交给 B 了,A 歇着了,B 继续跑。

示例代码:

package main

import (
	"fmt"
	"runtime"
)

func main() {
	go func() {
		for i := 0; i < 5; i++ {
			fmt.Println("go")
		}
	}()

	for i := 0; i < 2; i++ {
		//表示让出时间片,别的协程执行完成之后,再来执行主协程
		runtime.Gosched()
		fmt.Println("hello")
	}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

输出结果:

$ go run 4_Gosched的使用.go
go
go
go
go
go
hello
hello
1
2
3
4
5
6
7
8

如果不加runtime.Gosched()函数,那么结果会是如下:

$ go run 4_Gosched的使用.go
go
hello
go
hello
1
2
3
4
5

# 2,Goexit。

调用 runtime.Goexit() 将立即终止当前 goroutine 执⾏,调度器确保所有已注册 defer 延迟调用被执行。

package main

import (
	"fmt"
	"runtime"
)

func test() {
	defer fmt.Println("ccc")
	//return //表示终止次函数
	runtime.Goexit() //表示终止所在的协程

	fmt.Println("ddd")
}

func main() {
	//创建新的协程
	go func() {
		fmt.Println("aaa")
		//调用别的函数
		test()
		fmt.Println("bbb")
	}()

	//特地写一个死循环,目的不让主协程结束
	for {

	}
}
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

# 3,GOMAXPROCS

调用 runtime.GOMAXPROCS() 用来设置可以并行计算的 CPU 核数的最大值,并返回之前的值。

package main

import (
	"fmt"
	"runtime"
)

func main() {
	n := runtime.GOMAXPROCS(4) //指定以单核运算
	fmt.Println("n = ", n)

	for {
		go fmt.Print(1)

		fmt.Print(0)
	}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

在第一次执行(runtime.GOMAXPROCS(1))时,最多同时只能有一个 goroutine 被执行。所以 会打印很多 1。过了一段时间后,GO 调度器会将其置为休眠,并唤醒另一个 goroutine,这时候就开始打印很多 0 了,在打印的时候,goroutine 是被调度到操作系统线程上的。

在第二次执行(runtime.GOMAXPROCS(2))时,我们使用了两个 CPU,所以两个 goroutine 可以一起被执行,以同样的频率交替打印 0 和 1。

# 4,sync.WaitGroup

还有一种方式是利用 sync 包内的一个方法来实现:

package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

var wg sync.WaitGroup

func f1(i int) {
	defer wg.Done()		// goroutine结束就登记-1
	time.Sleep(time.Second * time.Duration(rand.Intn(3)))
	fmt.Println(i)
}

func main() {
	for i := 0; i < 10; i++ {
		wg.Add(1) //启动一个goroutine就登记+1
		go f1(i)
	}
	wg.Wait()		//等待所有等级的goroutine都结束
}

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

# 5,多协程导致资源竞争问题。

package main

import (
	"fmt"
	"time"
)

//定义一个打印机,参数为字符串,按每个字符串打印
//打印机属于公共资源
func Printer(str string) {
	for _, data := range str {
		fmt.Printf("%c", data)
		time.Sleep(time.Second)
	}
	fmt.Printf("\n")
}

func person1() {
	Printer("hello")
}
func person2() {
	Printer("world")
}

func main() {
	//新建2个协程,代表2个人,2个人同时使用打印机
	go person1()
	go person2()
	//特地不让主协程结束,创建一个死循环
	for {

	}
}
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

输出如下结果:

$ go run 7_多任务导致资源竞争问题.go
whoerllldo

exit status 2
1
2
3
4
微信 支付宝
#go
上次更新: 2024/07/04, 22:40:37
文件的操作
channel

← 文件的操作 channel→

最近更新
01
学习周刊-总第213期-2025年第22周
05-29
02
学习周刊-总第212期-2025年第21周
05-22
03
从赵心童世锦赛夺冠聊聊我的斯诺克情缘
05-16
更多文章>
Theme by Vdoing | Copyright © 2017-2025 | 点击查看十年之约 | 浙ICP备18057030号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式