go中的并发

本文介绍了Go语言中的并发机制,包括goroutine的创建、特点和终止情况,Channel的使用和类型,以及select的原理和应用场景。goroutine作为轻量级线程,具有低成本和动态栈大小等优点。Channel作为goroutine间通信的桥梁,提供了无缓冲、有缓冲、单向和双向四种类型。select用于监听多个channel,实现并发执行和超时判断等操作。
摘要由CSDN通过智能技术生成

go中的并发

从并发模型说起

并发目前来看比较主流的就三种:

  1. 多线程
    • 每个线程一次处理一个请求,线程越多可并发处理的请求数就越多
    • 在高并发下,多线程的调度开销会比较大。
  2. 协程
    • 无需抢占式的调度,开销小,可以有效的提高线程的并发性,从而避免了线程的缺点的部分
  3. 基于异步回调的IO模型
    • 利用Linux内核的AIO进行异步IO1
    • nginx使用的就是epoll模型,通过事件驱动的方式与异步IO回调(伪异步,实际上是程序循环等待IO的出现)

goroutine

简介

在go里面,使用goroutine进行并发操作。

goroutine的本质是协程2,也可以认为是轻量级的线程,与创建线程相比,创建成本和开销都很小。同时,Golang 在 runtime、系统调用等多方面对 goroutine 调度进行了封装和处理,当遇到长时间执行或者进行系统调用时,会主动把当前 goroutine 的CPU § 转让出去,让其他 goroutine 能被调度并执行。

Golang 从语言层面支持了协程,从语言层面支持了高并发。

使用3

(这儿不会对语法有过多的说明,大家请去官网查看,官网的教程非常详细,远比其他网页上的资料更全面)

当一个程序启动的时候,只有一个goroutine来调用main函数,称它为主goroutine。新的goroutine通过在函数或者方法前面加上关键字go进行创建。

package main

import (
    "fmt"
    "time"
)

func Hello() {
   
    fmt.Println("Hello")
}

func main() {
   
    go Hello()                 // 开启一个新的goroutine
    time.Sleep(1*time.Second)  // 大家可以把这个去掉看看会出现什么结果
    fmt.Println("World")
}
/*
Hello
World
*/

特点

终止情况

Go语言中没有任何显示方法可以从外部终止一个 goroutine 的执行。这儿说明一下Goroutine的终止情况。

  1. 当Goroutine运行错误,抛出异常,终止程序;
  2. 当Goroutine返回,可以通过别的方式进行操控4
  3. 当Main Goroutine结束,所有其他Goroutine强制终止。

之所以这么设计有很多原因。我目前理解地也不是很深刻,简单说一些我认为的原因:

对于杀死的协程占有的资源,需要进行释放与其他管理;
对于杀死的协程占有的锁,需要进行拆除;
禁止之后需要程序员更加注意考虑协程的开始与结束。

栈大小

线程的栈空间是固定分配的,虽然区别于不同的系统会有不同的大小,但是基本都是2MB。这个栈用于保存局部变量,用于在函数切换时使用。

对于goroutine这种轻量级的协程来说,一个大小固定的栈可能会导致资源浪费:比如一个协程里面只print了一个语句,那么栈基本没怎么用。当然,也有可能嵌套调用很深,那么可能也不够用。

所以go采用了动态扩张收缩的策略:初始化为2KB,最大可扩张到1GB。

没有id

每个线程都有一个id,这个在线程创建时就会返回,所以可以很方便的通过id操作某个线程。

但是在goroutine内没有这个概念,这个是go语言设计之初考虑的,防止被滥用,所以你不能在一个协程中杀死另外一个协程,编码时需要考虑到协程什么时候创建,什么时候释放。

GOMAXPROCS

GOMAXPROCS用于设置上下文个数,这个上下文用于协程间的切换,默认值是CPU的个数,也就是说这个个数是指定同时执行协程的内核线程数,即,用户线程(协程)与内核线程的数量对应关系是1:GOMAXPROCS的5(这儿说的1是一个虚指,若有多个协程同时开启,则对应是M:N,如下例子)。

for {
     
    go fmt.Print(0)
    fmt.Print(1)
}
/*
$ GOMAXPROCS=1 go run example.go
11111111111111111100000000000000000000111111111...  
$ GOMAXPROCS=2 go run example.go
01010101010010101001110010101010010101010101010...  
*/

第一次执行语句指定只启动一个上下文,那么由于是2个协程映射到1个内核线程,那么1次只能跑一个协程,所以会跑一段时间再进行切换(由调度器进行判断什么时候切换,而不是内核)。第二次启动二个上下文,2个协程映射到2个内核线程,那么同一时间有2个干活的内核线程,所以能看到0和1交替打印,也就是说,此时真正实现了并发。

Channel

简介

如果说goroutine是Go并发的执行体,那么 Channel 就是他们之间的连接。

Channel是可以让一个goroutine发送特定的值到另外一个goroutine的通信机制。

在这里插入图片描述

使用

var ch chan int      // 声明一个传递int类型的channel
ch := make(chan int) // 使用内置函数make()定义一个channel

//=========

ch <- value          // 将一个数据value写入至channel,这会导致阻塞
                     
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值