这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
进程、线程、协程
进程:
进程是应用程序的启动实例,是系统进行资源分配和调度的基本单位,每个进程都有独立的内存空间,不同进程通过进程间的通信方式来通信。
线程:
线程从属于进程,是进程中的一个实体,线程是CPU调度的基本单位,一个线程由线程ID、当前指令指针、寄存器集合和堆栈组成。
线程不拥有自己的系统资源,它与同属于同一进程的其他线程共享进程所拥有的全部资源,多个线程之间通过共享内存等线程间的通信方式来通信,线程拥有自己独立的栈和共享的堆。
协程:
协程可以理解为轻量级线程,一个线程可以拥有多个协程,与线程相比,协程不受操作系统调度,协程调度器按照调度策略把协程调度到线程中执行,协程调度器由应用程序的runtime包提供,用户使用go关键字即可创建协程,这也就是GO在语言层面直接支持协程的含义。
协程的优点:
1、部署简单 2、并发性好 3、良好的语言设计 4、执行性能好
协程数据结构:
type g struct {
stack stack // 协程栈
...
sched gobuf
...
param unsafe.Pointer
atomicstatus uint32 // 协程的状态
...
goid int64 // 协程的id
...
}
type stack struct {
lo uintptr // 协程栈的低地址
hi uintptr // 协程栈的高地址
}
type gobuf struct {
sp uintptr // 栈指针,现在运行到哪个方法
pc uintptr // 程序计数器,现在运行到哪个方法的哪一行
g guintptr
ctxt unsafe.Pointer
ret uintptr
lr uintptr
bp uintptr // for framepointer-enabled architectures
}
GO语言协程池实现:
package main
import (
"fmt"
"time"
)
// Pool 定义一个协程池
type Pool struct {
work chan func() // 任务
sem chan struct{} // 数量
}
// New 用于创建协程池对象
func New(size int) *Pool {
return &Pool{
work: make(chan func()),
sem: make(chan struct{}, size),
}
}
// worker 用于执行协程任务
func (p *Pool) worker(task func()) {
// 如果某个协程发生了异常, 则此时协程池中就少了一个协程。因此数量上需要移除一个协程。
defer func() {
<-p.sem
}()
for {
task()
task = <-p.work
}
}
// NewTask 协程中增加任务
func (p *Pool) NewTask(task func()) {
select {
case p.work <- task:
case p.sem <- struct{}{}:
go p.worker(task)
}
}
func task() {
time.Sleep(2 * time.Second)
fmt.Println(time.Now())
}
func main() {
pool := New(3)
for i := 1; i <= 6; i++ {
pool.NewTask(task)
}
// 保证所有的协程都执行完毕
time.Sleep(6 * time.Second)
}