go goroutine与channel详解

本文结构:

1、概念

2、goroutine

3、goroutine调度模型

4、不同goroutine之间通讯方式

5、goroutine之间的通信实例

6、select多路复用


1、概念:

进程和线程

1)进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位

2)线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位

3)一个进程能创建和撤销多个线程;同一进程中多个线程之间可以并发执行

并发和并行

1)多线程程序在一个核的CPU上运行,就是并发

2)多线程程序在多个核的cpu上运行,就是并行

协程和线程

1)协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上类似于用户级线程,这些用户级线程的调度也是自己实现(goroutine)

2)线程:一个线程上可以跑多个协程,协程是轻量级的线程

2、goroutine

在go语言中,每一个并发的执行单元叫做一个goroutine。当一个程序执行时,只有一个goroutine来调用main函数,这个routine称为主goroutine,新的goroutine通过关键字 go 进行创建—在函数/方法调用前加上go。

简单goroutine练习:

package main

import (
	"fmt"
	"time"
)

func test() {
	var i int
	for {
		fmt.Println(i)
		time.Sleep(time.Second)
		i++
	}
}

func main() {
	go test()
}

当运行程序,不会输出任何结果,因为主线程退出,协程没有机会执行,简单改进(在4goroutine通信会通过一个管道判断goroutine什么时候退出的):

package main

import (
	"fmt"
	"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)
	}
}

3、goroutine调度模型:

M代表操作系统里面的线程,P代表上下文,任何goroutine执行时,有上下文才能执行,保证执行状态、寄存器等,G代表goroutine(协程)

上图代表调度过程,两个M代表两个线程在跑,每个M挂四个协程,当前执行一个G,有三个在队列里面等待执行。从调度过程可以看出,一个物理线程可以跑多个goroutine,当创建上万个线程,实际在跑的goroutine可能只有几十个。因此,在go里不用担心创建的协程数太多导致系统响应不过来。

M0处,P执行G0的goroutine,另外三个goroutine处于等待状态,当G0要读文件执行io操作或读网络数据时,线程会阻塞,因为读文件,网络数据会很慢。P就执行不了任务,go调度机制会把M0-G0独立出来如右上角,并新创建一个线程M1,执行其他goroutine。

4、不同goroutine之间通讯方式

1)全局变量和锁同步

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)
	}
	lock.Lock()
	m[t.n] = sum
	lock.Unlock()
}

func main(){
	for i := 0; i<10;i++{
		t := &task{n:i}
		go calc(t)
	}
	time.Sleep(10*time.Second)
	lock.Lock()

	for k, v:=range m{
		fmt.Printf("%d != %v\n", k, v)
	}
	lock.Unlock()

}

2)channel 管道

概念:goroutine之间经常需要进行通信,管道channel就是goroutine之间的通信连接,它可以让一个goroutine发送特定值到另一个goroutine的通信机制。每一个管道都有具体类型,这叫做管道的元素类型,它规定了通过这个管道能发送和接收的数据类型。

管道的关闭可以通过调用close函数来完成,对关闭后的管道执行发送操作将会触发panic。在一个已经关闭的管道上进行接收操作,将获取所有已经发送的值,直到通道为空。

channel特点:a、类似unix中管道(pipe)

                       b、先进先出

                      c、线程安全,多个goroutine同时访问,不需要加锁

                      d、channel是有类型的,一个整数的channel只能存放整数

channel声明:a、var 变量名 chan 类型

                       b、var test chan int

                       c、var test chan string

                       d、var test chan map[string]string

                       e、var test chan stu

                       f、var test chan *stu

实例代码:

package main

import "fmt"

type student struct{
	name string
}
func main(){
	var intChan chan int
	intChan = make(chan int, 10)
	intChan <- 10  //写入数据
	var a int
	a = <- intChan //读出数据
	fmt.Println("a...", a)
	//结构体
	var stuChan chan student
	stuChan = make(chan student, 10)
	stu := student{name:"stu01"}
	stuChan <- stu  //写入数据
	var stu02 student
	stu02 = <- stuChan  //读出数据
	fmt.Println("stu02...", stu02)
	//指针
	var stuPChan chan *student
	stuPChan = make(chan *student, 10)
	stuPChan <- &stu  //写入数据
	var stu03 *student
	stu03 = <- stuPChan
	fmt.Println("stu03...", stu03)
}

channel和goroutine结合实例:

