从零开始Golang开发(5)

144 阅读3分钟

 协程:

CPU同时只能处理一个进程或线程

问题:

1、单一执行流程、计算机只能一个任务一个任务的处理

2、进程阻塞所带来的CPU浪费是不可避免的

多进程/多线程解决了阻塞的问题

但面临了新的问题

进程/线程数量越多,切换成本越大,更加浪费

并且多线程随着同步竞争(如锁、竞争资源冲突等)开发设计变得越来越复杂

弊端:高消耗成本,高内存占用

 

 

 N:1关系

 M:N关系

 Golang对协程的处理:

 

老调度器缺点:

1、创建、销毁、调度G都需要每一个M获取锁,这就形成了激烈的锁竞争

2、M转移G会造成延迟和额外的系统负载

3、系统调用(CPU在M之间的切换)导致频繁的线程阻塞和取消阻塞操作增加了系统的开销

  

 调度器设计策略:

1、复用线程

work stealing机制

M2拿了M1的协程

hand off机制

​ 

不耽误G2,用新的线程执行G2,M1继续阻塞G1

2、并行利用

GOMAXPROCS限定P的个数= CPU核数/2

3、抢占

 

​ 

 保证了Go并发的特性

4、全局G队列

基于work stealing机制

空闲的M先从其他M中获取G,之后才去队列中拿G

1.Go goroutine

 

package main

import (
	"fmt"
	"time"
)

//子goroutine
func newTask() {
	i := 0
	for {
		i++
		fmt.Printf("new Goroutine : i = %d\n", i)
		time.Sleep(1 * time.Second)
	}
}

//主goroutine
func main() {
	//创建一个go程 去执行newTask() 流程
	go newTask()

	fmt.Println("main goroutine exit")

	/*
		i := 0
		for {
			i++
			fmt.Printf("main goroutine: i = %d\n", i)
			time.Sleep(1 * time.Second)
		}
	*/
}

package main

import (
	"fmt"
	"time"
)

func main() {

		//用go创建承载一个形参为空,返回值为空的一个函数
		go func() {
			defer fmt.Println("A.defer")

			func() {
				defer fmt.Println("B.defer")
				//退出当前goroutine
				runtime.Goexit()
				fmt.Println("B")
			}()

			fmt.Println("A")
		}()

	go func(a int, b int) bool {
		fmt.Println("a = ", a, ", b = ", b)
		return true
	}(10, 20)

	//死循环
	for {
		time.Sleep(1 * time.Second)
	}

}

2.channel的基本定义与使用

类似于socket通信

 

package main

import "fmt"

func main() {
	//定义一个channel
	c := make(chan int)

	go func() {
		defer fmt.Println("goroutine结束")

		fmt.Println("goroutine 正在运行...")

		c <- 666 //将666 发送给c
	}()

	num := <-c //从c中接受数据,并赋值给num

	fmt.Println("num = ", num)
	fmt.Println("main goroutine 结束...")
}

其中:chan有同步的功能,在666没传递过来之前main.go处于阻塞状态 

3.channel有缓冲与无缓冲同步问题

无缓冲同步

在第 1 步,两个 goroutine 都到达通道,但哪个都没有开始执行发送或者接收。
在第 2 步,左侧的 goroutine 将它的手伸进了了通道,这模拟了向通道发送数据的行为。这时,这个 goroutine 会在通道中被锁住,直到交换完成。
在第 3 步,右侧的 goroutine 将它的手放⼊入通道,这模拟了从通道里接收数据。这个 goroutine 一样也会在通道中被锁住,直到交换完成。
在第 4 步和第 5 步,进行交换,并最终,在第 6 步,两个 goroutine 都将它们的手从通道里拿出来,这模拟了被锁住的 goroutine 得到释放。两个 goroutine 现在都可以去做其他事情了。

在第 1 步,右侧的 goroutine 正在从通道接收一个值。
在第 2 步,右侧的这个 goroutine独立完成了接收值的动作,而左侧的goroutine 正在发送一个新值到通道里。
在第 3 步,左侧的goroutine 还在向通道发送新值,而右侧的 goroutine 正在从通道接收另外一个值。这个步骤里的两个操作既不是同步的,也不会互相阻塞。
最后,在第 4 步,所有的发送和接收都完成,而通道里还有几个值,也有一些空间可以存更更多的值。

特点: 当channel已经满,再向里⾯面写数据,就会阻塞 当channel为空,从里面取数据也会阻塞

有缓冲代码:

package main

import (
	"fmt"
	"time"
)

func main() {
	c := make(chan int, 3) //带有缓冲的channel

	fmt.Println("len(c) = ", len(c), ", cap(c)", cap(c))

	go func() {
		defer fmt.Println("子go程结束")

		for i := 0; i < 4; i++ {
			c <- i
			fmt.Println("子go程正在运行, 发送的元素=", i, " len(c)=", len(c), ", cap(c)=", cap(c))
		}
	}()

	time.Sleep(2 * time.Second)

	for i := 0; i < 4; i++ {
		num := <-c //从c中接收数据,并赋值给num
		fmt.Println("num = ", num)
	}

	fmt.Println("main 结束")
}

4.channel关闭特点

package main

import "fmt"

func main() {
	c := make(chan int)

	go func() {
		for i := 0; i < 5; i++ {
			c <- i
			//close可以关闭一个channel
			close(c)
		}
	}()

	for {
		//ok如果为true表示channel没有关闭,如果为false表示channel已经关闭
		if data, ok := <-c; ok {
			fmt.Println(data)
		} else {
			break
		}
	}

	fmt.Println("Main Finished..")
}

注:

channel不像文件一样需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束range循环之类的,才去关闭channel;
关闭channel后,无法向channel 再发送数据(引发 panic 错误后导致接收立即返回零值);
关闭channel后,可以继续从channel接收数据;
对于nil channel,无论收发都会被阻塞。

5.channel与range、select

package main

import "fmt"

func main() {
	c := make(chan int)

	go func() {
		for i := 0; i < 5; i++ {
			c <- i
		}

		//close可以关闭一个channel
		close(c)
	}()

	//可以使用range来迭代不断操作channel
	for data := range c {
		fmt.Println(data)
	}

	fmt.Println("Main Finished..")
}

单流程下一个go只能监控一个channel的状态,select可以完成监控多个channel的状态

select具备多路路channel的监控状态功能

package main

import "fmt"

func fibonacii(c, quit chan int) {
	x, y := 1, 1

	for {
		select {
		case c <- x:
			//如果c可写,则该case就会进来
			x = y
			y = x + y
		case <-quit:
			fmt.Println("quit")
			return
		}
	}
}

func main() {
	c := make(chan int)
	quit := make(chan int)

	//sub go
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println(<-c)
		}

		quit <- 0
	}()

	//main go
	fibonacii(c, quit)
}