Go语言协程 | 青训营笔记

75 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 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)
}