百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

并发和goroutine(并发和多线程有啥区别)

wptr33 2025-03-25 18:10 19 浏览

并行和并发

并行(parallel): 指在同一时刻, 有多条指令在多个处理上同时执行。

并发(concurrency): 指在同一时刻只能有一条指令执行, 但多个进行指令被快速的轮换执行, 使得在宏观上具有多个进程同时执行的效果, 但在微观上并不是同时执行的, 只是把时间分成若干端, 使多个进程快速交替的执行.

并行是两个队列同时使用两台咖啡机

并发是两个队列交替使用一台咖啡机

有人把Go比作21世纪的C语言, 第一是因为Go语言设计简单, 第二, 21世纪最重要的就是并行程序设计, 而Go从语言层面就支持了并行。

goroutine

goroutine是Go并行设计的核心。goroutine说到底其实就是线程, 但是它比线程更小, 十几个goroutine可能体现在底层就是五六个线程, Go语言内部帮你实现了这些goroutine之间的内存共享。

执行goroutine只需极少的栈内存(大概是4~5KB), 当然会根据相应的数据伸缩。也正因为如此, 可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。

goroutine是通过Go的runtime管理的一个线程管理器。goroutine通过go关键字实现了, 其实就是一个普通的函数。

go hello(a, b, c)

package main //必须有个main包
import (
"fmt"
"time"
)
func newTask() {
    i := 0
    for {
        i++
        fmt.Printf("new goroutine: i = %d\n", i)
        time.Sleep(1 * time.Second) //延时1s
    }
}
func main() {
    //创建一个goroutine, 启动另外一个任务 (必须先创建协程和一个任务), 不能放在主协程的后面
    go newTask()
    i := 0
    //main goroutine 循环打印
    for { //死循环, 一致执行下去
    i++
    fmt.Printf("main goroutine: i = %d\n", 1)
    time.Sleep(1 * time.Second) //延时1秒
    }
}

语法规则: 1 协程要放到主goroutine的前面;

2 执行的时间: 先执行一个主goroutine, 然后再执行协程;

3 主goroutine结束, 那么协程也结束;

主goroutine先退出

主goroutine退出后, 其他的工作goroutine也会自动退出:

【实例】

package main //必须有个main包
import (
"fmt"
// "runtime"
"time"
)
func newTask() {
    i := 0
    for {
        i++
        fmt.Printf("new goroutine: i = %d\n", i)
        time.Sleep(1 * time.Second) //延时1s
    }
}
func main() {
    //创建一个goroutine, 启动另外一个任务(子协程)
    go newTask()
    //time.Sleep(10 * time.Second) //延时10s,让子协程继续执行
    //runtime.Gosched() //让子协程继续执行, 推荐使用
    fmt.Println("main goroutine exit") //主协程退出了, 子协程来不及调用执行
}

执行结果:

main goroutine exit

在程序启动时, Go程序就会为main()函数创建一个默认的goroutine。当main()函数返回的时候该goroutine就结束了, 所有在main()函数中启动的goroutine会一同结束;

main函数所在的goroutine就像是权利的游戏中的夜王, 其他的goroutine都是异鬼, 夜王一死它转化的那些异鬼也就全部GG了。

所以我们要想办法让main函数等一等newTask函数, 最简单粗暴的方式就是time.Sleep了。

解决方法有两个:

time.Sleep(10 * time.Second) 延时10s,让子协程继续执行(取决于协程的执行的时间长)

runtime.Gosched() 让子协程继续执行, 推荐使用

【实例】

package main //必须有个main包
import (
"fmt"
"time"
)
func newTask() {
i := 0
for {
i++
fmt.Printf("new goroutine: i = %d\n", i)
time.Sleep(1 * time.Second) //延时1s
}
}
func main() {
//创建一个goroutine, 启动另外一个任务
go newTask()
i := 0
//main goroutine 循环打印
for {
i++
fmt.Printf("main goroutine: i = %d\n", 1)
time.Sleep(1 * time.Second) //延时1秒
if i == 2 {
break //主协程退出, 其他子协程也跟着退出
}
}
}

输出结果:

main goroutine: i = 1

