精华内容
下载资源
问答
  • goroutine

    2021-04-13 10:39:16
    文章目录goroutine线程和协程创建goroutineGo并发模型优点缺点 goroutine goroutine是Go语言提供的一种用户态线程,有时我们也称之为协程。但是它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言...

    goroutine

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

    线程和协程

    • 进程:拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。
    • 线程:拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度。
    • 协程 :和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。

    创建goroutine

    创建goroutine,只需要在函数调用语句前添加go关键字。

    但是如果主函数先退出,其他的goroutine也会自动退出。

    如果这个函数有返回值,那么这个返回值会被丢弃。

    func newTask() {
        fmt.Println("new goroutine")
    }
    
    func main() {
        //创建一个 goroutine,启动另外一个任务
        go newTask()
    
        fmt.Println("main goroutine exit")
    }
    
    

    Go并发模型

    Go实现了两种并发形式。

    • 多线程共享内存。其实就是Java或者C++等语言中的多线程开发。
    • CSP(communicating sequential processes)并发模型。这是Go语言特有的,也是Go语言推荐的。

    普通的线程并发模型,就是像Java、C++、或者Python,他们线程间通信都是通过共享内存的方式来进行的。非常典型的方式就是,在访问共享数据(例如数组、Map、或者某个结构体或对象)的时候,通过锁来访问。

    CSP讲究的是“以通信的方式来共享内存”。

    DO NOT COMMUNICATE BY SHARING MEMORY; INSTEAD, SHARE MEMORY BY COMMUNICATING.
    “不要以共享内存的方式来通信,相反,要通过通信来共享内存。”

    Go的CSP并发模型,是通过goroutinechannel来实现的。

    • goroutine 是Go语言中并发的执行单位。有点抽象,其实就是和传统概念上的”线程“类似,可以理解为”线程“。
    • channel是Go语言中各个并发结构体(goroutine)之前的通信机制。 通俗的讲,就是各个goroutine之间通信的”管道“,有点类似于Linux中的管道。
    var ch = make(chan string)
    
    func send() {
    	fmt.Println("send data to ch")
    	ch <- "test"
    }
    
    func recive() {
    	result := <-ch
    	fmt.Printf("get data from ch: %s", result)
    }
    
    func main() {
    	go recive()
    	go send()
    	time.Sleep(5 * time.Second)
    }
    

    优点

    • 内存消耗更少:Goroutine所需要的内存通常只有2kb,而线程则需要1Mb
    • 创建与销毁的开销更小:由于线程创建时需要向操作系统申请资源,并且在销毁时将资源归还,因此它的创建和销毁的开销比较大。相比之下,goroutine的创建和销毁是由go语言在运行时自己管理的,因此开销更低。
    • 切换开销更小线程的调度方式是抢占式的,如果一个线程的执行时间超过了分配给它的时间片,就会被其它可执行的线程抢占;而goroutine的调度是协同式的,它不会直接地与操作系统内核打交道。

    缺点

    • 协程调度机制无法实现公平调度:因为协程的调度是非入侵式的,系统不会为他分配资源。
    展开全文
  • Goroutine

    千次阅读 2011-04-14 13:04:00
    Go语言的主要的功能在于令人简易使用的并行设计,这个方法叫做Goroutine,通过Goroutine能够让你的程序以异步的方式运行,而不需要担心一个函数导致程序中断,因此Go语言也非常地适合网络服务。 我们通过go让其中一...

    Go语言的主要的功能在于令人简易使用的并行设计,这个方法叫做Goroutine,通过Goroutine能够让你的程序以异步的方式运行,而不需要担心一个函数导致程序中断,因此Go语言也非常地适合网络服务。

    我们通过go让其中一个函数同步运行,如此就不需要等待该函数运行完后才能运行下一个函数。

    func main() {

    // 通过 `go`,我们可以把这个函数异步执行,这样就不会阻塞往下执行。 go loop()

    // 执行 Other

    }

    Goroutine是类似线程的概念(但Goroutine并不是线程)。线程属于系统层面,通常来说创建一个新的线程会消耗较多的资源且管理不易。而 Goroutine就像轻量级的线程,但我们称其为并发,一个Go程序可以运行超过数万个 Goroutine,并且这些性能都是原生级的,随时都能够关闭、结束。一个核心里面可以有多个Goroutine,通过GOMAXPROCS参数你能够限制Gorotuine可以占用几个系统线程来避免失控。

    在内置的官方包中也不时能够看见Goroutine的应用,像是net/http中用来监听网络服务的函数实际上是创建一个不断运行循环的Goroutine。

     

    设置同时执行的cpu数(GOMAXPROCS)

    GOMAXPROCS 在调度程序优化后会去掉,默认用系统所有资源。

    func main() {

    num := runtime.NumCPU() //本地机器的逻辑CPU个数

    runtime.GOMAXPROCS(num) //设置可同时执行的最大CPU数,并返回先前的设置 fmt.Println(num)

    }

     

    Goroutine中使用recover

    应用场景,如果某个goroutine panic了,而且这个goroutine里面没有捕获(recover),那么整个进程就会挂掉。所以,好的习惯是每当go产生一个goroutine,就需要写下recover。

    var (

    domainSyncChan = make(chan int, 10)

    )

     

    func domainPut(num int) {

    defer func() {

    err := recover()

    if err != nil {

    fmt.Println("error to chan put.")

    }

    }()

    domainSyncChan <- num

    panic("error....")

    }

     

    func main() {

    for i := 0; i < 10; i++ {

    domainName := i

    go domainPut(domainName)

    }

    time.Sleep(time.Second * 2)

    }

     

    Goroutine 栗子

    package main

     

    import (

    "fmt"

    "sync"

    "time"

    )

     

    var (

    m = make(map[int]uint64)

    lock sync.Mutex //申明一个互斥锁)

     

    type task struct {

    n int

    }

     

    func calc(t *task) {

    defer func() {

    err := recover()

    if err != nil {

    fmt.Println("error...")

    return

    }

    }()

     

    var sum uint64

    sum = 1

    for i := 1; i < t.n; i++ {

    sum *= uint64(i)

    }

     

    lock.Lock() //写全局数据加互斥锁

    m[t.n] = sum

    lock.Unlock() //解锁}

     

    func main() {

    for i := 0; i < 10; i++ {

    t := &task{n: i}

    go calc(t) // Goroutine来执行任务 }

     

    time.Sleep(time.Second) // Goroutine异步,所以等一秒到任务完成

     

    lock.Lock() //读全局数据加锁

    for k, v := range m {

    fmt.Printf("%d! = %v\n", k, v)

    }

    fmt.Println(len(m))

    lock.Unlock() //解锁

    }

     

    Goroutine 栗子(等待所有任务退出主程序再退出)

    package main

     

    import (

    "sync"

    "fmt"

    "time"

    )

     

    func calc(w *sync.WaitGroup, i int) {

    fmt.Println("calc: ", i)

    time.Sleep(time.Second)

    w.Done()

    }

     

    func main() {

    wg := sync.WaitGroup{}

    for i:=0; i<10; i++ {

    wg.Add(1)

    go calc(&wg, i)

    }

    wg.Wait()

    fmt.Println("all goroutine finish")

    }

     

    Channel

    channel,管道、队列,先进先出,用来异步传递数据。channel加上goroutine,就形成了一种既简单又强大的请求处理模型,使高并发和线程同步之间代码的编写变得异常简单。

    线程安全,多个goroutine同时访问,不需要加锁。

    channel是有类型的,一个整数的channel只能存放整数。

    channel使用

    //chan申明var userChan chan interface{} // chan里面放interface类型

    userChan = make(chan interface{}, 10) // make初始化,大小为10

     

    var readOnlyChan <-chan int // 只读chanvar writeOnlyChan chan<- int // 只写chan

    //chan放取数据

    userChan <- "nick"

    name := <- userChan

    name, ok := <- userChan

    //关闭chan

    intChan := make(chan int, 1)

    intChan <- 9

    close(intChan)

    // range chan

    intChan := make(chan int, 10)

    for i := 0; i < 10; i++ {

    intChan <- i

    }

    close(intChan)

     

    for v := range intChan {

    fmt.Println(v)

    }

      

    放入chan数据个数超过初始化指定大小会怎样?

    userChan := make(chan interface{})

    userChan <- "nick"// 错误!fatal error: all goroutines are asleep - deadlock!

    // 开启race会一直阻塞

    开启一个goroutine来放入初始化未指定大小的chan不会报错。

    即放即走,在等放入时有来拿数据的,就直接拿走。

    userChan := make(chan interface{})

    go func() {

    userChan <- "nick"

    }()

    name := <- userChan

    userChan := make(chan interface{})

    go func() {

    for {

    userChan <- "nick"

    }

    }()

    for {

    name := <- userChan

    fmt.Println(name)

    time.Sleep(time.Millisecond)

    }

     

    chan关闭与不关闭

    关闭chan后再放入数据会 panic: send on closed channel。

    chan不关闭取超数据的情况会报 deadlock

    func main() {

    intChan := make(chan int, 10)

     

    for i := 0; i < 10; i++ {

    intChan <- i

    }

    for {

    //十次后 fatal error: all goroutines are asleep - deadlock!

    i := <- intChan

    fmt.Println(i)

    time.Sleep(time.Second)

    }

    }

    chan关闭的情况取超出值为类型默认值,如int为0

    func main() {

    intChan := make(chan int, 10)

     

    for i := 0; i < 10; i++ {

    intChan <- i

    }

    close(intChan)

     

    for {

    i := <- intChan

    //十次后i值都为0,不报错 time.Sleep(time.Second)

    fmt.Println(i)

    }

    }

    判断chan是否取完

    func main() {

    intChan := make(chan int, 10)

     

    for i := 0; i < 10; i++ {

    intChan <- i

    }

    close(intChan)

     

    for {

    i, ok := <- intChan

    if !ok {

    fmt.Println("channel is close.")

    return

    }

    fmt.Println(i)

    }

    }

     

    channel 栗子

    栗子一

    func sendData(ch chan<- string) {

    ch <- "go"

    ch <- "java"

    ch <- "c"

    ch <- "c++"

    ch <- "python"

    close(ch)

    }

     

    func getData(ch <-chan string, chColse chan bool) {

    for {

    str, ok := <-ch

    if !ok {

    fmt.Println("chan is close.")

    break

    }

    fmt.Println(str)

    }

    chColse <- true

    }

     

    func main() {

    ch := make(chan string, 10)

    chColse := make(chan bool, 1)

    go sendData(ch)

    go getData(ch, chColse)

    <-chColse

    close(chColse)

    }

     

    栗子二:interface类型chan,取出后转化为对应类型。

    type user struct {

    Name string

    }

     

    func main() {

    userChan := make(chan interface{}, 1)

     

    u := user{Name: "nick"}

    userChan <- &u

    close(userChan)

     

    var u1 interface{}

    u1 = <-userChan

     

    var u2 *user

    u2, ok := u1.(*user)

    if !ok {

    fmt.Println("cant not convert.")

    return

    }

    fmt.Println(u2)

    }

     

    channel 超时处理

    利用select来处理chan超时。

    for {

    select {

    case v := <-chan1:

    fmt.Println(v)

    case v := <-chan2:

    fmt.Println(v)

    default:

    time.Sleep(time.Second)

    fmt.Println("timeout...")

    }

    }

    time.After()定时器来做处理。

    在time.After()计时器触发之前,底层计时器不会被垃圾收集器回收。

    select {

    case m := <-c:

    handle(m)

    case <-time.After(5 * time.Minute):

    fmt.Println("timed out")

    }

     定时器栗子

     

    Goroutine+Channel 栗子

    栗子一

    多个goroutine处理任务;

    等待一组channel的返回结果。

    func calc(taskChan, resChan chan int, exitChan chan bool) {

    defer func() {

    err := recover()

    if err != nil {

    fmt.Println("error...")

    return

    }

    }()

    for v := range taskChan {

    // 任务处理逻辑

    flag := true

    for i := 2; i < v; i++ {

    if v%i == 0 {

    flag = false

    break

    }

    }

    if flag {

    //结果进chan

    resChan <- v

    }

    }

    //处理完进退出chan

    exitChan <- true

    }

     

    func main() {

    //任务chan

    intChan := make(chan int, 1000)

    //结果chan

    resChan := make(chan int, 1000)

    //退出chan

    exitChan := make(chan bool, 8)

     

    go func() {

    for i := 0; i < 1000; i++ {

    intChan <- i

    }

    close(intChan)

    }()

     

    //启动8个goroutine做任务

    for i := 0; i < 8; i++ {

    go calc(intChan, resChan, exitChan)

    }

     

    go func() {

    //等所有goroutine结束

    for i := 0; i < 8; i++ {

    <-exitChan

    }

    close(resChan)

    close(exitChan)

    }()

     

    for v := range resChan {

    fmt.Println(v)

    }

    }

     

    栗子二

    等待一组channel的返回结果 sync.WaitGroup 的解决方法。

    WaitGroup用于等待一组线程的结束。父线程调用Add方法来设定应等待的线程的数量。每个被等待的线程在结束时应调用Done方法。同时,主线程里可以调用Wait方法阻塞至所有线程结束。

    func merge(cs <-chan int) <-chan int {

    var wg sync.WaitGroup

    out := make(chan int)

     

    output := func(c <-chan int) {

    for n := range c {

    out <- n

    }

    wg.Done()

    }

    wg.Add(len(cs))

     

    for _, c := range cs {

    go output(c)

    }

     

    go func() {

    wg.Wait()

    close(out)

    }()

    return out

    }

     

    展开全文
  • GoRoutine

    2019-08-26 11:27:33
    仔细研究会发现,其实根本没等goroutine执行完,main函数自己先跑完了,所以就没有数据流入主的goroutine,就不会被阻塞和报错 6.2goroutine的死锁处理 有两种办法可以解决: 1.把没取走的取走便是 package ...

    在这里不一定要去关闭channel,因为底层的垃圾回收机制会根据它是否可以访问来决定是否自动回收它。(这里不是根据channel是否关闭来决定的)

    3.单向通道类型

    当程序则够复杂的时候,为了代码可读性更高,拆分成一个一个的小函数是需要的。

    此时go提供了单向通道的类型,来实现函数之间channel的传递。

    上代码:

    package main

    import (
    “fmt”
    “time”
    )

    // 定义goroutine 1
    func Echo(out chan<- string) { // 定义输出通道类型
    time.Sleep(1*time.Second)
    out <- “咖啡色的羊驼”
    close(out)
    }

    // 定义goroutine 2
    func Receive(out chan<- string, in <-chan string) { // 定义输出通道类型和输入类型
    temp := <-in // 阻塞等待echo的通道的返回
    out <- temp
    close(out)
    }

    func main() {
    echo := make(chan string)
    receive := make(chan string)

    go Echo(echo)
    go Receive(receive, echo)
    
    getStr := <-receive   // 接收goroutine 2的返回
    
    fmt.Println(getStr)
    

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    程序输出:

    咖啡色的羊驼
    1
    4.缓冲管道

    goroutine的通道默认是是阻塞的,那么有什么办法可以缓解阻塞?
    答案是:加一个缓冲区。

    对于go来说创建一个缓冲通道很简单:

    ch := make(chan string, 3) // 创建了缓冲区为3的通道

    //=========
    len(ch) // 长度计算
    cap(ch) // 容量计算
    1
    2
    3
    4
    5

    6.goroutine死锁与友好退出
    6.1goroutine死锁
    来一个死锁现场一:

    package main

    func main() {
    ch := make(chan int)
    <- ch // 阻塞main goroutine, 通道被锁
    }
    1
    2
    3
    4
    5
    6
    输出:

    fatal error: all goroutines are asleep - deadlock!

    goroutine 1 [chan receive]:
    main.main()
    1
    2
    3
    4
    死锁现场2:

    package main

    func main() {
    cha, chb := make(chan int), make(chan int)

    go func() {
        cha <- 1 // cha通道的数据没有被其他goroutine读取走,堵塞当前goroutine
        chb <- 0
    }()
    
    <- chb // chb 等待数据的写
    

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    为什么会有死锁的产生?

    非缓冲通道上如果发生了流入无流出,或者流出无流入,就会引起死锁。
    或者这么说:goroutine的非缓冲通道里头一定要一进一出,成对出现才行。
    上面例子属于:一:流出无流入;二:流入无流出

    当然,有一个例外:

    func main() {
    ch := make(chan int)
    go func() {
    ch <- 1
    }()
    }
    1
    2
    3
    4
    5
    6
    执行以上代码将会发现,竟然没有报错。
    what?
    不是说好的一进一出就死锁吗?
    仔细研究会发现,其实根本没等goroutine执行完,main函数自己先跑完了,所以就没有数据流入主的goroutine,就不会被阻塞和报错

    6.2goroutine的死锁处理
    有两种办法可以解决:

    1.把没取走的取走便是

    package main

    func main() {
    cha, chb := make(chan int), make(chan int)

    go func() {
        cha <- 1 // cha通道的数据没有被其他goroutine读取走,堵塞当前goroutine
        chb <- 0
    }()
    
    <- cha // 取走便是
    <- chb // chb 等待数据的写
    

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    2.创建缓冲通道

    package main

    func main() {
    cha, chb := make(chan int, 3), make(chan int)

    go func() {
        cha <- 1 // cha通道的数据没有被其他goroutine读取走,堵塞当前goroutine
        chb <- 0
    }()
    
    <- chb // chb 等待数据的写
    

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    这样的话,cha可以缓存一个数据,cha就不会挂起当前的goroutine了。除非再放两个进去,塞满缓冲通道就会了。

    7.select的简介
    定义:在golang里头select的功能与epoll(nginx)/poll/select的功能类似,都是坚挺IO操作,当IO操作发生的时候,触发相应的动作。

    select有几个重要的点要强调:

    1.如果有多个case都可以运行,select会随机公平地选出一个执行,其他不会执行
    上代码:

    package main

    import “fmt”

    func main() {
    ch := make (chan int, 1)

    ch<-1
    select {
    case <-ch:
        fmt.Println("咖啡色的羊驼")
    case <-ch:
        fmt.Println("黄色的羊驼")
    }
    

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    输出:

    (随机)二者其一
    1
    2.case后面必须是channel操作,否则报错。

    上代码:

    package main

    import “fmt”

    func main() {
    ch := make (chan int, 1)
    ch<-1
    select {
    case <-ch:
    fmt.Println(“咖啡色的羊驼”)
    case 2:
    fmt.Println(“黄色的羊驼”)
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    输出报错:

    2 evaluated but not used
    select case must be receive, send or assign recv
    1
    2
    3.select中的default子句总是可运行的。所以没有default的select才会阻塞等待事件
    上代码:

    package main

    import “fmt”

    func main() {
    ch := make (chan int, 1)
    // ch<-1 <= 注意这里备注了。
    select {
    case <-ch:
    fmt.Println(“咖啡色的羊驼”)
    default:
    fmt.Println(“黄色的羊驼”)
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    输出:

    黄色的羊驼
    1
    4.没有运行的case,那么江湖阻塞事件发生报错(死锁)

    package main

    import “fmt”

    func main() {
    ch := make (chan int, 1)
    // ch<-1 <= 注意这里备注了。
    select {
    case <-ch:
    fmt.Println(“咖啡色的羊驼”)
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    输出报错:

    fatal error: all goroutines are asleep - deadlock!
    1
    8.select的应用场景
    1.timeout 机制(超时判断)

    package main

    import (
    “fmt”
    “time”
    )

    func main() {
    timeout := make (chan bool, 1)
    go func() {
    time.Sleep(1*time.Second) // 休眠1s,如果超过1s还没I操作则认为超时,通知select已经超时啦~
    timeout <- true
    }()
    ch := make (chan int)
    select {
    case <- ch:
    case <- timeout:
    fmt.Println(“超时啦!”)
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    以上是入门版,通常代码中是这么写的:

    package main

    import (
    “fmt”
    “time”
    )

    func main() {
    ch := make (chan int)
    select {
    case <-ch:
    case <-time.After(time.Second * 1): // 利用time来实现,After代表多少时间后执行输出东西
    fmt.Println(“超时啦!”)
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    2.判断channel是否阻塞(或者说channel是否已经满了)

    package main

    import (
    “fmt”
    )

    func main() {
    ch := make (chan int, 1) // 注意这里给的容量是1
    ch <- 1
    select {
    case ch <- 2:
    default:
    fmt.Println(“通道channel已经满啦,塞不下东西了!”)
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    3.退出机制

    package main

    import (
    “fmt”
    “time”
    )

    func main() {
    i := 0
    ch := make(chan string, 0)
    defer func() {
    close(ch)
    }()

    go func() {
        DONE: 
        for {
            time.Sleep(1*time.Second)
            fmt.Println(time.Now().Unix())
            i++
    
            select {
            case m := <-ch:
                println(m)
                break DONE // 跳出 select 和 for 循环
            default:
            }
        }
    }()
    
    time.Sleep(time.Second * 4)
    ch<-"stop"
    

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    输出:

    1532390471
    1532390472
    1532390473
    stop
    1532390474
    1
    2
    3
    4
    5
    这边要强调一点:退出循环一定要用break + 具体的标记,或者goto也可以。否则其实不是真的退出。

    package main

    import (
    “fmt”
    “time”
    )

    func main() {
    i := 0
    ch := make(chan string, 0)
    defer func() {
    close(ch)
    }()

    go func() {
    
        for {
            time.Sleep(1*time.Second)
            fmt.Println(time.Now().Unix())
            i++
    
            select {
            case m := <-ch:
                println(m)
                goto DONE // 跳出 select 和 for 循环
            default:
            }
        }
        DONE:
    }()
    
    time.Sleep(time.Second * 4)
    ch<-"stop"
    

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    输出:

    ————————————————
    版权声明:本文为CSDN博主「咖啡色的羊驼」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/u011957758/article/details/81159481

    展开全文
  • goroutine.go

    2019-10-06 07:42:43
    goroutine
  • Golang Goroutine

    千次阅读 2020-08-04 11:17:09
    什么是 Goroutine goroutine 是 Go 并行设计的核心。goroutine 说到底其实就是协程,它比线程更小,十几个 goroutine 可能体现在底层就是五六个线程,Go 语言内部帮你实现了这些 goroutine 之间的内存共享。 执行 ...

    什么是 Goroutine

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

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

    一般情况下,一个普通计算机跑几十个线程就有点负载过大了,但是同样的机器却可以轻松地让成百上千个 goroutine 进行资源竞争。

    Goroutine 的创建

    只需在函数调⽤语句前添加 go 关键字,就可创建并发执⾏单元。

    开发⼈员无需了解任何执⾏细节,调度器会自动将其安排到合适的系统线程上执行。

    在并发编程中,我们通常想将一个过程切分成几块,然后让每个 goroutine 各自负责一块工作,当一个程序启动时,主函数在一个单独的 goroutine 中运行,我们叫它 main goroutine。新的 goroutine 会用 go 语句来创建。而 go 语言的并发设计,让我们很轻松就可以达成这一目的。

    例如:

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func foo() {
    	i := 0
    	for true {
    		i++
    		fmt.Println("new goroutine: i = ", i)
    		time.Sleep(time.Second)
    	}
    }
    
    func main() {
    	// 创建一个 goroutine, 启动另外一个任务
    	go foo()
    
    	i := 0
    	for true {
    		i++
    		fmt.Println("main goroutine: i = ", i)
    		time.Sleep(time.Second)
    	}
    }
    

    结果:

    main goroutine: i =  1
    new goroutine: i =  1
    new goroutine: i =  2
    main goroutine: i =  2
    main goroutine: i =  3
    new goroutine: i =  3
    ...
    

    Goroutine 特性

    主go程 退出后,其它的 子go程 也会自动退出:

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func foo() {
    	i := 0
    	for true {
    		i++
    		fmt.Println("new goroutine: i = ", i)
    		time.Sleep(time.Second)
    	}
    }
    
    func main() {
    	// 创建一个 goroutine, 启动另外一个任务
    	go foo()
    
    	time.Sleep(time.Second * 3)
    
    	fmt.Println("main goroutine exit")
    }
    

    运行结果:

    new goroutine: i =  1
    new goroutine: i =  2
    new goroutine: i =  3
    main goroutine exit
    

    runtime 包

    Gosched

    runtime.Gosched() 用于出让当前 go程 所占用的 CPU 时间片,让出当前 goroutine 的执行权限,调度器安排其他等待的任务运行,并在下次再获得 cpu 时间轮片的时候,从该出让 cpu 的位置恢复执行。

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

    例如:

    package main
    
    import (
    	"fmt"
    	"runtime"
    	"time"
    )
    
    func main() {
    	// 创建一个 goroutine
    	go func(s string) {
    		for i := 0; i < 2; i++ {
    			fmt.Println(s)
    		}
    	}("world")
    
    	for i := 0; i < 2; i++ {
    		runtime.Gosched()
    		fmt.Println("hello")
    	}
    	time.Sleep(time.Second * 3)
    }
    

    运行结果:

    world
    world
    hello
    hello
    

    如果没有 runtime.Gosched() 则运行结果如下:

    hello
    hello
    world
    world
    

    注意: runtime.Gosched() 只是出让一次机会,看下面的代码,注意运行结果:

    package main
    
    import (
    	"fmt"
    	"runtime"
    	"time"
    )
    
    func main() {
    	// 创建一个 goroutine
    	go func(s string) {
    		for i := 0; i < 2; i++ {
    			fmt.Println(s)
    			time.Sleep(time.Second)
    		}
    	}("world")
    
    	for i := 0; i < 2; i++ {
    		runtime.Gosched()
    		fmt.Println("hello")
    	}
    }
    

    运行结果:

    world
    hello
    hello
    

    为什么 world 只有一次呢?因为之前我们说过,主 goroutine 退出后,其它的工作 goroutine 也会自动退出。

    Goexit

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

    注意与 return 的区别,return 是返回当前函数调用给调用者。

    例如:

    package main
    
    import (
    	"fmt"
    	"runtime"
    	"time"
    )
    
    func main() {
    	go func() {
    		defer fmt.Println("A.defer")
    		func() {
    			defer fmt.Println("B.defer")
    			runtime.Goexit() // 终止当前 goroutine
    			fmt.Println("B") // 不会执行
    		}()
    		fmt.Println("A") // 不会执行
    	}() // 不要忘记 ()
    
    	time.Sleep(time.Second * 3)
    }
    

    运行结果:

    B.defer
    A.defer
    

    GOMAXPROCS

    调用 runtime.GOMAXPROCS() 用来设置可以并行计算的 CPU 核数的最大值,并返回 上一次(没有则是电脑默认的) 设置的值。

    package main
    
    import (
    	"fmt"
    	"runtime"
    )
    
    func main() {
    	runtime.GOMAXPROCS(1)  // 将 cpu 设置为单核
    
    	for true {
    		go fmt.Print(0)  // 子 go 程
    		fmt.Print(1)  // 主 go 程
    	}
    }
    

    运行结果:

    111111 ... 1000000 ... 0111 ...
    

    在执行 runtime.GOMAXPROCS(1) 时,最多同时只能有一个 goroutine 被执行。所以会打印很多 1。过了一段时间后,GO 调度器会将其置为休眠,并唤醒另一个 goroutine,这时候就开始打印很多 0 了,在打印的时候,goroutine 是被调度到操作系统线程上的。

    package main
    
    import (
    	"fmt"
    	"runtime"
    )
    
    func main() {
    	runtime.GOMAXPROCS(2)
    
    	for true {
    		go fmt.Print(0)
    		fmt.Print(1)
    	}
    }
    

    运行结果:

    111111111111111000000000000000111111111111111110000000000000000011111111100000...
    

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

    runtime 包中的其它函数

    中文文档在这里:https://studygolang.com/pkgdoc

    这里就简单列举一下一些函数以及功能。

    func GOROOT() string
    

    GOROOT 返回 Go 的根目录。如果存在 GOROOT 环境变量,返回该变量的值;否则,返回创建 Go 时的根目录。


    func Version() string
    

    返回 Go 的版本字符串。它要么是递交的 hash 和创建时的日期;要么是发行标签如 “go1.3”。


    func NumCPU() int
    

    NumCPU返回本地机器的逻辑CPU个数(真 · 八核)。


    func GC()
    

    GC执行一次垃圾回收。(如果你迫切的希望做一次垃圾回收,可以调用此函数)


    其它的大家自行去文档查看吧~

    李培冠博客

    欢迎访问我的个人网站:

    李培冠博客:lpgit.com

    展开全文
  • go goroutine id

    2021-01-08 05:39:03
    很多情况,需要了解goroutine的执行情况,尤其在大量并发场景中,需要根据日志跟踪任务的执行情况,这个时候可以大致根据goroutine id来跟踪程序执行的状态。 在go语言中,没有获取goroutine id(简称goid)的API。 为...
  • goroutine [goroutine]是一个pkg,可以安全地使用golang goroutine。 为了得到很好的划分, goroutine被描述为golang官方对象,而[goroutine]被描述为此仓库。 宣言 [goroutine]没有重新设计另一个goroutine。 这...
  • pool - 一个有限的goroutine消费者或无限goroutine池用于轻松实现goroutine 处理和取消
  • add goroutine

    2020-12-25 22:18:37
    <div><p>added goroutine, please check it.</p><p>该提问来源于开源项目:benbjohnson/ego</p></div>
  • goroutine 协程

    2020-10-18 15:57:03
    goroutine说到底就是线程,但是它比线程更小,十几个 goroutine 可能体现在底层就是五六个线程,Go 语言内部帮你实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB),当然会根据相应...
  • goroutine基础

    2020-11-04 21:59:36
    一、goroutine是什么? 是go里的一种轻量级线程-协程 1、相对线程,协程的优势在于它非常轻量级,进行上下文切换的代价非常小 2、相对于一个goroutine,每个结构中有一个sched的属性就是用来保存它上下文的,这样...
  • Goroutine泄漏

    2020-03-03 13:22:45
    在Go中,goroutine很轻量级,随便创建成千上万个goroutine不是问题,但要注意,要是这么多的goroutine一致递增,而不退出,不释放资源,可就麻烦了。 本文介绍goroutine泄露的实际场景,并讨论如何解决该问题。 产生...
  • Goroutine介绍

    2019-07-19 21:06:42
    Goroutine: 在函数调用之前添加go关键字。自动并发。 如果主Goroutine先于子Goroutine结束运行。主动释放整个虚拟进程地址空间。 ****所有子Goroutine被强制结束**** 进程操作相关函数: Goroutine调度机制: // ...
  • Golang Goroutine的使用

    2021-01-19 23:38:44
    什么是 Goroutine goroutine 是 Go 并行设计的核心。goroutine 说到底其实就是协程,它比线程更小,十几个 goroutine 可能体现在底层就是五六个线程,Go 语言内部帮你实现了这些 goroutine 之间的内存共享。 执行 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 15,045
精华内容 6,018
热门标签
关键字:

goroutine