# Go语言 goroutine

下面的代码，在for里面调用函数前加上了go之后，使func进行并发执行。

```go
package main

import (
	"fmt"
	"time"
)

func main() {
	for i := 0; i < 1000; i++ {
		go func(i int) {
			for {
				fmt.Printf("Hello from "+
					"goroutine %d\n", i)
			}
		}(i)
	}
	time.Sleep(time.Minute)
}
```

* 上面的代码如果不加go执行的话，只会在第一次i=0的时候无限循环执行`fmt.Printf("Hello from "+"goroutine %d\n", i)`。
* 如果加上go,但是不加`time.Sleep(time.Minute)`的话，在func执行之前，for文就会结束，导致内部的func来不及执行。

**go开启的其实不是线程，而是协程。**

* 轻量级线程
* **非抢占式**多任务处理，由协程主动交出控制权
* 编译器/解释器/虚拟机层面的多任务（线程是系统级）
* 多个协程可能在一个或者多个线程上运行

### 非抢占式

关于非抢占式可以在看一个例子。

```go
func main() {
	var a [10]int
	for i := 0; i < 10; i++ {
		go func(i int) {
			for {
				a[i]++
			}
		}(i)
	}
	time.Sleep(time.Minute)
	fmt.Println(a)
}
```

这段代码执行以后我们会发现程序死机了。

主要原因有2个

* `a[i]++`无法主动交出控制权
* main函数自身也是一个协程，但是`time.Sleep(time.Minute)`因为没有人交出控制权，导致main一直等待。

从之前的代码可以看到，io操作时可以自动交出控制权的，如何手动交出控制权？可以使用`runtime.Gosched()`。**一般情况下不会用到`runtime.Gosched()`**。

```go
func main() {
	var a [10]int
	for i := 0; i < 10; i++ {
		go func(i int) {
			for {
				a[i]++
                runtime.Gosched()
			}
		}(i)
	}
	time.Sleep(time.Minute)
	fmt.Println(a)
}
```

### 关于闭包

还有一个点，可以看到for的里面的函数func定义了参数i，每次调用的时候都传入了i。理论上我们应该直接用i的。

如果我们去掉传参的处理执行的话发现会报错。

```go
func main() {
	var a [10]int
	for i := 0; i < 10; i++ {
		go func() {
			for {
				a[i]++
				runtime.Gosched()
			}
		}()
	}
	time.Sleep(time.Millisecond)
	fmt.Println(a)
}
```

这个时候我们可以用`go run -race goroutine.go`来看一下发生了什么。

```
==================
WARNING: DATA RACE
Read at 0x00c00008c008 by goroutine 6:
  main.main.func1()
      /Users/ding/go/coding-180/lang/goroutine/goroutine.go:16 +0x70

Previous write at 0x00c00008c008 by main goroutine:
  main.main()
      /Users/ding/go/coding-180/lang/goroutine/goroutine.go:11 +0x11b

Goroutine 6 (running) created at:
  main.main()
      /Users/ding/go/coding-180/lang/goroutine/goroutine.go:12 +0xf1
==================
```

出错的原因是因为如果不把i作为参数传给func，就不会形成闭包，在最后一次执行for的时候i会是10，导致a\[10]超出范围。你也可以把`var a [10]int`改成`var a [11]int`这样就不会报错了，但是这很不安全，所以不建议这样做。

### 协程和普通函数

{% tabs %}
{% tab title="普通函数" %}
![doWork执行完把控制权交还给main](https://3536919124-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LaJGzuTd3rg88xS__Wx%2F-LhPhcuGb6nu2z1UCnqc%2F-LhPiUEGyn4ER7ssQuSw%2Fimage.png?alt=media\&token=0a0fbf10-6a69-48a1-ae45-52832b0dd1c9)
{% endtab %}

{% tab title="协程" %}
![main函数和doWrok之间可以互相交换控制权](https://3536919124-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LaJGzuTd3rg88xS__Wx%2F-LhPhcuGb6nu2z1UCnqc%2F-LhPidL_8HXZU3bXsUV7%2Fimage.png?alt=media\&token=3480a1ae-9407-4ccc-a52f-eeec412e04f0)
{% endtab %}
{% endtabs %}

### 其他语言的协程支持

| 语言     | 协程                              |
| ------ | ------------------------------- |
| C++    | Boost.Coroutine                 |
| JAVA   | 不支持                             |
| Python | yield关键字 / async def(python3.5) |

### go语言的调度器

在go语言里，具体每个协程如何分配到线程里，都是交给调度器来分配，我们不用操心。

![](https://3536919124-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LaJGzuTd3rg88xS__Wx%2F-LhPhcuGb6nu2z1UCnqc%2F-LhPjXtSjmUi9IXhdGyz%2Fimage.png?alt=media\&token=7171c86b-5559-4e3a-a397-6196db3ce6e2)

### goroutine的定义

* 直接在函数前加入go
* 不需要再定义时区分是否是异步函数
* 调度器在合适的点进行切换
* 使用-race检测数据访问冲突

### goroutine的切换点

下面这些点可以参考，但是由于我们无法100%控制，所以只能是参考。

* I/O select
* channel
* 等待锁
* 函数调用（有时）
* runtime.Gosched()



















