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

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

    • 导航
    • 打赏
  • 我的工具

    • 备忘录清单 (opens new window)
    • 网站状态 (opens new window)
    • json2go (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
  • Prometheus
  • Grafana
  • CentOS
  • Systemd
  • Docker
  • Rancker
  • Ansible
  • Ldap
  • Gitlab
  • GitHub
  • Etcd
  • Consul
  • RabbitMQ
  • Kafka
  • MySql
  • MongoDB
  • OpenVPN
  • KVM
  • VMware
  • Other
  • ELK
  • K8S
  • Nexus
  • Jenkins
  • 随写编年
  • 家人物语
  • 追忆青春
  • 父亲的朋友圈
  • 电影音乐
  • 效率工具
  • 博客相关
  • Shell
  • 前端实践
  • Vue学习笔记
  • Golang学习笔记
  • Golang编程技巧
  • 学习周刊
  • Obsidian插件周刊
关于
友链
推广
  • 本站索引

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

    • 导航
    • 打赏
  • 我的工具

    • 备忘录清单 (opens new window)
    • 网站状态 (opens new window)
    • json2go (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
上次更新: 2022/08/19, 09:49:20

← 文件的操作 channel→

最近更新
01
极客时间课程推荐
02-02
02
Vuepress配置rss订阅功能
02-01
03
Vuepress添加首页轮播图与打赏按钮的配置
02-01
更多文章>
Theme by Vdoing | Copyright © 2017-2023 | 点击查看十年之约 | 浙ICP备18057030号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式