Go语言之初识Goroutine

2020-07-09 00:00:00 线程 时间 编程 并发 资源

1. Go的并发机制

面对前来面试Java程序员的求职者的时候我一般都会问问并发编程,毕竟并发编程总是属于Java编程的部分。其实Java的并发编程已经被设计的很简单了,不过要想掌握并发编程也并不是一件很轻松的事情。

作为一个DBA,面对高并发的场景算是司空见惯的了,没有并发能力的数据库(对,我说的就是MyISAM),还不如玩具。

那么数据库并发的时候重要的几个点是什么呢?

  • 锁,分为排他锁和共享锁,我认为这是个天才的设计;
  • 连接池,使用了连接池可以大大降低高并发时的负载;
  • 索引,索引能大幅提升查询效率,降低锁持有的时间。

其实说了这三样,还是锁重要,锁保护了并发时出现竞态时的资源安全。实际上在Java编程的时候也总是说什么线程安全之类的概念,无非也就是对竞争状态下资源的保护。

Go作为一个现代的语言,其并发模型也是很简单的,这一点确实比Python好,很多人认为Python是没有并发编程的,这也不能算是全错,至少Python的并发能力和Java比起来还是差不少。

扯了这么多闲话,画一张图来说明一下Go的并发:



  • G4到G7代表着goroutine,一旦创建出来就会被分配给一个逻辑处理器,这里记为P0;
  • 逻辑处理器P0被绑定到一个系统线程上;
  • 逻辑处理器是可以在代码里分配的。

这个模型看起来是还是怪怪的,因为我们知道计算机里有个很重要的概念叫做中断,大致意思是CPU每个时间片只能处理一件事,但是CPU可以分配时间片,即处理一会儿任务A,然后中断,分配一些时间片给任务B,因为切换时间太快,反应迟钝的人是反应不过来的,还以为计算机是并行处理的。

那么逻辑处理器要如何进行并发呢,这看起来还是不像并发的样子。事实上Go提供了这样的能力,不然还谈什么并发编程。

Go遇到需要阻塞的goroutine,就会分配一个新的线程,将这个goroutine和原来的线程分离,直到该阻塞线程有返回。这段时间里,逻辑处理器P0继续做队列里其他的事情。

2. 编码实现

下面来写一段代码展示如何实现并发:

package main

import (
    "fmt"
    "runtime"
    "sync"
)

var wg sync.WaitGroup

func main() {
    runtime.GOMAXPROCS(1)

    wg.Add(2)

    fmt.Println("Start........")

    go printPrime("A")
    go printPrime("B")

    fmt.Println("Waiting to finish")
    wg.Wait()
    fmt.Println("Done!")
}

func printPrime(prefix string) {
    defer wg.Done()

    next:
        for outer := 2; outer < 50000; outer++ {
            for inner := 2; inner < outer; inner++ {
                if outer % inner ==  {
                    continue next
                }
            }
            fmt.Printf("%s: %d\n", prefix, outer)
        }
        fmt.Printf("Finish %s\n", prefix)
}

相关文章