0x01 协程
什么是进程、线程、协程????????
概念
进程概念:
1
|
进程(Process)是操作系统中运⾏中的程序,它是程序执⾏的⼀个实例。当你运⾏⼀个应⽤程序时,⽐如打开浏览器、启动游戏,操作系统会为这个程序创建⼀个进程。进程包含了程序执⾏的所有必要信息,例如代码、数据、资源和内存。
|
线程概念:
1
|
线程是比进程更轻量的执⾏单元,是进程的⼀部分。⼀个进程可以包含多个线程,每个线程可以独⽴执⾏任务,但它们共享相同的内存空间和资源。
|
举例:
1
2
3
4
5
|
进程就像⼀个厨房,⾥⾯有完整的设备、材料和⻝谱,允许你做饭。⽽线程则是厨房⾥的厨师。⼀个厨房可以有多个厨师(线程),每个厨师可以同时进⾏不同的任务(切菜、炒菜、煮汤等),并且他们共享同样的厨房资源,⽐如锅、灶台和⻝材。
单线程
如果是单线程的状态下,⼀个厨师(线程)就需要处理⼀整个厨房的事情(进程),有⼀个⼤菜,厨师只能⼀个⼀个来弄,效率低下
多线程
⼀个厨房有多个厨师,他们的效率就会更⾼,每个厨师可以同时进⾏不同的任务,并且因为他们是在⼀个厨房⾥(进程),他们可以直接沟通,(共享的数据结构进⾏通信),⽽进程是独⽴的厨房,两个厨房(进程)之间的通信,相对于来说要复杂很多。
|
协程概念:
1
2
3
|
协程 Coroutines 是⼀种⽐线程更加轻量级的微线程。⼀个进程可以拥有多个线程,⼀个线程也可以拥有多个协程,因此协程⼜称微线程和纤程。
可以粗略的把协程理解成⼦程序调⽤,每个⼦程序都可以在⼀个单独的协程内执⾏。
Go协程⽐传统的操作系统线程更轻量级。它们的启动和销毁开销⾮常⼩,并且允许并发地执⾏代码,这意味着多个协程可以在同⼀时间段内运⾏,协程相当于是⼀个⼦程序,他属于⽤户态,也就是由语⾔来操作,线程与进程,都是属于内核态,由操作系统来操作,⽆法⼲预,但是协程,我们可以控制它。
|
调度器
线程调度
1
|
线程调度通常由操作系统的内核负责,是⼀种抢占式调度机制。抢占式调度意味着操作系统会强制地决定何时停⽌⼀个线程的执⾏,并切换到另⼀个线程。这种调度机制称为抢占式多任务,由于抢占式多任务,操作系统会根据线程的优先级、状态(如阻塞、就绪、运⾏)以及可⽤资源来决定哪个线程获得CPU时间⽚。每个线程在操作系统中都会分配⼀个时间⽚,当时间⽚⽤完时,操作系统会切换到另⼀个线程,在⼀个CPU时间⽚⽤完时,需要进⾏上下⽂切换,操作系统在线程之间切换时,需要保存和恢复线程的上下⽂(包括寄存器状态、程序计数器、内存⻚表等)。这种上下⽂切换发⽣在内核态和⽤户态之间,开销较⼤。但由于抢占式调度,操作系统会随时中断正在运⾏的线程,以便处理其他线程的任务,线程本身对调度过程没有控制权。
|
Go Goroutines 调度器
1
2
|
协程调度通常是由编程语⾔的运⾏时(⽤户态)来负责,在go中,由Goroutines负责,Goroutines 的调度由 Go 的运⾏时系统(runtime)管理,通过协作式调度和抢占式调度结
合的⽅式,来保证 Goroutines 在多核 CPU 上⾼效执⾏。
|
Go调度的优势
轻量性:
1
|
相⽐于传统的操作系统线程,Goroutines 是⾮常轻量的。操作系统线程通常需要消耗⼤量内存(每个线程的栈⼤⼩默认⼏乎占 1-2 MB),⽽ Goroutine 的初始栈⼤⼩只有 2 KB,并且可以按需动态增⻓,这使得在同样的资源下,Go 程序可以轻松创建成千上万的 Goroutines ⽽不会占⽤过多系统资源。
|
⾃动调度:
1
|
Goroutines 不需要程序员⼿动管理。Go 的调度器会根据负载和系统资源⾃动进⾏调度,使得程序员能够专注于业务逻辑,⽽⽆需担⼼如何调度 Goroutines。
|
多核优化:
1
|
Goroutines 的调度器充分利⽤了多核 CPU。Go 的运⾏时调度器通过逻辑处理器 P 和操作系统线程 M 的绑定机制,能够在多核 CPU 上⾃动并⾏执⾏ Goroutines,最⼤化性能。
|
协程的使用
协程创建
1
2
3
|
Golang中的并发是函数相互独⽴运⾏的能⼒,
创建协程在任务函数前⾯加上go关键字:
go test()
|
示例代码:
先演示一下正常情况下的方法使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package main
import "fmt"
func show(msg string) {
for i := 0; i < 5; i++ {
fmt.Println(msg)
}}
func main() {
show("go")
show("java")
}
输出:正常打印5次go和5次java
go
go
go
go
go
java
java
java
java
java
|
创建协程的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package main
import (
"fmt"
)
func show(msg string) {
for i := 0; i < 5; i++ {
fmt.Println(msg)
//time.Sleep(100 * time.Millisecond) //每次循环,睡100毫秒
}
}
func main() {
go show("go") //协程
show("java") //主线程
}
输出:
java
java
java
java
java
一般会挤进来一次go,但次数很少
|
0x02 通道
概念
1
|
Go 提供了⼀种称为通道的机制,⽤于在 goroutine 之间共享数据。当 goroutine 执⾏并发活动时,需要在goroutine 之间共享资源或数据,通道充当 goroutine 之间的管道(管道)并提供⼀种机制来保证同步交换。
|
1
|
Channels 需要在声明通道时指定数据类型。我们可以共享内置、命名、结构和引⽤类型的值和指针。数据在通道上传递:在任何给定时间只有⼀个 goroutine 可以访问数据项:因此按照设计不会发⽣数据竞争。根据数据交换的⾏为,有两种类型的通道:⽆缓冲通道和缓冲通道。⽆缓冲通道⽤于执⾏goroutine 之间的同步通信,⽽缓冲通道⽤于执⾏异步通信。⽆缓冲通道保证在发送和接收发⽣的瞬间执⾏两个 goroutine 之间的交换。缓冲通道没有这样的保证。
|
1
|
协程之间⽆法直接通信,但可以使⽤通道来使协程之间互相通信通道分为有缓冲和⽆缓冲,有缓冲,容量设置后,可以异步读取数据,不会堵塞,⽆缓冲的话,放⼀个数据就必须取出来,否则就会阻塞。
|
创建⽆缓冲和缓冲通道
1
2
3
|
make(chan int) //整型⽆缓冲通道,写入和读取同步进行,容易死锁,通常用于同步执行
make(chan int ,10) //整型有缓冲通道,通常用于异步执行。
|
使⽤内置函数make创建⽆缓冲、缓冲通道,make第⼀个参数需要是关键字chan,也就是通道Channels的缩写,然后是通道交换的数据类型
使用
管道在左边,则说明是往管道中传输数据,管道在右边,则说明要取出数据
1
2
3
4
5
|
ch := make(chan int,5) //整型缓冲通道
ch <- 5 //通过管道发送值
data := <-ch
|
⽆缓冲区实例
阻塞+主协程结束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package main
import (
"fmt"
)
func main() {
ch := make(chan int) //创建一个int类型的管道
go func() { //创建一个协程
fmt.Println("准备向⽆缓冲通道发送数据")
ch <- 111 //往管道里添加一个111数据
fmt.Println("向⽆缓冲通道发送数据完毕")
}() //time.Sleep(1 * time.Second)
fmt.Println("准备从⽆缓冲通道接收数据")
data := <-ch
fmt.Println("从⽆缓冲通道接收到数据:", data)
}
输出:
准备从⽆缓冲通道接收数据
准备向⽆缓冲通道发送数据
向⽆缓冲通道发送数据完毕
从⽆缓冲通道接收到数据: 111
|
缓冲区实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package main
import (
"fmt"
"time")
func main() {
ch := make(chan int, 1)
go func() { fmt.Println("准备向⽆缓冲通道发送数据")
ch <- 111
fmt.Println("向⽆缓冲通道发送数据完毕")
}() time.Sleep(10 * time.Second)
fmt.Println("准备从⽆缓冲通道接收数据")
data := <-ch
fmt.Println("从⽆缓冲通道接收到数据:\n", data)
}
输出:
准备向⽆缓冲通道发送数据
向⽆缓冲通道发送数据完毕
准备从⽆缓冲通道接收数据
从⽆缓冲通道接收到数据:
111
|
实例1
这个实例代码创建了⼀个通道 ch,在 main 函数中启动⼀个 goroutine 执⾏ Ion 函数,Ion 函数⽣成⼀个随机数并发送到通道,main 函数等待接收通道中的数据并打印,最后关闭通道。
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
|
package main
import (
"fmt"
"math/rand"
"time"
)
var ch = make(chan int)
func Ion() {
rand.Seed(time.Now().UnixNano())
intn := rand.Intn(100)
fmt.Println("随机数为:", intn)
time.Sleep(5 * time.Second)
ch <- intn
}
func main() {
defer close(ch) //main关闭后,关闭通道
go Ion()
fmt.Println("Wait...")
data := <-ch
fmt.Println("值为:", data)
fmt.Println("Done")
}
输出:
Wait...
随机数为: 76
值为: 76
Done
|