Go语言之并发篇五

/ Go多线程 / 没有评论 / 2038浏览

2019713151728-gopher

并发

Go 程

go f(x, y, z)
f(x, y, z)

代码:

package main

import (
	"fmt"
	"time"
)

func say(s string) {
	for i := 0; i < 5; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Println(s)
	}
}

func main() {
	go say("world")
	say("hello")
}

信道

ch <- v //将 v 发送至信道ch
v := <- ch //从 ch 接收值并赋予 v。

(“箭头”就是数据流的方向。)

ch := make(chan int)
package main

import "fmt"

func sum(s [] int,c chan int)  {
	sum := 0
	for _,v := range s {
		sum += v
	}
	c <- sum // 将和送入 c
}

func main() {
	s := [] int {7,2,-3,2,4,-5}
	c := make(chan int)

	go sum(s[:len(s)/2],c)
	go sum(s[len(s)/2:],c)
	x , y := <-c,<-c	// 从 c 中接收
	fmt.Println(x, y, x+y)
}

思考:向同一个信道多次发送值,接受的顺序如何?信道中的值存储的数据结构如何?

带缓冲的信道

ch := make(chan int, 100)
package main

import "fmt"

func main() {
	ch := make(chan int, 2)
	ch <- 1
	ch <- 2
	fmt.Println(<-ch)
	fmt.Println(<-ch)
}
1
2
package main

import "fmt"

func main() {
	ch := make(chan int, 2)
	ch <- 1
	ch <- 2
	ch <- 3
	fmt.Println(<-ch)
	fmt.Println(<-ch)
}
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
	/tmp/sandbox937270155/main.go:9 +0xa0
package main

import "fmt"

func main() {
	c := make(chan int)//此时buffer size默认为0,当前go程将一直阻塞(或者叫死锁)
	c <- 1
	fmt.Println("test")
}
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
package main

import "fmt"

func main() {
	c := make(chan int)
	//注意这里goroutine必须创建在给信道传值之前,否则在`c <- 1`就阻塞了,根本执行不到下面的代码
	go func() {
		t := <-c
		fmt.Println(t)
	}()

	c <- 1
	fmt.Println("test")
}
1
test

range 和 close

v, ok := <-ch

代码:

package main

import "fmt"

func fibonacci(n int,c chan int){
	x,y := 0,1
	for i := 0; i < n; i++{
		c <-x
		x,y = y,x+y
	}
	close(c)
}

func main(){
	c := make(chan int,10)
	go fibonacci(cap(c),c)

	for i := range c{
		fmt.Println(i)
	}
}

结果

0
1
1
2
3
5
8
13
21
34

select 语句

代码:

package main

import "fmt"

func fibonacci (c, quit chan int){
	x, y := 0, 1
	for{
		select{
		case c <- x:
			x, y = y, x+y
		case <-quit:
			fmt.Println("quit")
			return
		}
	}
}

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

	//goroutine1
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println(<-c)
		}
		quit <- 0
	}()
	//goroutine2
	fibonacci(c, quit)
}
0
1
1
2
3
5
8
13
21
34
quit

默认选择

select {
case i := <-c:
    // 使用 i
default:
    // 从 c 中接收会阻塞时执行
}
package main

import (
	"fmt"
	"time"
)

func main() {
	tick := time.Tick(100 * time.Millisecond)
	boom := time.After(500 * time.Millisecond)

	for {
		select {
		case <-tick:
			fmt.Println("tick.")
		case <-boom:
			fmt.Println("BOOM!")
			return
		default:
			fmt.Println("	.")
			time.Sleep(50 * time.Millisecond)
		}
	}
}
	.
	.
tick.
	.
	.
tick.
	.
	.
tick.
	.
	.
tick.
	.
	.
BOOM!

练习:等价二叉查找树

type Tree struct {
    Left  *Tree
    Value int
    Right *Tree
}
  1. 实现 Walk 函数。
  2. 测试 Walk 函数。 函数 tree.New(k) 用于构造一个随机结构的已排序二叉查找树,它保存了值 k 、 2k 、 3k ... 10k 。 创建一个新的信道 ch 并且对其进行步进:
go Walk(tree.New(1), ch)

然后从信道中读取并打印 10 个值。应当是数字 1, 2, 3, ..., 10 。 3. 用 Walk 实现 Same 函数来检测 t1t2 是否存储了相同的值。 4. 测试 Same 函数。 Same(tree.New(1), tree.New(1)) 应当返回 true ,而 Same(tree.New(1), tree.New(2)) 应当返回 false

