goroutines

go的协程goroutines,通过go关键字启动,由runtime负责管理
协程是比线程更加轻量级的存在,线程的创建是需要向操作系统申请的,而协程可以由程序自己本身来控制(所以也叫用户态线程),go拥有一个主线程,一个线程上可以跑多个协程(但是串行)。协程多用来做并发

avatar

协程是不会阻塞主线程的运行的,并且同一个线程内的协程共享内存(堆共享栈不共享,跟线程一样),不需要加锁,主线程结束协程跟着退出。如下例,先打印了主线程,更改了然后协程才打印出了被主线程修改了值的word,因为要保证协程的运行,所以主线程还得睡三秒来等协程运行完毕

这里有个注意点!!!!!如果,你没有做任何措施来保证一个协程能够正常的运行(如下例的主线程睡几秒),那么,很有可能出现因为主线程退出过快而导致协程还没来得及运行也跟着退出的情况

  1. 栈:栈用于维护函数调用的上下文,包括函数的参数,局部变量等等。
  2. 堆:用来容纳程序中动态分配的内存区域,当程序使用malloc和new分配的内存就来自于堆里可执行文件映像。

    package main
    
    import (
     "fmt"
     "time"
    )
    
    func main() {
     word := "HELLO"
    
     go func() {
         time.Sleep(2 * time.Second)
         fmt.Println(word)
     }()
     fmt.Println("hello")
     word = "World"
     time.Sleep(3 * time.Second)
    }
    
    
    /*
    go run ./example.go 
    hello
    World
    */

    当需要进行协程上下文切换的时候,主线程只需要交换栈空间和恢复协程的一些相关的寄存器的状态就可以实现一个用户态的线程上下文切换,没有了从用户态转换到内核态的切换成本

从头到尾理解有栈协程实现原理

channel

channel用于两个协程之间的通信,当一方向调用channel来从另外一方获取信息的时候,他就会进入堵塞状态直到获取到需要的信息,同样的,如果发送方无人接收,他也会堵塞
channel使用chan关键字

从下例可以看到,goroutine调用wait_channel函数后,因为b接收不到信息,所以进入了阻塞状态,直到第十一行给c赋了值,管道里面有东西了,才继续运作下去

package main

import (
    "fmt"
)

func main() {
    c := make(chan bool)
    go wait_channel(c, "world")
    fmt.Println("Hello")
    c <- true
    <-c
}

func wait_channel(c chan bool, s string) {
    if b := <-c; b {
        fmt.Println(s)
    }
    c <- true
}

/*
go run ./example.go
Hello
world
*/

使用buffer可以避免发送方必须等待接收方出现才能继续的问题,但是如果缓冲区被塞满了,发送方依旧会被堵塞,如果buffer是空的,接收方也会被阻塞

我们可以使用range和for来监听一个channel,当结束的时候使用closed来关闭他

package main

import (
    "fmt"
)

func main() {
    c := make(chan string)
    go hello(c, 4)
    i := 0
    for s := range c {
        fmt.Println(s, "\n", i)
        i++
    }
    v, ok := <-c
    fmt.Println("Is Channel closed?", !ok, "values", v)
}

func hello(c chan string, n int) {
    for i := 0; i <= n; i++ {
        c <- "hello!"
    }
    close(c)
}

/*
go run ./example.go
hello! 
 0
hello! 
 1
hello! 
 2
hello! 
 3
hello! 
 4
Is Channel closed? true values
*/

select

select 会去同时监听多个channel,他会一直阻塞直到有channel ready,如果同时有多个channel满足条件,他会随机选择一个
default会在别的分支都没背触发的时候选中

package main

import (
    "fmt"
    "math/rand"
    "time"
)

var list = map[string]string{
    "zhangsan":  "nihao!",
    "lisi":      "hello~",
    "yoshinobu": "nigero!!",
}

func find_p(name string, server string, c chan string) {
    time.Sleep(time.Duration(rand.Intn(10)) * time.Second)
    c <- list[name]
}

func main() {
    rand.Seed(time.Now().UnixNano())
    v1 := make(chan string)
    v2 := make(chan string)

    name := "lisi"

    go find_p(name, "v1", v1)
    go find_p(name, "v2", v2)

    select {
    case word := <-v1:
        fmt.Println("v1 say", word, "as", name)
    case word := <-v2:
        fmt.Println("v2 say", word, "as", name)
    case <-time.After(5 * time.Second):
        fmt.Println("it's too late!")
        // default:
        //     fmt.Println("well........")
    }
}

//rand.Intn生成的是伪随机,必须用rand.Seed()并传入一个变化的值如当前的时间,否则不管运行几次都是同一个值
//Duration是time里的一种类型,多种time函数都以这个为参数,
//     type Duration int64

// const (
//     minDuration Duration = -1 << 63
//     maxDuration Duration = 1<<63 - 1
// )

// Common durations. There is no definition for units of Day or larger
// to avoid confusion across daylight savings time zone transitions.
//
// To count the number of units in a Duration, divide:
//    second := time.Second
//    fmt.Print(int64(second/time.Millisecond)) // prints 1000
//
// To convert an integer number of units to a Duration, multiply:
//    seconds := 10
//    fmt.Print(time.Duration(seconds)*time.Second) // prints 10s
//
// const (
//     Nanosecond  Duration = 1
//     Microsecond          = 1000 * Nanosecond
//     Millisecond          = 1000 * Microsecond
//     Second               = 1000 * Millisecond
//     Minute               = 60 * Second
//     Hour                 = 60 * Minute
// )

标签: none

评论已关闭