• go goroutine


    进程和线程

    • 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的 一个独立单位。
    • 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更 小的能独立运行的基本单位。
    • 一个进程可以创建和撤销多个线程;同一个进程中的多个线程之间可以并发执行。

    并发和并行

    • 多线程程序在一个核的cpu上运行,就是并发
    • 多线程程序在多个核的cpu上运行,就是并行

    协程和线程

    • 协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于 用户级线程,这些用户级线程的调度也是自己实现的。
    • 线程:一个线程上可以跑多个协程,协程是轻量级的线程。

    可以说,协程与线程主要区别是它将不再被内核调度,而是交给了程序自己而线程是将自己交给内核调度,所以也不难理解golang中调度器的存在。

    示例:

    package main
    
    import "fmt"
    import "time"
    
    func test() {
    	var i int
    	for {
    		fmt.Println(i)
    		time.Sleep(time.Second)
    		i++
    	}
    }
    
    func main() {
    	go test()
    	for {
    		fmt.Println("i' running in main")
    		time.Sleep(time.Second)
    	}
    }
    

    goroutine调度模型

    golang的goroutine是如何实现的?  知乎上一篇介绍文章。

    • M: 代表真正的内核OS线程,和POSIX里的thread差不多,真正干活的人。
    • G: 代表一个goroutine,它有自己的栈,instruction pointer和其他信息(正在等待的channel等等),用于调度。
    • P: 代表调度的上下文,可以把它看做一个局部的调度器,使go代码在一个线程上跑,它是实现从N:1到N:M映射的关键。

    图中看,有2个物理线程M,每一个M都拥有一个context(P),每一个也都有一个正在运行的goroutine。
    P的数量可以通过GOMAXPROCS()来设置,它其实也就代表了真正的并发度,即有多少个goroutine可以同时运行。
    图中灰色的那些goroutine并没有运行,而是出于ready的就绪态,正在等待被调度。P维护着这个队列(称之为runqueue)。

    图中看到,当一个OS线程M0陷入阻塞时,P转而在OS线程M1上运行。调度器保证有足够的线程来运行所以的context P。

     三者关系的宏观的图为:

    如何设置golang运行的cpu核数

    package main
    
    import (
    	"fmt"
    	"runtime"
    )
    
    func main() {
    	num := runtime.NumCPU()
    	runtime.GOMAXPROCS(num)
    	fmt.Println(num)
    }
    

    备注:go1.8版本之后可以不用设置,默认使用所有CPU核数。

    锁示例:

    package main
    
    import (
    	"fmt"
    	"sync"
    	"time"
    )
    
    var (
    	m    = make(map[int]uint64)
    	lock sync.Mutex
    )
    
    type task struct {
    	n int
    }
    
    func calc(t *task) {
    	var sum uint64
    	sum = 1
    	for i := 1; i < t.n; i++ {
    		sum *= uint64(i)
    	}
    
    	fmt.Println(t.n, sum)
    	lock.Lock()
    	m[t.n] = sum
    	lock.Unlock()
    }
    
    func main() {
    	for i := 0; i < 16; i++ {
    		t := &task{n: i}
    		go calc(t)
    	}
    
    	time.Sleep(10 * time.Second)
    	lock.Lock()
    	for k, v := range m {
    		fmt.Printf("%d! = %v
    ", k, v)
    	}
    	lock.Unlock()
    }
    

    goroutine中使用recover

    应用场景,如果某个goroutine panic了,而且这个goroutine里面没有 捕获(recover),那么整个进程就会挂掉。所以,好的习惯是每当go产 生一个goroutine,就需要写下recover。

    package main
    
    import (
    	"fmt"
    	"runtime"
    	"time"
    )
    
    func test() {
    
    	defer func() {
    		if err := recover(); err != nil {
    			fmt.Println("panic:", err)
    		}
    	}()
    
    	var m map[string]int
    	m["stu"] = 100
    }
    
    func calc() {
    	for {
    		fmt.Println("i'm calc")
    		time.Sleep(time.Second)
    	}
    }
    
    func main() {
    	num := runtime.NumCPU()
    	runtime.GOMAXPROCS(num - 1)
    	go test()
    	for i := 0; i < 2; i++ {
    		go calc()
    	}
    
    	time.Sleep(time.Second * 10000)
    }
    

    Go并发原理 https://i6448038.github.io/2017/12/04/golang-concurrency-principle/

    Golang非CSP并发模型外的其他并行方法总结 https://i6448038.github.io/2018/12/18/Golang-no-csp/

     

  • 相关阅读:
    现身说法“好奇心害死人啊”
    代码错误集合(全是低级错误,欢迎高手前来指教)
    Return from TAOKEE
    泡了DOUBAN一个下午,思考中。。。
    买了两本书
    PDFBox,PDF文件处理
    数据库营销,DM杂志
    一个JS写的时间选择显示的控件,源文件下载
    WEB2.0新想法,让明星还有你无所遁行,让你我都做“狗仔队”
    hdu 1237 简单计算器 (栈的简单应用)
  • 原文地址:https://www.cnblogs.com/shhnwangjian/p/7490280.html
Copyright © 2020-2023  润新知