GO语言goroutine

前言

        go语言对于并发编程有原生的支持。

        如下代码:创建10个协程并发执行。

package main

import (
	"fmt"
	"time"
)

func main() {
	for i := 0; i < 10; i++ {
		go func(i int) {
			fmt.Printf("hello from goroutine %d\n", i)
		}(i)
	}
	//main函数执行完,协程也会被终止,等待1秒,协程执行完
	time.Sleep(time.Second)
}

Courotine 协程:

1. go语言中开辟的实际是协程,协程是轻量级线程。作用和线程差不多,并发执行任务。

        在go语言中可以开辟很多协程,比如:1000个协程,但是开辟1000个线程消耗的资源就比较多了。

2. 协程为什么是轻量级线程。

        go语言中的协程是非抢占式多任务处理,由协程主动交出控制权。

        抢占式式多任务处理:操作系统由更高优先级的任务,可以停止当前执行的线程,去执行更高优先级线程。

        非抢占式多任务处理:操作系统不能主动去停止。而是由任务主动交出控制权。

        线程是抢占式多任务处理,所以等任务切换时,需要内存来保存程序执行的上下文。

        协程不需要那麽多内存来保存上下文,只需要保存切换点。

        协程是编译器/解释器/虚拟机层面的多任务,不是操作系统层面的多任务。操作系统层面没有协程,

        所以协程是轻量级线程。

3. 多个协程可能在一个或者多个线程上执行。

        我们知道操作系统有调度器,而go语言也有自己的调度器,来调度协程的运行。所以可能协程在不同的一个或者一个线程上运行。

参数传递给协程的注意点:

代码:

        每次开辟的协程参数i需要被传入,如果不传入,会出现越界错误。

        因为使用的i在全局只有一份,当i被加到10时,有的协程还没有运行完或者正在运行,此时i时同一个,再执行a[i]时,就越界了,作为参数传入,i会被拷贝一份,每个协程私有。

package main

import (
	"fmt"
	"time"
)

func main() {
	var a [10]int
	for i := 0; i < 10; i++ {
		go func(i int) {
			for {
				a[i]++
			}
		}(i)
        // 会吹按越界错误
		// go func() {
		// 	for {
		// 		a[i]++
		// 	}
		// }()
	}
	//main函数执行完,协程也会被终止,等待1秒,协程执行完
	time.Sleep(time.Second)
	fmt.Println(a)
}

可以在命令行中可以输入go run -race xxx, 查看访问冲突。

GO语言goroutine_第1张图片

 

go语言的调度器

        子程序(普通函数)是协程的特例。

GO语言goroutine_第2张图片

  1.  任何函数前加上go关键字,就能送给调度器调度,
  2.  虽然协程是非抢占式的,但是为了提高效率,调度器会在合适的点进行切换,不需要显示去切换。

一般的切换点:并不保存会切换,也不能保证在别的地方不切换。

  • I/O操作,Select
  • channel
  • 等待锁
  • 函数调用(有时)
  • runtime.Gosched(),显示切换

go语言的协程可能运行在一个线程里,也可能多个协程运行在一个线程里,也有可能多个协程在多个线程里,这由go语言调度器来决定。

GO语言goroutine_第3张图片

 

你可能感兴趣的:(golang,goroutine,go)