代码

package main

import (
	"golang.org/x/tour/tree"
	"fmt"
)
/**
	二叉树小知识
	先序遍历:遍历顺序规则为【根左右】
	中序遍历:遍历顺序规则为【左根右】
	后序遍历:遍历顺序规则为【左右根】
 */

// Walk 步进 tree t 将所有的值从 tree 发送到 channel ch。
// 此处使用中序遍历
func Walk(t *tree.Tree, ch chan int) {
	if t.Left != nil{
		Walk(t.Left, ch)
	}
	ch <- t.Value
	if t.Right != nil{
		Walk(t.Right, ch)
	}
}

// Same 检测树 t1 和 t2 是否含有相同的值。
func Same(t1, t2 *tree.Tree) bool {
	ch1 := make(chan int)
	ch2 := make(chan int)
	go Walk(t1,ch1)
	go Walk(t2,ch2)

	for i := 0; i < 10; i++ {
		if <-ch1 != <-ch2{
			return false
		}
	}
	return true
}

func main() {
	ch := make(chan int)
	go Walk(tree.New(1), ch)
	for i := 0; i < 10; i++ {
		fmt.Println(<-ch)
	}

	fmt.Println(Same(tree.New(1),tree.New(1)))
	fmt.Println(Same(tree.New(1),tree.New(2)))
}
func Walk(t *tree.Tree, ch chan int) {
	ReWalk(t,ch)
	close(ch)
}

func ReWalk(t *tree.Tree, ch chan int) {
	if t.Left != nil{
		Walk(t.Left, ch)
	}
	ch <- t.Value
	if t.Right != nil{
		Walk(t.Right, ch)
	}
}

func main() {
	ch := make(chan int)
	go Walk(tree.New(1), ch)
	for i := range ch{
		fmt.Println(i)
	}

	fmt.Println(Same(tree.New(1),tree.New(1)))
	fmt.Println(Same(tree.New(1),tree.New(2)))
}

sync.Mutex

Lock
Unlock

代码:

package main

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

// SafeCounter 的并发使用是安全的。
type SafeCounter struct {
	v map[string] int
	mux sync.Mutex
}

// Inc 增加给定 key 的计数器的值。
func (c * SafeCounter)Inc(key string){
	c.mux.Lock()
	c.v[key]++
	c.mux.Unlock()
}

// Value 返回给定 key 的计数器的当前值。
func (c *SafeCounter)Value(key string)int  {
	c.mux.Lock()
	defer c.mux.Unlock()
	return c.v[key]
}

func main() {
	c := SafeCounter{v:make(map[string] int)}
	for i := 0;i < 100;i++{
		go c.Inc("somekey")
	}
	time.Sleep(time.Second)
	fmt.Println(c.Value("somekey"))
}

sync.WaitGroup

我们往往会遇到这样的场景,有多个任务(goroutine)在执行,需要等待最后一个任务执行结束,程序才能推出,针对这种场景我们可以使用 sync.WaitGroup

func say(s string) {
	time.Sleep(1000 * time.Millisecond)
	fmt.Println(s)
}

func TestScanTheHost(t *testing.T) {
	go say("world")
	fmt.Println("test")
}

say 方法执行时间很长需要一秒左右,主 go 程中没有等待 say就直接执行退出了,导致 go say("world") 得不到执行

func say(s string,wg sync.WaitGroup) {
	defer wg.Done()
	time.Sleep(1000 * time.Millisecond)
	fmt.Println(s)
}

func TestScanTheHost(t *testing.T) {
	var wg sync.WaitGroup
	wg.Add(1)
	go say("world",wg)
	fmt.Println("test")
	wg.Wait()
}

一开始这么写的,发现一直在等待,调查后发现这里有个坑,原来是 golang 里如果方法传递的不是地址,那么就会做一个拷贝,所以这里调用的 wg 根本就不是一个对象。简单更改如下:

//注意这里是 wg 的引用
func say(s string,wg *sync.WaitGroup) {
	defer wg.Done()
	time.Sleep(1000 * time.Millisecond)
	fmt.Println(s)
}

func TestScanTheHost(t *testing.T) {
	var wg sync.WaitGroup
	wg.Add(1)
	go say("world",&wg)
	fmt.Println("test")
	wg.Wait()
}

输出结果正常

test
world