golang学习笔记之三 - 并发与通信

2023-09-05 16:11:26 浏览数 (1)

学习笔记 golang

并发

goroutine是Go并行设计的核心。goroutine说到底其实就是协程,但是它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB),当然会根据相应的数据伸缩。也正因为如此,可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。

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

runtime.Gosched()表示让CPU把时间片让给别人,下次某个时候继续恢复执行该goroutine。 多个goroutine运行在同一个进程里面,共享内存数据,不过设计上我们要遵循:不要通过共享来通信,而要通过通信来共享。

代码语言:javascript复制
func sayHello() {
    for i := 0; i < 5; i   {
        fmt.Println("hello")
    }
}

func sayWrole()  {
    for i := 0; i < 2; i   {
        fmt.Println("world")
        // time.Sleep(time.Second * 3)
    }
}

func main()  {
    go sayHello()    // 子进程,没来得及执行,已经退出,除非主进程执行时间慢
    sayWrole()       // 默认先执行主进程,当执行完后,没来得及执行子进程
}

/* 输出
world
world
*/
代码语言:javascript复制
func sayHello() {
    for i := 0; i < 5; i   {
        fmt.Println("hello")
    }
}

func sayWrole()  {
    for i := 0; i < 2; i   {
        fmt.Println("world")
    }
}

func main()  {
    go sayHello()

    runtime.Gosched()  // 让出时间片,先让别的协议执行,它执行完,再回来执行此协程
    sayWrole()
}

/* 输出
hello
hello
hello
hello
hello
world
world
*/

Channels

goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。那么goroutine之间如何进行数据的通信呢,Go提供了一个很好的通信机制channel。channel可以与Unix shell 中的双向管道做类比:可以通过它发送或者接收值。这些值只能是特定的类型:channel类型。定义一个channel时,也需要定义发送到channel的值的类型。注意,必须使用make 创建channel:

代码语言:javascript复制
ci := make(chan int)
cs := make(chan string)
cf := make(chan interface{})

默认情况下,channel接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得Goroutines同步变的更加的简单,而不需要显式的lock。所谓阻塞,也就是如果读取(value := <-ch)它将会被阻塞,直到有数据接收。其次,任何发送(ch<-5)将会被阻塞,直到数据被读出。无缓冲channel是在多个goroutine之间同步很棒的工具。

代码语言:javascript复制
func sum(a []int, c chan int)  {
    var total int
    for _, value := range a {
        total  = value
    }

    c <- total
}

func main()  {
    var a = []int{2, 4, 6, 9, 22}
    var b = []int{3, 6, 61, 19, 90}

    //var c = make(chan int, 5)
    var c = make(chan int)

    go sum(a, c)
    go sum(b, c)

    fmt.Println(<-c, <-c)
}

ch := make(chan type, value) 当 value = 0 时,channel 是无缓冲阻塞读写的,当value > 0 时,channel 有缓冲、是非阻塞的,直到写满 value 个元素才阻塞写入。

使用 Range 和 Close, for i := range c 能够不断的读取channel里面的数据,直到该channel被显式的关闭:

代码语言:javascript复制
func main()  {
    c := make(chan int, 5)

    c <- 333
    c <- 444
    c <- 555
    c <- 666
    close(c)

    for v := range c {
        fmt.Println(v)
    }
}

记住应该在生产者的地方关闭channel,而不是消费的地方去关闭它,这样容易引起panic channel不像文件之类的,不需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束range循环之类的

如果存在多个channel的时候,Go里面提供了一个关键字select,通过select可以监听channel上的数据流动。 select默认是阻塞的,只有当监听的channel中有发送或接收可以进行时才会运行,当多个channel都准备好的时候,select是随机的选择一个执行的

代码语言:javascript复制
func fibonacci(c, q chan int)  {
    x, y := 1, 1
    for {
        select {
        case c <-x:
            //x = y
            //y = x   y
            x, y = y, x   y
        case <-q:
            fmt.Println("q")
            return
        }
    }
}
func main() {
    c := make(chan int)
    q := make(chan int)

    go func() {
        for i := 0; i < 10; i   {
            fmt.Println(<-c)
        }
        q <-0
    }()

    fibonacci(c, q)
}

在select里面还有default语法,select其实就是类似switch的功能,default就是当监听的channel都没有准备好的时候,默认执行的(select不再阻塞等待channel)。

代码语言:javascript复制
select {
case i := <-c:
    // use i
default:
    // 当c阻塞的时候执行这里
}

runtime包中有几个处理goroutine的函数

  • Goexit

退出当前执行的goroutine,但是defer函数还会继续调用

  • Gosched

让出当前goroutine的执行权限,调度器安排其他等待的任务运行,并在下次某个时候从该位置恢复执行。

  • NumCPU

返回 CPU 核数量

  • NumGoroutine

返回正在执行和排队的任务总数

  • GOMAXPROCS

用来设置可以并行计算的CPU核数的最大值,并返回之前的值。

总结 (25个关键字)

代码语言:javascript复制
break    default      func    interface    select
case     defer        go      map          struct
chan     else         goto    package      switch
const    fallthrough  if      range        type
continue for          import  return       var
var和const参考2.2Go语言基础里面的变量和常量申明
package和import已经有过短暂的接触
func 用于定义函数和方法
return 用于从函数返回
defer 用于类似析构函数
go 用于并发
select 用于选择不同类型的通讯
interface 用于定义接口,参考2.6小节
struct 用于定义抽象数据类型,参考2.5小节
break、case、continue、for、fallthrough、else、if、switch、goto、default这些参考2.3流程介绍里面
chan用于channel通讯
type用于声明自定义类型
map用于声明map类型数据
range用于读取slice、map、channel数据 

0 人点赞