【Go语言干货】从入门说起,你不得不要知道的goroutine和Channel

理论基础:Communication Sequential Process(CSP)
不要通过共享内存来通信;通过通信来共享内存

goroutine

  • 任何函数只需加上go就能送给调度器运行
  • 不需要在定义时区分是否是异步函数
  • 调度器在合适的点进行切换
  • 使用-race来检测数据访问冲突
  • 可能切换的点
    • I/O
    • select
    • 函数调用
    • 等待锁
    • channel
    • runtime.Gosched()
func main(){
	for i :=0;i<1000;i++{
		go func(test int){
			for{
				a[test] ++
				runtime.Gosched()
			}(i)
		}
		// 因为是并发执行,所以醒一下,否则循环完了,还没来得及输出,输出为空
		time.Sleep(time.Millisecond)
}

协程 Coroutine

  • 轻量级“线程”
  • 非抢占式多任务处理,由协程主动交出控制权
  • 编译器/解释器/虚拟机层面的多任务
  • 多个协程可能在一个或多个线程上运行
  • 子程序是协程的一个特例
  • 其他语言
    • C++:Boost.Coroutine
    • Java:不支持
    • Python
      • 使用yield关键字实现协程
      • Python 3.5加入了async def 对协程原生支持

Channel

一个channel是一个通信机制,它可以让一个goroutine通过它给另一个goroutine发送值信息。每个channel都有一个特殊的类型,也就是channels可发送数据的类型。一个可以发送int类型数据的channel一般写为chan int。

package main

import (
	"fmt"
	"time"
)

func worker(id int, c chan int) {
	// 接收方除了使用range来判断close,也可以通过如下代码来判断
	// n,ok := <-c
	// if !ok {
	// break;
	// }
	for n := range c {
		fmt.Printf("Worker %d received %c\n",
			id, n)
	}
}

func createWorker(id int) chan<- int {
	c := make(chan int)
	go worker(id, c)
	return c
}

func chanDemo() {
	// 发数据 chan<- 收数据 <-chan
	var channels [10]chan<- int
	for i := 0; i < 10; i++ {
		channels[i] = createWorker(i)
	}

	for i := 0; i < 10; i++ {
		channels[i] <- 'a' + i
	}

	for i := 0; i < 10; i++ {
		channels[i] <- 'A' + i
	}

	time.Sleep(time.Millisecond)
}

func bufferedChannel() {
	// 添加三个缓冲区,不需要切换协程
	c := make(chan int, 3)
	go worker(0, c)
	c <- 'a'
	c <- 'b'
	c <- 'c'
	c <- 'd'
	time.Sleep(time.Millisecond)
}

func channelClose() {
	c := make(chan int)
	go worker(0, c)
	c <- 'a'
	c <- 'b'
	c <- 'c'
	c <- 'd'
	// 发送发close
	close(c)
	time.Sleep(time.Millisecond)
}

func main() {
	fmt.Println("Channel as first-class citizen")
	chanDemo()
	fmt.Println("Buffered channel")
	bufferedChannel()
	fmt.Println("Channel close and range")
	channelClose()
}

传统的同步机制

  • WaitGroup
  • Mutex
  • Cond

测试

传统测试

  • 测试数据和测试逻辑混在一起
  • 出错信息不明确
  • 一旦一个数据出错测试全部结束
@Test public void testAdd(){
	assertEquals(3,add(1,2));
	assertEquals(2,add(0,2))assertEquals(0,add(0,0));
	assertEquals(0,add(-1,1));
	assertEquals(Integer.MIN_VALUE,add(1,Integer.MAX_VALUE));

表格驱动测试

  • 分离的测试数据和测试逻辑
  • 明确的出错信息
  • 可以部分失败
  • go语言的语法使得我们更易实践表格驱动测试

测试

  • go tool cover -html=c.out
  • benchmark
  • go tool pprof cpu.out (web graphviz)
  • 使用go test获取代码覆盖报告
  • 使用go tool cover查看代码覆盖报告
  • godoc -http :6060 查看文档

常用标准库

  • http
    • 使用http客户端发送请求
    • 使用http.Client控制请求头部等
    • 使用httputil简化工作
    • import _ “net/http/pprof”
    • 访问/debug/pprof/
    • 使用go tool pprof分析性能
  • bufio
  • log
  • encoding/json
  • regexp
  • time
  • strings/math/rand
  • 文档
    • godoc -http :8888
    • 标准库中文版
点赞