协程; runtime.Gosched();

协程(Coroutine)

本质上是一种用户态线程,不需要操作系统来进行抢占式调度, 且在真正的实现中寄存于线程中,因此,系统开销极小,可以有效提高线程的任务并发 性,而避免多线程的缺点。使用协程的优点是编程简单,结构清晰;缺点是需要语言的 支持,如果不支持,则需要用户在程序中自行实现调度器。

执行体是个抽象的概念,在操作系统层面有多个概念与之对应,比如操作系统自己掌管的 进程(process)、进程内的线程(thread)以及进程内的协程(coroutine,也叫轻量级线程)。与 传统的系统级线程和进程相比,协程的大优势在于其“轻量级”,可以轻松创建上百万个而不 会导致系统资源衰竭,而线程和进程通常多也不能超过1万个。这也是协程也叫轻量级线程的 原因。

多数语言在语法层面并不直接支持协程,而是通过库的方式支持,但用库的方式支持的功能 也并不完整,比如仅仅提供轻量级线程的创建、销毁与切换等能力。如果在这样的轻量级线程中 调用一个同步 IO 操作,比如网络通信、本地文件读写,都会阻塞其他的并发执行轻量级线程, 从而无法真正达到轻量级线程本身期望达到的目标。

Go 语言在语言级别支持轻量级线程,叫goroutine。Go 语言标准库提供的所有系统调用操作 (当然也包括所有同步 IO 操作),都会出让 CPU 给其他goroutine。这让事情变得非常简单,让轻 量级线程的切换管理不依赖于系统的线程和进程,也不依赖于CPU的核心数量。

goroutine是Go语言中的轻量级线程实现,由Go运行时(runtime)管理。你将会发现,它的 使用出人意料得简单。 假设我们需要实现一个函数Add(),它把两个参数相加,并将结果打印到屏幕上,具体代码 如下: 
func Add(x, y int) {

   z := x + y

   fmt.Println(z)

 }

那么,如何让这个函数并发执行呢?具体代码如下: 
go Add(1, 1) 

 

如果没有在代码中通过runtime.GOMAXPROCS(n) (其中n是整数)指定使用多核的话,goroutins都是在一个线程里的,它们之间通过不停的让出时间片轮流运行,达到类似同时运行的效果。

当一个goroutine发生阻塞,Go会自动地把与该goroutine处于同一系统线程的其他goroutines转移到另一个系统线程上去,以使这些goroutines不阻塞。

 

runtime.Gosched()

untime.Gosched()用于让出CPU时间片。这就像跑接力赛,A跑了一会碰到代码runtime.Gosched()就把接力棒交给B了,A歇着了,B继续跑。

看代码:

package main

import (

    "fmt"

    "runtime"

)
 
func say(s string) {

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

        runtime.Gosched()

        fmt.Println(s)

    }

}

func main() {

    go say("world")

    say("hello")

}

输出结果:

hello

world

hello

注意结果:

1、先输出了hello,后输出了world.

2、hello输出了2个,world输出了1个(因为第2个hello输出完,主线程就退出了,第2个world没机会了)

把代码中的runtime.Gosched()注释掉,执行结果是:

hello

hello

因为say("hello")这句占用了时间,等它执行完,线程也结束了,say("world")就没有机会了。

这里同时可以看出,go中的goroutins并不是同时在运行。事实上,如果没有在代码中通过

runtime.GOMAXPROCS(n) 其中n是整数,

指定使用多核的话,goroutins都是在一个线程里的,它们之间通过不停的让出时间片轮流运行,达到类似同时运行的效果。

还需要学习一句话:

当一个goroutine发生阻塞,Go会自动地把与该goroutine处于同一系统线程的其他goroutines转移到另一个系统线程上去,以使这些goroutines不阻塞

你可能感兴趣的:(go)