golang学习总结--协程、channel

1 golang协程(goroutine)

1.1 goroutine使用

golang在语言层面原生支持协程,在方法或函数前面加上关键字go,就会运行一个新的goroutine

package main

import (
	"fmt"
	"time"
)

func hello() {
	fmt.Println("Hello world goroutine")
}
func main() {
	go hello()
	time.Sleep(1 * time.Second)
	fmt.Println("main function")

}

注意:我让main函数sleep了1s钟。如果不加sleep,上述程序什么也不会输出,因为main函数先于协程结束了。这里就引出了一个问题:怎样让主线程等待所有的协程结束后再结束?2.4 ,2.5 ,2.6会讲到。请继续往下看

1.2 goroutine一些注意点

a.子协程异常挂掉,整个进程就会挂掉

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("run in main goroutine")
	go func() {
		fmt.Println("run in child goroutine")
		var ptr *int
		*ptr = 0x12345 // 故意制造崩溃
	}()
	time.Sleep(time.Second)
	fmt.Println("main goroutine will quit")
}

打印:

可以看到,协程崩溃后的打印信息没有输出

b.使用recover处理异常

recover 是一个Go语言的内建函数,可以让进入宕机流程中的 goroutine 恢复过来,recover 仅在延迟函数 defer 中有效。

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("run in main goroutine")
	go func() {
		defer func() { // 要在对应的协程里执行
			fmt.Println("执行defer:")
			if err := recover(); err != nil {
				fmt.Println("捕获error:", err)
			}
		}()
		var ptr *int
		*ptr = 0x12345 // 故意制造崩溃
		fmt.Println("run in child goroutine")
	}()
	time.Sleep(time.Second)
	fmt.Println("main goroutine will quit")
}

2 golang通道(channel)

go语言的并发机制是使用协程,channel就是协程间的通信机制。通道是一个容器,它可以向容纳协程写入的数据,也可以向协程提供数据,即协程对它可读可写;协程是有大小的,需要注意满或空;通道也是有类型的,它可以限定存放的数据类型

2.1 创建channel

channel分为两种:缓冲型通道和非缓冲型通道,都使用make创建,只此一种方法

缓冲型:var bufChannel = make(chan int, 10)

非缓冲型:var nonebufChannel = make(chan int)

2.2 读写channel

使用go提供的语法<-,通道在箭头左边:写通道;通道在箭头右边:读通道

ch <- value //写通道
calue <- ch //读通道

2.3 读写阻塞

通道为空,读操作阻塞,通道满了,写操作阻塞

2.4 WaitGroup  --实现主线程等待所有协程结束再退出

sync.WaitGroup是等待一组协程结束,sync.WaitGroup只有3个方法,Add()添加一个计数,Done()减去一个计数,Wait()阻塞直到所有任务完成。

package main

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

var wg sync.WaitGroup //定义一个同步等待的组

func task(i int) {
	fmt.Println("task...", i)
	//耗时操作任务,网络请求,读取文件
	time.Sleep(time.Second)
	wg.Done() //减去一个计数
}

func main() {
	for i := 0; i < 10; i++ {
		wg.Add(1) //添加一个计数
		go task(i)
	}
	wg.Wait() //阻塞直到所有任务完成
	fmt.Println("over")
}

2.5 缓冲通道

-- 利用缓冲通道等待所有协程结束  --无序

package main

import (
	"fmt"
)

var ch = make(chan int, 10)

func task(i int) {
	fmt.Println("task...", i)
	ch <- i
}

func main() {
	for i := 0; i < 10; i++ {
		go task(i)
	}
	for i := 0; i < 10; i++ {
		<-ch
	}
	fmt.Println("over")
}

2.6 无缓冲通道

--利用无缓冲通道等待所有协程结束  --有序

package main

import (
	"fmt"
	"time"
)

var ch = make(chan int)

func task(i int) {
	fmt.Println("task...", i)
	time.Sleep(time.Second)
	<-ch
}

func main() {
	for i := 0; i < 10; i++ {
		go task(i)
		ch <- i
	}
	fmt.Println("over")
}

2.7 select 

select语句用于在多个发送/接收信道操作中进行选择。select语句会一直阻塞,直到发送/接收操作准备就绪。如果有多个信道操作准备完毕,select会随机地选取其中之一执行。

package main

import (
	"fmt"
	"time"
)

func send(ch chan int, gap time.Duration) {
	i := 0
	for {
		i++
		ch <- i
		time.Sleep(gap)
	}
}

func recv(ch1 chan int, ch2 chan int) {
	for {
		select {
		case v := <-ch1:
			fmt.Printf("recv %d from ch1\n", v)
		case v := <-ch2:
			fmt.Printf("recv %d from ch2\n", v)
		}
	}
}

func main() {
	var ch1 = make(chan int)
	var ch2 = make(chan int)
	go send(ch1, time.Second)
	go send(ch2, 2*time.Second)
	recv(ch1, ch2)
}

2.8 非阻塞读写

2.7的写法只有当通道有内容时才会消费,通道为空时阻塞。如果我们加上default分支,这样当通道为空时就会执行default分支,起到了非阻塞读写的效果

package main

import (
	"fmt"
	"time"
)

func send(ch1 chan int, ch2 chan int) {
	i := 0
	for {
		i++
		select {
		case ch1 <- i:
			fmt.Printf("send ch1 %d\n", i)
		case ch2 <- i:
			fmt.Printf("send ch2 %d\n", i)
		default:
			fmt.Printf("ch block\n")
			time.Sleep(2 * time.Second) // 这里只是为了演示
		}
	}
}

func recv(ch chan int, gap time.Duration, name string) {
	for v := range ch {
		fmt.Printf("receive %s %d\n", name, v)
		time.Sleep(gap)
	}
}

func main() {
	// 无缓冲通道
	var ch1 = make(chan int)
	var ch2 = make(chan int)
	// 两个消费者的休眠时间不一样,名称不一样
	go recv(ch1, time.Second, "ch1")
	go recv(ch2, 2*time.Second, "ch2")
	send(ch1, ch2)
}

猜你喜欢

转载自blog.csdn.net/weixin_44843859/article/details/110817318