设为首页 加入收藏

TOP

goroutine调度(二)
2023-07-23 13:29:03 】 浏览:84
Tags:goroutine 调度
d) // 打印3次字母 for count := 0; count < 3; count++ { for ch := 'a'; ch < 'a'+26; ch++ { if count == 0 { time.Sleep(10 * time.Millisecond) } if count == 1 { time.Sleep(30 * time.Millisecond) } if count == 2 { time.Sleep(50 * time.Millisecond) } fmt.Printf("%c ", ch) } fmt.Println() } }() // 开一个go协程打印数字 go func() { defer wg.Done() // 打印3次数字 for count := 0; count < 3; count++ { for n := 1; n <= 26; n++ { if count == 0 { time.Sleep(20 * time.Millisecond) } if count == 1 { time.Sleep(40 * time.Millisecond) } if count == 2 { time.Sleep(60 * time.Millisecond) } fmt.Printf("%d ", n) } fmt.Println() } }() // 等待返回 fmt.Println("Waiting To Finish") wg.Wait() fmt.Println("\nTerminating Program") }

看下结果:

go run main.go
Starting Goroutines
Waiting To Finish
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 
1 2 3 4 5 6 7 8 9 10 11 a 12 b c d e 13 f g h i 14 j k l m 15 n o p 16 q r s t 17 u v w x 18 y z 
19 a b 20 c 21 d 22 e f 23 g 24 h 25 i j 26 
k l 1 m n 2 o p 3 q r 4 s t 5 u v 6 w x 7 y z 
8 a 9 b 10 c 11 d 12 e f 13 g 14 h 15 i 16 j 17 k l 18 m 19 n 20 o 21 p 22 q r 23 s 24 t 25 u 26 
v w x y z 

Terminating Program

通过上面的结果我们可以看到,当goroutine1阻塞时,go调度器会调度goroutine2执行。

我们可以得出:

  • 即使我们将 runtime.GOMAXPROCS(1) 设置为 1,程序也在并发运行
  • Running 状态的 Goroutine 数量最大为 1,Block Goroutine 可以多于一个,其他所有 Goroutine 都处于 Runnable 状态

3、线程池

  • 在需要时创建一个线程,这意味着如果有 goroutine 要运行但所有其他线程都忙,则创建一个线程
  • 一旦线程完成其执行而不是销毁重用它
  • 这可以更快的创建goroutine,因为我们可以重用线程
  • 但是还有更多的内存消耗,性能问题,并且没有无限堆栈。

4、M: N 线程共享运行队列调度(GMP)

  • M代表系统线程的数量
  • N代表goroutine的数量
  • goroutine 的创建成本很低,我们可以完全控制 goroutine 的整个生命周期,因为它是在用户空间中创建的
  • 创建一个操作系统线程很昂贵,我们无法控制它,但是使用多个线程我们可以实现并行
  • 在这个模型中,多个 goroutine 被多路复用到内核线程中

我们上面提到过导致goroutine阻塞调用可能是下面一些原因:

  • 在channel中收发数据
  • 网络IO调用
  • 阻塞的系统调用
  • 计时器
  • 互斥操作(Mutex)

下面看一些goroutine阻塞的例子:

package main

 import (
     "time"
     "fmt"
     "sync"
     "os"
     "net/http"
     "io/ioutil"
 )

 // 全局变量
 var worker int

 func writeToFile(wg *sync.WaitGroup,){
     defer wg.Done()
      
     file, _ := os.OpenFile("file.txt", os.O_RDWR|os.O_CREATE, 0755)             // 系统调用阻塞
     resp, _ := http.Get("https://blog.waterflow.link/articles/1662706601117") // 网络IO阻塞
     body, _ := ioutil.ReadAll(resp.Body)                                        // 系统调用阻塞

     file.WriteString(string(body))
 }

 func workerCount(wg *sync.WaitGroup, m *sync.Mutex, ch chan string) { 
     // Lock() 给共享资源上锁
     // 独占访问状态, 
     // 增加worker的值,
     // Unlock() 释放锁
     m.Lock()                                                                    // Mutex阻塞
     worker = worker + 1
     ch <- fmt.Sprintf("Worker %d is ready",worker)
     m.Unlock()
    
     // 返回, 通知WaitGroup完成
     wg.Done()
 }

 func printWorker(wg *sync.WaitGroup, done chan bool, ch chan string){
      
     for i:=0;i<100;i++{
         fmt.Println(<-ch)                                               // Channel阻塞
     }
     wg.Done()
     done <-true
 }

 func main() {
      
     ch :=make(chan string)
     done :=make(chan bool)
      
     var mu sync.Mutex
      
     var wg sync.WaitGroup
      
     for i:=1;i<=100;i++{
         wg.Add(1)
         go workerCount(&wg,&mu,ch)
     }
      
     wg.Add(2)
     go writeToFile(&wg)
     go printWorker(&wg,done,ch)
      
     wg.Wait()
      
     <-done                                                             // Channel阻塞
      
     <-time.After(1*time.Second)                                        // Timer阻塞
     close(ch)
     close(done)
 }

下面我们看看go调度器在上面这些例子中是如何工作的:

  • 如果一个 goroutine 在通道上被阻塞,则通道有等待队列,所有阻塞的 goroutine 都列在等待队列中,并且很容易跟踪。 在阻塞调用之后,它们将被放入 schedular 的全局运行队列中,OS Thread 将再次按照 FIFO 的顺序选择 goroutine。
    http://image-1313007945.cos.ap-nanjing.myqcloud.com/image/1662974630.gif
  1. M1,M2,M3尝试从全局G
首页 上一页 1 2 3 4 下一页 尾页 2/4/4
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Golang GMP原理(2) 下一篇分享Go书籍-《Go Web编程》

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目