new goroutine: i = 1

main goroutine: i = 1

new goroutine: i = 2

runtime包

Gosched

runtime.Gosched()表示让CPU把时间片让给别人, 让出当前goroutine的执行权限, 调度器安排其他等待的任务运行, 并在下次某个时候从该位置恢复执行。

这就像跑接力赛, A跑了一会碰到代码runtime.Gosched()就把接力棒交给B了, A歇着了, B继续跑。

【实例】

package main //必须有个main包
import (
"fmt"
"runtime"
)
func main() {
    //创建一个goroutine, 启动另外一个任务
    go func(s string) {
    for i := 0; i < 3; i++ {
    fmt.Println(s)
    }
    }("world")
    for i := 0; i < 2; i++ {
    runtime.Gosched() //让子协程继续执行
    fmt.Println("hello")
    }
}

【实例】 数组越界问题与参数传递

package main
import (
"fmt"
"runtime"
"time"
)
func main() {
    var a [10]int
    for i := 0; i < 10; i++ {
    go func(i int) { // race condition! 如果这里不传递i, 就会出现数组越界 panic: runtime error: index out of range [10] with length 10
    for {
    a[i]++
    //fmt.Printf("Hello from "+"goroutine %d\n", i)
    runtime.Gosched() //交出控制权
    }
    }(i) //如果这里不传递i, 就会出现数组越界 panic: runtime error: index out of range [10] with length 10
    }
    time.Sleep(time.Millisecond)
    fmt.Println(a)
}

运行方法:

go run main.go

使用-race来检测数据访问冲突

go run -race main.go

Goexit

调用runtime.Goexit()将立即终止当前goroutine执行, 调度器确保所有已注册defer延迟调用被执行。

package main //必须有个main包
import (
"fmt"
"runtime"
)
func main() {
    //创建一个goroutine, 启动另外一个任务
    go func() {
    defer fmt.Println("A.defer")
    func() {
    defer fmt.Println("B.defer")
    runtime.Goexit() //终止当前goroutine, import "runtime"
    fmt.Println("B") //不会执行
    }()
    fmt.Println("A") //不会执行
    }()
    //死循环, 目的不让主goroutine结束
    for {
    }
}

运行结果:

B.defer

A.defer

GOMAXPROCS

调用 runtime.GOMAXPROCS()用来设置可以并行计算的CPU核数的最大值, 并返回之前的值。

package main //必须有个main包
import (
"fmt"
"runtime"
)
func main() {
    n := runtime.GOMAXPROCS(1) //打印结果 111111111111111111111111100000000000000000
    //n := runtime.GOMAXPROCS(2) //打印结果 010101010101010101010101010101
    fmt.Printf("n = %d\n", n)
    for {
        go fmt.Print(0)
        fmt.Print(1)
    }
}

GOMAXPROCS设置可同时执行的最大CPU数, 并返回先前的设置。

在第一次执行(runtime.GOMAXPROCS(1)), 最多同时只能有一个goroutine被执行。所以会打印很多1。

过了一段时间后, GO调度器将其设置为休眠, 并呼唤另一个goroutine, 这时候就开始打印很多0了, 在打印的时候, goruntine是被调度到操作系统线程上的。

在第二次执行(runtime.GOMAXPROCS(2)), 我们使用了两个CPU, 所以两个goroutine可以一起被执行, 以同样的频率交替打印0和1。

函数内部 return 之前 runtime(协程) 是否会执行?


package main //必须有个main包
import "fmt"
func Test(args ...int) {
        for _, n := range args { //遍历参数列表
        fmt.Println(n)
   		 }
    fmt.Println("判断协程是否执行? 已经执行!")
}
func sum(numbers ...int) int {
    s := 0
    for i := range numbers {
    s += numbers[i]
    }
    // Test(1, 2, 3, 4, 5) // 这里得到了执行
    go Test(1, 2, 3, 4, 5) // 函数内部 return 前 协程 runtime 没有执行;
    return s
}
func main() {
    //函数调用,可传0到多个参数
    fmt.Println(sum(1, 2, 3, 4, 5)) // 15

    //解决方法: 解决子协程来不及执行的问题
    for {
    }
}

