Go并发

发布于 2019-05-23 · 本文总共 2199 字 · 阅读大约需要 7 分钟

GMP

M(Machine): Work Thread,代表着真正的执行计算资源,可以认为它就是os thread(系统线程),是被操作系统管理的线程,与POSIX中的标准线程非常类似。

P(Processor): 表示逻辑processor,是线程M的执行的上下文,可以被看做一个运行于线程M上的本地调度器。

G(Goroutine): 调度系统的最基本单位goroutine,存储了goroutine的执行stack信息、goroutine状态以及goroutine的任务函数等。

三者关系:

每一个运行的M都必须绑定一个P,线程M创建后会去检查并执行G(goroutine)对象

每一个P保存着一个协程G的队列

除了每个P自身保存的G的队列外,调度器还拥有一个全局的G队列

M从队列中提取G,并执行

P的个数就是GOMAXPROCS(最大256),启动时固定的,一般不修改 (一般将 GOMAXPROCS 的个数设置为 CPU 的核数,且需要注意的是:go 1.5 版本之前的GOMAXPROCS默认是 1;go 1.5 版本之后的GOMAXPROCS默认是Num of cpu)

M的个数和P的个数不一定一样多(会有休眠的M或P不绑定M) (最大10000)

P是用一个全局数组(255)来保存的,并且维护着一个全局的P空闲链表

go并发

多进程-多线程-协程

  • 多进程。多进程是在操作系统层面进行并发的基本模式。同时也是开销最大的模式。

  • 多线程。多线程在大部分操作系统上都属于系统层面的并发模式,也是我们使用最多的、最有效的一种模式。 目前,我们所见的几乎所有工具链都会使用这种模式。 它比多进程 的开销小很多,但是其开销依旧比较大,且在高并发模式下,效率会有影响。

  • 协程。协程(Coroutine)本质上是一种用户态线程,不需要操作系统来进行抢占式调度,且在真正的实现中寄存于线程中, 因此,系统开销极小,可以有效提高线程的任务并发性,而避免多线程的缺点。

协程

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

“不要通过共享内存来通信,而应该通过通信来共享内存。”

Do not communicate by sharing memory; instead, share memory by communicating.

//thread.go
package main
import "fmt"
import "sync"
import "runtime"
var counter int = 0
func Count(lock *sync.Mutex) { lock.Lock()
        counter++
        fmt.Println(z)
        lock.Unlock()
}
func main() {
lock := &sync.Mutex{}
   for i := 0; i < 10; i++ { go Count(lock)
}
for { lock.Lock()
c := counter
    lock.Unlock()
runtime.Gosched() if c >= 10 {
  } }
}

channel

// channel.go
package main
import "fmt"
func Count(ch chan int) {
  ch <- 1
  fmt.Println("Counting")
}
func main() {
  chs := make([]chan int 10)
  for i := 0; i < 10; i++ {
    chs[i] = make(chan int)
    go Count(chs[i])
  }
  for _, ch := range(chs) {
      <-ch
  }
}

单向channel

单向channel变量的声明非常简单,如下:

var ch1 chan int // ch1是一个正常的channel,不是单向的

var ch2 chan<- float64// ch2是单向channel,只用于写float64数据

var ch3 <-chan int // ch3是单向channel,只用于读取int数据

关闭channel

关闭channel非常简单,直接使用Go语言内置的close()函数即可: close(ch)

在介绍了如何关闭channel之后,我们就多了一个问题: 如何判断一个channel是否已经被关闭?我们可以在读取的时候使用多重返回值的方式:

 x, ok := <-ch

这个用法与map中的按键获取value的过程比较类似,只需要看第二个bool返回值即可,如 果返回值是false则表示ch已经被关闭。

全局唯一性操作

type Manager struct{}

var m *Manager

var once sync.Once

func GetInstance() *Manager {
	once.Do(func() {
		m = &Manager{}
	})
	return m
}

func (p *Manager) Manage() {
	fmt.Println("manage...")
}

无缓冲 Chan 的发送和接收是否同步

channel无缓冲时,发送阻塞直到数据被接收,接收阻塞直到读到数据。

channel有缓冲时,当缓冲满时发送阻塞,当缓冲空时接收阻塞。




本博客所有文章采用的授权方式为 自由转载-非商用-非衍生-保持署名 ,转载请务必注明出处,谢谢。
声明:
本博客欢迎转发,但请保留原作者信息!
博客地址:邱文奇(qiuwenqi)的博客;
内容系本人学习、研究和总结,如有雷同,实属荣幸!
阅读次数:

文章评论

comments powered by Disqus


章节列表