golang笔记10--go语言并发编程模块 goroutine

golang笔记10--go语言并发编程模块 goroutine

  • 1 介绍
  • 2 Goroutine
    • 2.1 goroutine
    • 2.2 go语言的调度器
  • 3 注意事项
  • 4 说明

1 介绍

本文继上文 golang笔记09–go语言测试与性能调优, 进一步了解 go语言的并发编程模块 --goroutine,以及相应注意事项。
具体包括 : goroutine 和 go语言的调度器 等内容。

2 Goroutine

2.1 goroutine

主流的并发模型包括:多进程、多线程、基于回调的非阻塞|异步IO、协程 等方式,其优缺点如下:

并发模型 基本原理 优点 缺点
多进程 操作系统层面进行并发的基本模式 简单、进程见互不影响 系统开销大
多线程 也属于操作系统层面的并发模型,但其比进程效率高 开销低,可提高 cpu 内存利用率 和 执行效率 线程会占用一定资源,使用过多会降低程序性能;程序设计比多进程复杂
非阻塞|异步IO 通过事件驱动的方式进行IO,CPU执行指令后不需要立即返回,过段时间执行完成后通知cpu继续处理相关任务 能够降低线程使用两,适用于IO密集操作 编程相对复杂
协程 本质是用户态的线程(轻量级线程),不需要操作系统进行抢占式调度,属于编译器|解释器|虚拟机层面的多任务,多个协程可以在一个或多个线程上运行 开销极小能提高并发性,编程简单结构清晰 需要特定语言的支持,若不支持则小自行实现调度器

go 语言原生支持轻量级线程,即 goroutine,它由go运行时管理。 Go语言从初始化main package 并执行main() 函数开始,当mai()函数返回时程序退出,且程序不等待其它 goroutine(非主goroutine) 结束。

以下案例中通过1000 个 goroutine 输出当前时间 + hello 信息,为了避免主 goroutine 退出,当前使用较粗暴的方式直接 sleep 一分钟。

vim 10.1.go
package main

import (
	"fmt"
	"runtime"
	"time"
)

func main() {
	/*
		for i := 0; i < 1000; i++ {
			go func(i int) {
				for {
					fmt.Printf("%s hello,i'm from goroutine %d\n", time.Now().String(), i)
				}
			}(i)
		}
		time.Sleep(time.Minute)
	*/
	var a [10]int
	for i := 0; i < 10; i++ {
		go func(i int) {
			for {
				a[i]++
				runtime.Gosched()
			}
		}(i)
	}
	time.Sleep(time.Second)
	fmt.Println("goroutine a[i]++")
	fmt.Println(a)
}

输出(1)2021-02-17 11:41:06.938943939 +0800 CST m=+3.550203203 hello,i'm from goroutine 469
2021-02-17 11:41:06.663614907 +0800 CST m=+3.274874166 hello,i'm from goroutine 5
2021-02-17 11:41:07.164039483 +0800 CST m=+3.775298734 hello,i'm from goroutine 469
2021-02-17 11:41:07.16404103 +0800 CST m=+3.775300290 hello,i'm from goroutine 5
2021-02-17 11:41:07.069884497 +0800 CST m=+3.681143750 hello,i'm from goroutine 785
......
输出(2):
goroutine a[i]++
[575155 549057 578855 550274 575947 557208 537390 548969 563552 539226]

注意:
此处使用函数式编程,必须将 i 作为参数传入;若不传入i,则函数闭包,会使用外部的i,最终导致使用到 a[10] 报错;
程序运行结果不达预期,可能原因为存在数据冲突,此时可以通过 go run -race 10.1.go 的形式来检测。

2.2 go语言的调度器

从程序角度来看,子程序是协程的一个特例,子程序代表的普通函数和协程的对比如下,从图中可见协程包含了普通函数:
golang笔记10--go语言并发编程模块 goroutine_第1张图片
从调度角度来看,一个或者多个协程都有可能被调度到某个线程中,其由调度器决定,一般情况下不需要用户关心。
golang笔记10--go语言并发编程模块 goroutine_第2张图片
goroutine 的定义:

  • 任何函数只需要加上go就能送给调度器运行;
  • 不需要在定义时区分是否是异步函数;
  • 调度器在合适的点进行切换;
  • 使用 -race 来检查数据访问冲突;

goroutine 可能的切换点:

  • I/O, select
  • channel
  • 等待锁
  • 函数调用(有可能)
  • runtime.Gosched()

3 注意事项

  1. Go语言从初始化main package 并执行main() 函数开始,当mai()函数返回时程序退出,且程序不等待其它 goroutine(非主goroutine) 结束;因此可以使用sleep 等简单粗暴的方式避免程序退出。

4 说明

  1. 软件环境
    go版本:go1.15.8
    操作系统:Ubuntu 20.04 Desktop
    Idea:2020.01.04
  2. 参考文档
    由浅入深掌握Go语言 --慕课网
    go 语言编程 --许式伟

你可能感兴趣的:(Golang,golang,go并发编程,goroutine,协程)