Go语言学习笔记19.并发编程

前置知识点:(如果不了解建议百度下再学并发编程)

  • 并发、并行,同时进行叫并行,交替进行叫并发(时间片轮循)。
  • 进程、线程、协程
  • 进程的状态,挂起、休眠、运行等等
  • 进程的关系,比如子进程、子线程、孤儿进程、僵尸进程,守护进程等等。

协程 goroutine

从创建的消耗来比较,进程>线程>协程。
有很多时候我们只是想多个座位,结果却不得不去盖一栋房子。而协程就是这个凳子,进程就是房子。而进程和线程的消耗差距比这个要大几千倍。

创建

go func(){
}
package main

import (
	"fmt"
	"time"
)

func newTask() {
	index := 1
	fmt.Println("协程创建了")
	for {
		time.Sleep(time.Second * 2) //延时2s
		fmt.Println("协程 index",index)
		index ++
	}
}

func main() {

	go newTask() //新建一个协程, 新建一个任务
	index := 1
	//死循环
	for {
		time.Sleep(time.Second) //延时1s
		fmt.Println("主进程 index",index)
		index ++
	}
}

输出:

协程创建了
主进程 index 1
协程 index 1
主进程 index 2
主进程 index 3
协程 index 2
主进程 index 4
主进程 index 5
协程 index 3
主进程 index 6
主进程 index 7
协程 index 4
主进程 index 8
...

和创建子进程或子线程是一样的逻辑,互不干扰。
当主进程或主协程停止的时候,子协程也会停止。

相关函数

  1. runtime.Gosched() 让出时间片
	go func() {
		for i := 0; i < 5; i++ {
			fmt.Println("go")
		}
	}()

	for i := 0; i < 2; i++ {
		//让出时间片,先让别的协议执行,它执行完,再回来执行此协程
		runtime.Gosched()
		fmt.Println("hello")
	}
  1. runtime.Goexit() 终止当前 goroutine 执⾏,调度器确保所有已注册 defer延迟调用被执行。
package main

import (
	"fmt"
	"runtime"
)

func test() {
	defer fmt.Println("协程结束")

	//return //终止此函数
	runtime.Goexit() //终止所在的协程

	fmt.Println("这句话不会被打印")
}

func main() {

	//创建新建的协程
	go func() {
		fmt.Println("协程开始前")

		//再创建一个
		test()

		runtime.Gosched()
		fmt.Println("协程创建了")
	}() //别忘了()
	
	//死循环
	for  {}
}
  1. runtime.GOMAXPROCS() 设置可以并行计算的CPU核数的最大值,并返回之前的值。
    比如有两个协程,设置1,就会交替执行,如果设置2,就会一起执行。
	n := runtime.GOMAXPROCS(2) //指定以1核运算
	//n := runtime.GOMAXPROCS(4) //指定以4核运算
	fmt.Println("n = ", n)

	for i:=1;i<100000;i++{
		go fmt.Print(1)
		fmt.Print(0)
	}

从打印看效果不是很明显,但是可以用耗时任务来看。

package main

import (
	"fmt"
	"runtime"
	"time"
)

func main() {
	n := runtime.GOMAXPROCS(2)
	fmt.Println("n = ", n)

	go func(){
		for { //抢光所有资源
		}	
	}()
	for i:=1;i<100000;i++{
		fmt.Print(0)
		time.Sleep(time.Second)
	}	
}

这个就很明显了,如果只用1核,最多打印一个0,然后sleep,资源就全部被协程抢了,再也打印不出来了。但是如果用2核,则可以打印出来。
有人可能会说,不是说交替执行么,但是这里面的协程是死循环,事情一直没有完成,而主进程的sleep会表示我还可以等,则CPU不会让出时间片。
如果都是死循环会怎么样?会交替执行,因为主进程说,我也有急事,CPU则会让他们各自执行一段时间。

channel

之前的进程、线程之间,会通过共享内存来通信,但是数据都是存放在各自的家里。但是协程则是通过通信来共享内存。
channel本意是管道,控制并发最好的方法是什么?排队啊!管道就是将所有人的任务按顺序执行。

创建:
make(chan Type, capacity) capacity表示缓存区大小
关闭:
close(chan Type)

package main

import (
	"fmt"
	"time"
)

//定义一个打印机,参数为字符串,按每个字符打印
//打印机属于公共资源
func Printer(str string) {
	for _, data := range str {
		fmt.Printf("%c", data)
		time.Sleep(time.Second)
	}
	fmt.Printf("\n")
}

func person1() {
	Printer("hello")
}

func person2() {
	Printer("world")
}

//全局变量,创建一个channel
var ch = make(chan int)

//person3执行完后,才能到person4执行
func person3() {
	Printer("HELLO")
	ch <- 666 //给管道写数据,发送
	close(ch)
}

func person4() {
	//从管道取数据,接收,如果通道没有数据他就会阻塞,所有前面的会先执行
	if num, ok := <-ch; ok == true {
		fmt.Println("num = ", num)
	} else { //管道关闭
		fmt.Println("管道关闭了")
	}
	Printer("WORLD")
}

func main() {
	//新建2个协程,代表2个人,2个人同时使用打印机
	//没有管道的情况,会抢  输出:hwoelrllod
	//go person1()
	//go person2()

	//有管道的情况,顺序执行: 
	/*
	HELLO
	num =  666
	WORLD
	*/
	go person3()
	go person4()
	
	//特地不让主协程结束,死循环
	for {

	}
}

管道的缓冲
无缓冲,则必须要发送方和接收方同时在才可以继续,否则发送方发了一个后就会阻塞。
有缓存,在缓冲大小内,发送方可以一直发,直到缓冲区满,接收方什么时候来收都可以。

    //创建一个无缓存的channel
	ch := make(chan int, 0)
	//创建一个有缓存的channel
	//ch := make(chan int, 3)

	//len(ch)缓冲区剩余数据个数, cap(ch)缓冲区大小
	fmt.Printf("len(ch) = %d, cap(ch)= %d\n", len(ch), cap(ch))

	//新建协程
	go func() {
		for i := 0; i < 3; i++ {
			fmt.Printf("子协程:i = %d\n", i)
			ch <- i //往chan写内容
		}
	}()

	//延时,没有缓存则发送方也会等待
	time.Sleep(2 * time.Second)

	for i := 0; i < 3; i++ {
		num := <-ch //读管道中内容,没有内容前,阻塞
		fmt.Println("num = ", num)
	}

管道的方向限定

	//创建一个channel, 双向的
	ch := make(chan int)

	//双向channel能隐式转换为单向channel
	var writeCh chan<- int = ch //只能写,不能读
	var readCh <-chan int = ch  //只能读,不能写
	
	writeCh <- 666 //写
	<-readCh //读

你可能感兴趣的:(Go语言学习笔记19.并发编程)