Go语言goroutine,runtime

一.  goroutine

很多语言在进行并发编程的时候需要维护一个线程池,包装一个个的任务,并且需要自己去调度线程执行任务维护上下文的切换,而go语言为了我们提供了goroutine,我们可以定义很多的任务,让系统帮助我们把这些任务分配到cpu上实现并且执行,gorotinue类似于线程的概念,但是它是由go的运行时(runtime)调度和管理的,go程序会将goroutine中的任务合理地分配给每个cpu,在语言层面内置了调度和上下文切换的机制;在go语言的并发编程中我们只需要将任务包装成一个函数,开启一个goroutine去执行这个函数。如何使用goroutine呢?其实很简单,我们只需要在调用函数(普通函数和匿名函数)的前面加上go关键字,就可以为一个函数创建一个goroutine,一个goroutine对应一个函数,使用go关键字调用则开启了一个协程:

package main

import "fmt"

func f() {
	fmt.Println("f goroutine!")
}

func main() {
    // 开启一个goroutine执行函数f()
	go f()
	fmt.Println("main goroutine")
}

上面的例子中我们在调用f()函数的前面加上go关键字,开启一个goroutine去执行这个函数,但是发现只是打印了main函数中的字符串,没有打印f f goroutine!,为什么呢?其实在程序启动的时候go语言就为main()函数创建了一个默认的goroutine,当main()函数返回的时候该goroutine就结束了,所有在main()函数中启动的goroutine会一同结束,如何解决呢?一个最简单的一个方法是让main()函数等一下f()函数,可以使用time.sleep()函数让当前main()函数的goroutine停止一段时间,让f()函数的goroutine执行完,打印一个字符串可以在1s之内完成所以我们在控制台可以看到输出了两个字符串:

package main

import (
	"fmt"
	"time"
)

func f() {
	fmt.Println("f goroutine!")
}

func main() {
	go f()
	fmt.Println("main goroutine")
    // 让main()函数所在的goroutine阻塞一段时间让f()函数执行完
	time.Sleep(time.Second)
}

启动多个goroutine

通常一次会执行多个任务,那么这个时候就需要启动多个goroutine执行多个任务,下面的例子使用了sync.WaitGroup来实现goroutine的同步,每一次执行上面的代码发现输出的i都是不一样的,这是因为这10个goroutine是并发执行的,由goroutine的调度机制决定先执行哪个goroutine,而且在main函数中语句是从上往下执行的,而对于使用go关键字调用的函数是并发执行的,由go语言的调度机制决定执行哪一个goroutine,反正需要记住的一点是如果需要并发执行多个任务,则需要使用go关键字开启一个goroutine来执行一个任务,并且是由go语言的调度机制决定当前执行哪个goroutine,如果没有并发操作则正常调用函数即可:

package main

import (
	"fmt"
	"sync"
)

var wg sync.WaitGroup

func hello(i int) {
	defer wg.Done() // 当前函数执行完返回的时候将当前登记的goroutine-1
	fmt.Println("Hello Goroutine!", i)
}

func main() {
	for i := 0; i < 10; i++ {
		wg.Add(1) // 启动一个goroutine就登记+1
		go hello(i)
	}
	wg.Wait() // 等待所有登记的goroutine都结束
}

go语言中goroutine与操作系统线程之间的关系:一个操作系统线程对应用户态多个goroutine;go语言程序可以同时使用多个操作系统线程,goroutine与操作系统线程是多对多的关系。

二. runtime

1. runtime.Gosched()函数,让出cpu时间片,允许其他的goroutine执行,这个方法不会暂停当前的goroutine,所以在后面当前的goroutine会自动恢复运行:

package main

import (
	"fmt"
	"runtime"
)

func f() {
	for i := 0; i < 3; i++ {
		fmt.Println("hello")
	}
}

func main() {
	go f()
	for i := 0; i < 3; i++ {
		runtime.Gosched()
		fmt.Println("world")
	}
}

输出结果:

hello
hello
hello
world
world
world

2. runtime.Goexit()函数,结束当前的goroutine,不会影响到其他的goroutine,并且在结束当前的goroutine之前会运行完当前goroutine中所有的defer语句,如果在main()函数中调用Goexit()函数,当其他goroutine调用完成之后,由于main()函数没有返回最终导致整个程序崩溃:

package main

import (
	"fmt"
	"runtime"
	"time"
)

func main() {
	go func() {
		defer fmt.Println("A.defer")
		func() {
			defer fmt.Println("B.defer")
			// 结束协程
			runtime.Goexit()
			defer fmt.Println("C.defer")
			fmt.Println("B")
		}()
		fmt.Println("A")
	}()
	time.Sleep(time.Second)
}

3. runtime.GOMAXPROCS,go语言运行的时候调度机制根据参数GOMAXPROCS确定需要使用多少个的操作系统线程来同时执行go语言代码,默认值是机器上的cpu核心数,在一个具有8个cpu核心的机器上,调度器会把go语言代码同时调度到8个操作系统线程上,可以通过参数GOMAXPROCS设置当前程序并发执行的占用的cpu核心数,如果设置为1那么两个goroutine只有一个cpu核心,此时是先做完一个任务再做另外一个任务,如果设置为2两个任务并发执行,可以使用runtime.NumCPU()函数查看电脑的cpu的核心数:

package main

import (
	"fmt"
	"runtime"
	"time"
)

func a() {
	for i := 1; i < 1000; i++ {
		fmt.Println("A: ", i)
	}
}

func b() {
	for i := 1; i < 1000; i++ {
		fmt.Println("B: ", i)
	}
}

func main() {
	runtime.GOMAXPROCS(2)
	go a()
	go b()
	time.Sleep(time.Second)
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值