Goroutine初识
# 一、Go
语言的协程 --- Goroutine
进程(Process)
、线程(Thread)
、协程(Coroutine,也叫轻量线程)
进程 进程是一个程序在一个数据集中的一次动态执行过程,可以简单理解为"正在执行的程序",它是CPU
资源分配和调度的独立单位.进程一般由程序、数据集、进程控制块三部分组成.在编写程序用来描述要完成哪些功能以及如何完成;数据集则是程序执行过程中所需要使用的资源;进程空间块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志.进程吧局限是创建、撤销和切换的开销比较大
线程 线程是在进程之后发展出来的概念.线程也叫轻量级进程,它是一个基本的CPU
执行单元,也是程序执行过程中的最小单元,由线程ID
、程序计数器、寄存器集合和堆栈共同组成.一个进程可以包含多个线程.线程的优点是减小了程序并发执行时的开销,提高了操作系统的并发性能,缺点是线程没有自己的系统资源,只拥有在运行时必不可少的资源,但同一进程的各线程可以共享进程所拥有的系统资源,如果把进程比作一个车间,那么线程就好比是车间里面的工人.不过对于某些独占性资源存在锁机制,处理不当可能会产生"死锁"
协程 协程是一种用户态的轻量级线程,又称微线程,英文名字Coroutine
,协程的调用完全由用户控制.人们通常将协程和子程序(函数)比较着好理解.子程序总是一个入口,一次返回,一旦退出即完成了子程序的执行.
比起传统的系统线程和进程相比,协程的最大优势在于其"轻量级",可以轻松创建上百万个而不会导致系统资源衰竭,而线程和进程通常最多也不能超过1万的.这也是协程也叫轻量级线程的原因.
协程与多线程相比,其优势体现在: 协程的执行效率极高.因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显.
# 二、Goroutine
# 什么是Goroutine
go
中使用Goroutine
来实现并发concurrently
Goroutine
是Go
语言特有的名词.区别与进程Process
,线程Thread
,协程Coroutine
,因为Go
语言的创造者们觉得和他们是有所区别的,所以专门创造了Goroutine
Goroutine
是与其他函数或方法同时运行的函数或方法.Goroutine
可以被认为是轻量级的线程.与线程相比,创建Goroutine
的成本很小,它就是一段代码,一个函数入口.以及在堆上为其分配的一个堆栈(初始大小为4K
,会随着程序的执行自动增长删除).因此它非常廉价,Go
应用程序可以并发运行数千个Goroutine
Goroutine
在线程上的优势.
1.与线程相比,Goroutine
非常便宜,它们只是堆栈大小的几个kb
,堆栈可以根据应用程序的需要增长和收缩,而在线程的情况下,堆栈大小必须指定并且是固定的.
2.Goroutine
被多路复用到较少的OS
线程.在一个程序中可能只有一个线程与数千个Goroutines
.如果线程中的任何Goroutine
都表示等待用户输入,则会创建另一个OS
线程,剩下的Goroutines
被转移到新的OS
线程.所有这些都由运行时进行处理,作为程序员从这些复杂的细节中抽象出来,并得到了一个与并发工作相关的干净的API
3.当使用Goroutine
访问共享内存时,通过设计的通道可以防止竞态条件发生.通道可以被认为是Goroutines
通信的管道.
# 主goroutine
封装main
函数的goroutine
称为主goroutine
主goroutine
所做的事情并不是执行main
函数那么简单.它首次要做的是: 设定每一个goroutine
所能申请的栈空间的最大尺寸.在32
为计算机系统中此最大尺寸为250M
,而在64
的计算机系统中此尺寸为1GB
.如果有某个goroutine
的栈空间尺寸大于这个限制,那么运行时系统就会引发一个栈溢出(stack overflow)
的运行时恐慌.随后,这个go
程序的运行也会终止.
此后,主goroutine
会进行一系列的初始化工作,涉及的工作内容如下:
1.创建一个特殊的
defer
语句,用于在主goroutine
退出时必要的善后处理.因为主goroutine
也可能非正常的结束2.启动专用与在后台清理内存垃圾的
goroutine
,并设置GC
可用的标识3.执行
main
包中的init
函数4.执行
main
函数执行完
main
函数后,它还会检查主goroutine
是否引发运行时恐慌,并进行必要的处理.最后主goroutine
会结束自己以及当前进程的运行.
# 如何使用Goroutines
在函数或方法调用前面加上关键字go
,这将会同时运行一个新的Goroutine
示例代码:
package main
import (
"fmt"
)
func hello(){
fmt.Println("Hello world goroutine")
}
func main() {
go hello()
fmt.Println("main function")
}
2
3
4
5
6
7
8
9
10
11
12
13
14
运行结果:
- 可能会只输出"main function"
Goroutine
规则1.当新的
Goroutine
开始时,Goroutine
调用立即返回.与函数不同,go
不等待Goroutine
执行结束.当Goroutine
调用,并且Goroutine
的任何返回值被忽略之后,go
立即执行到下一行代码.2.
main
的Goroutine
应该为其他的Goroutine
执行.如果main
的Goroutine
终止流量,程序将被终止,而其他Gortouine
将不会运行.
修改代码:
package main
import (
"fmt"
"time"
)
func hello() {
fmt.Println("Hello world goroutine")
}
func main() {
go hello()
time.Sleep(1 * time.Second)
fmt.Println("main function")
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
运行结果:
在上面的程序中,已经调用了时间包的Sleep
方法,它会在执行过程中睡觉.在这种情况下,main
的goroutine
被用来睡觉1秒.现在调用go hello()
有足够的时间在main Groutine
终止之前执行.这个程序首先打印Hello world goroutine
,等待1秒,然后打印main
函数.
# 启动多个Goroutines
示例代码
package main
import (
"fmt"
"time"
)
func numbers() {
for i := 1; i <= 5; i++ {
time.Sleep(250 * time.Millisecond)
fmt.Printf("%d ", i)
}
}
func alphabets() {
for i := 'a'; i <= 'e'; i++ {
time.Sleep(400 * time.Millisecond)
fmt.Printf("%c ", i)
}
}
func main() {
go numbers()
go alphabets()
time.Sleep(3000 * time.Millisecond)
fmt.Println("main terminated")
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
运行结果:
时间轴分析
- 01
- AWS NAT-NetWork-Firwalld配置(一)04-09
- 02
- AWS NAT-NetWork-Firwalld配置(二)04-09
- 03
- kubernetes部署minio对象存储01-18