输出结果:

15

1

2

3

4

5

判断协程是否执行? 已经执行

结论: 主goroutine(main)退出后, 其他的工作goroutine也会自动退出;

函数的返回值在 goroutine 之前先执行

相关推荐

MySQL进阶五之自动读写分离mysql-proxy

自动读写分离目前,大量现网用户的业务场景中存在读多写少、业务负载无法预测等情况,在有大量读请求的应用场景下,单个实例可能无法承受读取压力,甚至会对业务产生影响。为了实现读取能力的弹性扩展,分担数据库压...

Postgres vs MySQL_vs2022连接mysql数据库

...

3分钟短文 | Laravel SQL筛选两个日期之间的记录,怎么写?

引言今天说一个细分的需求,在模型中,或者使用laravel提供的EloquentORM功能,构造查询语句时,返回位于两个指定的日期之间的条目。应该怎么写?本文通过几个例子,为大家梳理一下。学习时...

一文由浅入深带你完全掌握MySQL的锁机制原理与应用

本文将跟大家聊聊InnoDB的锁。本文比较长,包括一条SQL是如何加锁的,一些加锁规则、如何分析和解决死锁问题等内容,建议耐心读完,肯定对大家有帮助的。为什么需要加锁呢?...

验证Mysql中联合索引的最左匹配原则

后端面试中一定是必问mysql的,在以往的面试中好几个面试官都反馈我Mysql基础不行,今天来着重复习一下自己的弱点知识。在Mysql调优中索引优化又是非常重要的方法,不管公司的大小只要后端项目中用到...

MySQL索引解析(联合索引/最左前缀/覆盖索引/索引下推)

目录1.索引基础...

你会看 MySQL 的执行计划(EXPLAIN)吗?

SQL执行太慢怎么办?我们通常会使用EXPLAIN命令来查看SQL的执行计划,然后根据执行计划找出问题所在并进行优化。用法简介...

MySQL 从入门到精通(四)之索引结构

索引概述索引(index),是帮助MySQL高效获取数据的数据结构(有序),在数据之外,数据库系统还维护者满足特定查询算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构...

mysql总结——面试中最常问到的知识点

mysql作为开源数据库中的榜一大哥,一直是面试官们考察的重中之重。今天,我们来总结一下mysql的知识点,供大家复习参照,看完这些知识点,再加上一些边角细节,基本上能够应付大多mysql相关面试了(...

mysql总结——面试中最常问到的知识点(2)

首先我们回顾一下上篇内容,主要复习了索引,事务,锁,以及SQL优化的工具。本篇文章接着写后面的内容。性能优化索引优化,SQL中索引的相关优化主要有以下几个方面:最好是全匹配。如果是联合索引的话,遵循最...

MySQL基础全知全解!超详细无废话!轻松上手~

本期内容提醒:全篇2300+字,篇幅较长,可搭配饭菜一同“食”用,全篇无废话(除了这句),干货满满,可收藏供后期反复观看。注:MySQL中语法不区分大小写,本篇中...

深入剖析 MySQL 中的锁机制原理_mysql 锁详解

在互联网软件开发领域,MySQL作为一款广泛应用的关系型数据库管理系统,其锁机制在保障数据一致性和实现并发控制方面扮演着举足轻重的角色。对于互联网软件开发人员而言,深入理解MySQL的锁机制原理...

Java 与 MySQL 性能优化:MySQL分区表设计与性能优化全解析

引言在数据库管理领域,随着数据量的不断增长,如何高效地管理和操作数据成为了一个关键问题。MySQL分区表作为一种有效的数据管理技术,能够将大型表划分为多个更小、更易管理的分区,从而提升数据库的性能和可...

MySQL基础篇:DQL数据查询操作_mysql 查

一、基础查询DQL基础查询语法SELECT字段列表FROM表名列表WHERE条件列表GROUPBY分组字段列表HAVING分组后条件列表ORDERBY排序字段列表LIMIT...

MySql:索引的基本使用_mysql索引的使用和原理

一、索引基础概念1.什么是索引?索引是数据库表的特殊数据结构(通常是B+树),用于...