目的:写两个goroutine 一个往里面写数据,一个读出数据,并将结果打印出来。

package main

import (
	"fmt"
	"time"
)

func write(ch chan int){
	for i := 0; i < 100; i++ {
		ch <- i
	}
}

func read(ch chan int){
	for {
		var b int
		b = <- ch
		fmt.Println(b)
	}
}

func main(){
	intChan := make(chan int, 10)
	go write(intChan)
	go read(intChan)
	time.Sleep(10*time.Second)
}

   判读管道是否关闭:

func main(){
	var ch chan int
	ch = make(chan int, 20)
	for i := 0; i<20; i++{
		ch <- i
	}
	close(ch)
	for{
		var b int
		b, ok := <- ch
		if ok == false{
			fmt.Println("chan is close")
			break
		}
		fmt.Println(b)
	}
}

5、goroutine之间的通信实例

实例一:通过一个exitChan判断协程退出时关闭(1改进)

package main

import (
	"fmt"
)

func write(intChan chan int, exitChan chan bool) {
	for i := 0; i < 100; i++ {
		intChan <- i
	}
	exitChan <- true
	close(intChan)
}

func read(intChan chan int, exitChan chan bool) {
	for {
		i, ok := <-intChan
		if !ok {
			break
		}
		fmt.Println(i)
	}
	exitChan <- true
}

func main() {
	intChan := make(chan int, 100)
	exitChan := make(chan bool, 2)
	go write(intChan, exitChan)
	go read(intChan, exitChan)
	for i := 0; i < 2; i++ {
		<-exitChan
	}
}

实例二:打印10万以内的所有素数(此处用到exitChan判断goroutine什么时候退出)

package main

import (
	"fmt"
)

func calc(intChan chan int, resultChan chan int, exitChan chan bool) {
	for v := range intChan {
		flag := true
		for i := 2; i < v; i++ {
			if v%i == 0 {
				flag = false
				break
			}
		}
		if flag {
			resultChan <- v
		}
	}
	exitChan <- true
}

func main() {
	intChan := make(chan int, 1000)
	resultChan := make(chan int, 1000)
	exitChan := make(chan bool, 8)

	go func() {
		for i := 0; i < 100000; i ++ {
			intChan <- i
		}
		close(intChan)
	}()

	for i := 0; i < 8; i++ {
		go calc(intChan, resultChan, exitChan)
	}
	go func(){
		for i := 0; i < 8; i++ {
			<-exitChan
		}
		close(resultChan)
	}()

	for v := range resultChan {
		fmt.Println(v)
	}
}

6、select多路复用

select语句的形式其实和switch语句有点类似,这里每个case代表一个通信操作——在某个channel上发送或者接收,并且会包含一些语句组成的一个语句块 。

如果有多个管道操作准备完毕,select会随机选择其中一个执行。select中的default来设置当其它的操作都不能够马上被处理时程序需要执行的逻辑。

channel 的零值是nil, 并且对nil的channel 发送或者接收操作都会永远阻塞,在select语句中操作nil的channel永远都不会被select到。这可以让我们用nil来激活或者禁用case,来达成处理其他输出或者输出时间超时和取消的逻辑。
 

package main

import "fmt"

func main() {
	intChan := make(chan int, 10)
	for i := 0; i < 10; i++ {
		intChan <- i
	}
	for {
		b := <-intChan
		fmt.Println(b)
	}
}

以上程序会阻塞,报死锁,用select解决,如下:

package main

import (
	"fmt"
	"time"
)

func main() {
	intChan := make(chan int, 10)
	for i := 0; i < 10; i++ {
		intChan <- i
	}
	for {
		select {
		case v := <-intChan:
			fmt.Println(v)
		default:
			fmt.Println("time out")
			time.Sleep(time.Second)
		}
	}
}

运行结果如下:

当两个管道时,哪个管道有数据就取哪个,当没数据时,运行default分支:

package main

import (
	"fmt"
	"time"
)

func main() {
	intChan := make(chan int, 10)
	intChan2 := make(chan int, 10)

	go func() {
		i := 0
		for {
			intChan <- i
			time.Sleep(time.Second)
			intChan2 <- i * i
			i ++
		}

	}()

	for {
		select {
		case v := <-intChan:
			fmt.Println(v)
		case b := <-intChan2:
			fmt.Println(b)
		default:
			fmt.Println("time out")
			time.Sleep(time.Second)
		}
	}
}

结果如下:

 

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值