# 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](/files/-LhPiUEGyn4ER7ssQuSw)
{% endtab %}

{% tab title="协程" %}
![main函数和doWrok之间可以互相交换控制权](/files/-LhPidL_8HXZU3bXsUV7)
{% endtab %}
{% endtabs %}

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

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

### go语言的调度器

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

![](/files/-LhPjXtSjmUi9IXhdGyz)

### goroutine的定义

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

### goroutine的切换点

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

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://dingyj.gitbook.io/blog/golang/basic/go-yu-yan-xue-xi-bi-ji/g-yu-yan-goroutine.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
