Go基础教程05-并发

75 阅读2分钟

goroutine 协程

协程是goruntime管理的轻量级线程, go f(args)会启动一个goroutine并执行f(args), f和args的求值在当前的goroutine中, 而f的执行在新的goroutine中

package main

import (
	"fmt"
	"time"
)

func f(tag string) {
	for i := 0; i < 3; i++ {
		fmt.Println(tag, "-", i)
	}
}

func main() {
	f("main thread")
	go f("goroutine")
	go func() {
		fmt.Println("takeing ...")
	}()
	time.Sleep(time.Second)
	fmt.Println("finished")
}
//main thread - 0
//main thread - 1
//main thread - 2
//goroutine - 0
//takeing ...
//goroutine - 1
//goroutine - 2
//finished

可以看到2个协程是交替输出的, 表示go runtime是以并发的方式运行协程的, 等待协程的完成我们这里用的是sleep, 更好的方式是用WaitGroup.

信道

channels可以连接多个协程, 从一个协程将值发送到信道, 在另一个协程中接收, 使用make(chan val-type)创建一个新通道, 类型就是需要传递值的类型, 使用channel <- val把val发送到通道中, 使用<-channel从通道中接收值, 箭头方向是数据流的方向;默认的发送和接收操作是阻塞的, 直到发送和接收方都就绪,这可以使goroutine在没有显式的锁和竟态变量的情况下同步.

package main

import "fmt"

func main() {
	msgs := make(chan string)

	go func() {
		msgs <- "ping"
	}()

	msg := <-msgs
	fmt.Println(msg)
}

信道缓冲

默认情况下, 信道是无缓冲的, 也就是说对应的接收方准备好接收时,才允许发送, 有缓冲的信道允许没有接收者时, 缓存一定数量的值.

package main

import "fmt"

func main() {
	msgs := make(chan string, 2)
	msgs <- "ping"
	msgs <- "pong"
	fmt.Println(<-msgs) //ping
	fmt.Println(<-msgs) //pong
}

信道同步

利用信道阻塞的特性来通知协程的函数完成.

package main

import (
	"fmt"
	"time"
)

func ping(done chan bool) {
	fmt.Println("ping")
	time.Sleep(time.Second)
	fmt.Println("pong")
	done <- true
}

func main() {
	done := make(chan bool)
	go ping(done)
	<-done  // 如果没有接收, 程序不等协程执行就会直接退出了
}

信道方向

chan作为函数参数时, 可以指定信道是否为只读或者只写.

package main

import "fmt"

func ping(p chan<- string, msg string) {
	p <- msg
}

func pong(p <-chan string, q chan<- string) {
	msg := <-p
	q <- msg
}

func main() {
	p := make(chan string, 1)
	q := make(chan string, 1)
	ping(p, "~~~~~~")
	pong(p, q)
	fmt.Println(<-q)
}

range和close

发送者可以通过close关闭信道, 接收者可以用v,ok:=<-ch测试信道是否关闭, 如果关闭,ok为false.

只有发送者才能关闭信道, 向关闭的信道发送数据会引发panic

信道通常情况下不需要关闭, 只有在必须告诉接收者是否还需要接收时才有必要关闭,比如终止循环.

package main

import "fmt"

func fib(n int, c chan int) {
	a, b := 0, 1
	for i := 0; i < n; i++ {
		c <- a
		a, b = b, a+b
	}
	close(c)
}

func main() {
	c := make(chan int, 10)
	go fib(cap(c), c)
	for i := range c {
		fmt.Println(i)
	}
}

信道选择器

select选择器可以让你同时等待多个信道操作.

package main

import (
	"fmt"
	"time"
)

func main() {
	start := time.Now()
	c1 := make(chan string)
	c2 := make(chan string)

	go func() {
		time.Sleep(1 * time.Second)
		c1 <- "one"
	}()

	go func() {
		time.Sleep(2 * time.Second)
		c2 <- "two"
	}()
	for i := 0; i < 2; i++ {
		select {
		case msg1 := <-c1:
			fmt.Println("revc:", msg1)
		case msg2 := <-c2:
			fmt.Println("revc:", msg2)
		}
	}
	fmt.Println("use time:", time.Now().Sub(start))
}
//revc: one
//revc: two
//use time: 2.00109499s

如预期一样, 先收到one,后收到two

超时处理

<-time.After()等待超时几秒, 我们等待时间长一点就成功接收到c2的值了,select 会阻塞到某个分支可以继续执行为止,这时就会执行该分支;当多个分支都准备好时会随机选择一个执行.

package main

import (
	"fmt"
	"time"
)

func main() {
	c1, c2 := make(chan string, 1), make(chan string, 1)
	go func() {
		time.Sleep(2 * time.Second)
		c1 <- "1"
	}()
	select {
	case r1 := <-c1:
		fmt.Println(r1)
	case <-time.After(time.Second):
		fmt.Println("timeout 1")
	}

	go func() {
		time.Sleep(2 * time.Second)
		c2 <- "2"
	}()
	select {
	case r2 := <-c2:
		fmt.Println(r2)
	case <-time.After(3 * time.Second):
		fmt.Println("timeout 2")
	}
}
//timeout 1
//2

互斥锁

如果在并发中不需要通信, 只想保证每次只要一个协程能访问一个共享变量, 就需要用互斥锁来提供这种机制, sync.Mutex互斥锁提供了Lock和UnLock两个方法.

package main

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

type Counter struct {
	c  map[string]int
	mu sync.Mutex
}

func (co *Counter) inc(name string) {
	co.mu.Lock()
	defer co.mu.Unlock() // 函数结束时解锁
	co.c[name]++
}

func main() {
	co := Counter{
		c: map[string]int{"test": 0},
	}
	for i := 0; i < 1000; i++ {
		go co.inc("test")
	}
	time.Sleep(time.Second)
	fmt.Println(co.c["test"])
}

学习更多Golang知识

不负春光 不负自己

戳“阅读原文”我们一起进步