@@ -1,58 +0,0 @@ 25.goroutine | 凤凰涅槃进阶之路

25.goroutine

Abel sun2023年1月6日约 1753 字大约 6 分钟

25.goroutine

1,前言

1,并行和并发

  • 并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。

65c600bcaf658755

  • 并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。

33c7114d42fb5432

  • 并行是两个队列同时使用两台咖啡机
  • 并发是两个队列交替使用一台咖啡机

ee2eedc9ebae698b

你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。 你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。 你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。

并发的关键是你有处理多个任务的能力,不一定要同时。 并行的关键是你有同时处理多个任务的能力。 所以我认为它们最关键的点就是:是否是**『同时』**。

http://t.cn/Ai94Gf7Nopen in new window

2,Go语言并发的优势

有人把Go比作21世纪的C语言,第一是因为Go语言设计简单,第二,21世纪最重要的就是并行程序设计,而Go从语言层面就支持了并发。同时,并发程序的内存管理有时候是非常复杂的,而Go语言提供了自动垃圾回收机制。

Go语言为并发编程而内置的上层API基于CSP(communicating sequential processes, 顺序通信进程)模型。这就意味着显式锁都是可以避免的,因为Go语言通过相册安全的通道发送和接受数据以实现同步,这大大地简化了并发程序的编写。

一般情况下,一个普通的桌面计算机跑十几二十个线程就有点负载过大了,但是同样这台机器却可以轻松地让成百上千甚至过万个goroutine进行资源竞争。

2,goroutine

1,goroutine是什么

goroutine是Go并行设计的核心。goroutine说到底其实就是协程,但是它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB),当然会根据相应的数据伸缩。也正因为如此,可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。

2,创建goroutine

只需在函数调⽤语句前添加 go 关键字,就可创建并发执⾏单元。开发⼈员无需了解任何执⾏细节,调度器会自动将其安排到合适的系统线程上执行。

在并发编程里,我们通常想讲一个过程切分成几块,然后让每个goroutine各自负责一块工作。当一个程序启动时,其主函数即在一个单独的goroutine中运行,我们叫它main goroutine。新的goroutine会用go语句来创建。

示例:

package main

import (
 "fmt"
 "time"
)

func Newtask() {
 for {
  fmt.Println("this is a Newtask")
  time.Sleep(time.Second) //表示延时1s
 }
}

func main() {

 go Newtask() //新建一个协程,一个任务

 for {
  fmt.Println("this is a main goroutine")
  time.Sleep(time.Second) //表示延时1s
 }
}

3,主协程退出,子协程也会退出

package main

import (
 "fmt"
 "time"
)

//如果主协程退出了,其他子协程也会跟着退出
func main() {
 go func() {
  i := 0
  for {
   i++
   fmt.Println("子协程 i = ", i)
   time.Sleep(time.Second)
  }
 }() //别忘了()进行调用

 i := 0
 for {
  i++
  fmt.Println("main i = ", i)
  time.Sleep(time.Second)

  if i == 2 {
   break
  }
 }
}

所以有时候会出现一种情况,就是程序运行之后什么都没有输出,有可能就是子协程还没来得及打印的时候,主协程就已经退出了。要注意这个点。

4,runtime包

1,Gosched

runtime.Gosched() 用于让出CPU时间片,让出当前goroutine的执行权限,调度器安排其他等待的任务运行,并在下次某个时候从该位置恢复执行。

这就像跑接力赛,A跑了一会碰到代码runtime.Gosched() 就把接力棒交给B了,A歇着了,B继续跑。

示例代码:

package main

import (
 "fmt"
 "runtime"
)

func main() {
 go func() {
  for i := 0; i < 5; i++ {
   fmt.Println("go")
  }
 }()

 for i := 0; i < 2; i++ {
  //表示让出时间片,别的协程执行完成之后,再来执行主协程
  runtime.Gosched()
  fmt.Println("hello")
 }

}

输出结果:

$ go run 4_Gosched的使用.go
go
go
go
go
go
hello
hello

如果不加runtime.Gosched()函数,那么结果会是如下:

$ go run 4_Gosched的使用.go
go
hello
go
hello

2,Goexit

调用 runtime.Goexit() 将立即终止当前 goroutine 执⾏,调度器确保所有已注册 defer 延迟调用被执行。

package main

import (
 "fmt"
 "runtime"
)

func test() {
 defer fmt.Println("ccc")
 //return //表示终止次函数
 runtime.Goexit() //表示终止所在的协程

 fmt.Println("ddd")
}

func main() {
 //创建新的协程
 go func() {
  fmt.Println("aaa")
  //调用别的函数
  test()
  fmt.Println("bbb")
 }()

 //特地写一个死循环,目的不让主协程结束
 for {

 }
}

3,GOMAXPROCS

调用 runtime.GOMAXPROCS() 用来设置可以并行计算的CPU核数的最大值,并返回之前的值。

package main

import (
 "fmt"
 "runtime"
)

func main() {
 n := runtime.GOMAXPROCS(4) //指定以单核运算
 fmt.Println("n = ", n)

 for {
  go fmt.Print(1)

  fmt.Print(0)
 }

}

在第一次执行(runtime.GOMAXPROCS(1))时,最多同时只能有一个goroutine被执行。所以 会打印很多1。过了一段时间后,GO调度器会将其置为休眠,并唤醒另一个goroutine,这时候就开始打印很多0了,在打印的时候,goroutine是被调度到操作系统线程上的。

在第二次执行(runtime.GOMAXPROCS(2))时,我们使用了两个CPU,所以两个goroutine可以一起被执行,以同样的频率交替打印0和1。

4,sync.WaitGroup

还有一种方式是利用sync包内的一个方法来实现:

package main

import (
 "fmt"
 "math/rand"
 "sync"
 "time"
)

var wg sync.WaitGroup

func f1(i int) {
 defer wg.Done()  // goroutine结束就登记-1
 time.Sleep(time.Second * time.Duration(rand.Intn(3)))
 fmt.Println(i)
}

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

5,多协程导致资源竞争问题

package main

import (
 "fmt"
 "time"
)

//定义一个打印机,参数为字符串,按每个字符串打印
//打印机属于公共资源
func Printer(str string) {
 for _, data := range str {
  fmt.Printf("%c", data)
  time.Sleep(time.Second)
 }
 fmt.Printf("\n")
}

func person1() {
 Printer("hello")
}
func person2() {
 Printer("world")
}

func main() {
 //新建2个协程,代表2个人,2个人同时使用打印机
 go person1()
 go person2()
 //特地不让主协程结束,创建一个死循环
 for {

 }
}

输出如下结果:

$ go run 7_多任务导致资源竞争问题.go
whoerllldo

exit status 2
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.9.1