goroutine | 青训营笔记

146 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记

进程与线程

线程和进程的关系:每个进程都有相应的线程,在执行程序时,实际上是执行相应的一系列线程。进程是资源分配的最小单位,线程是程序执行的最小单位。
详细讲解:www.php.cn/faq/468521.…

并发与并行

并发:多线程程序在单核上运行
并行:多线程程序在多核上运行
详细讲解:blog.csdn.net/u011555996/…

Go协程和Go主线程

Go主线程:一个Go线程上,可以起多个协程,可以理解为轻量级的线程 测试案例

package main

import (
	"fmt"
	"strconv"
	"time"
)

// 编写一个函数,每隔一秒输出"hello,world"
func test(){
	for i := 0; i < 10; i++ {
		fmt.Println("hello,world"+strconv.Itoa(i))
		time.Sleep(time.Second)
	}
}
func main(){

	go test() //开启一个协程

	for i := 0; i < 10; i++ {
		fmt.Println("main hello,golang"+strconv.Itoa(i))
		time.Sleep(time.Second)
	}
}

执行结果 image.png 由输出的效果可知main主线程test协程同时执行

goroutine的调度模型

MPG模式

M(Machine):操作系统的主线程
P(Processor):协程执行需要的资源(上下文context),可以看作一个局部的调度器,使go代码在一个线程上跑,他是实现从N:1到N:M映射的关键
G(Gorountine):协程,有自己的栈。包含指令指针(instruction pointer)和其它信息(正在等待的channel等等),用于调度。一个P下面可以有多个G

查看,设置golang运行的CPU数

查看逻辑CPU数 cpuNum := runtime.NumCPU()
设置golang运行的CPU数 runtime.GOMAXPROCS(cpuNum)

全局互斥锁解决资源竞争

测试代码

package main

import (
	"fmt"
	"sync"
	"time"
)

var(
    myMap = make(map[int]int, 10)
    // 声明一个全局的互斥锁
    // lock 是一个全局的互斥锁
    // sync 是包:synchornized 同步
    // Mutex : 是互斥
    lock sync.Mutex
)

func test(n int){
    res := 1
    lock.Lock()
    for i := 1; i <= n; i++ {
        res *= i
    }
    lock.Unlock()

    // 加锁
    lock.Lock()
    myMap[n]=res //concurrent map writes
    // 解锁
    lock.Unlock()
}

func main(){
    for i := 1; i <= 20; i++ {
        go test(i)
    }

    time.Sleep(time.Second)

    lock.Lock()
    for i,v := range myMap {
        fmt.Printf("myMap[%v]=%d\n",i,v)
    }
    lock.Unlock()
}   

channel

  1. channel 本质就是一个数据结构-队列
  2. 线程安全,多goroutine访问时,不需要加锁
  3. channel时有类型的,一个string的channel只能存放string类型的数据 channel的基本使用1
    代码实现
package main

import(
	"fmt"
)

func main(){
	// 管道的使用
	// 1.创建一个可以存放3个int类型的管道
	var intChan chan int = make(chan int,3)

	// 2.看看intChan是什么
	fmt.Printf("intChan 的值=%v intChan本身的地址=%v\n",intChan,&intChan)

	// 3.向管道写入数据
	intChan<-10
	num := 211
	intChan<-num
	intChan<-29
	// intChan<-90 // 注意点,当我们给管道写入数据时,不能超过容量

	// 4.查看管道的长度和cap(容量)
	fmt.Printf("channel len=%v,cap=%v\n",len(intChan),cap(intChan)) //3,3

	// 5.从管道中读取数据
	var num2 int = <-intChan
	fmt.Println("num2=",num2)
	fmt.Printf("channel len=%v,cap=%v\n",len(intChan),cap(intChan)) //2,3

	// 6.在没有使用协程的情况下,如果将管道数据全部取出,再取就会报出 deadlock
	num3 := <-intChan
	num4 := <-intChan

	fmt.Println("num3=",num3,",num4=",num4)
	fmt.Printf("channel len=%v,cap=%v\n",len(intChan),cap(intChan)) //0,3

	// num5 := <-intChan //deadlock
	// fmt.Println(num5)
}

channel的基本使用2
关闭管道: func close(c chan<-Type)
代码实现

package main

import(
	"fmt"
)

func main(){

	intChan := make(chan int,3)
	intChan<-100
	intChan<-200
	close(intChan) //close
	// 这时不能再写入数到channel中
	// intChan<-30 //panic: send on closed channel
	
	// 当管道关闭后,读取数据是可以的
	n1 := <-intChan
	fmt.Println("n1=",n1)

	// 遍历管道
	intChan2 := make(chan int,100)
	for i := 0; i < 100; i++ {
		intChan2<-i*2
	}

	// 在遍历时 如果没有关闭channel,则会出现deadlock的错误
	close(intChan2)

	for v := range intChan2{
		fmt.Println("v=",v)
	}
}